Veuillez lire et accepter notre politique d'utilisation avant d'accéder à votre compte.
+
Confidentialité des données
+
Vos informations personnelles et professionnelles sont protégées. Toute divulgation non autorisée est strictement interdite.
+
Utilisation autorisée
+
L’application est réservée à un usage professionnel. Toute utilisation à des fins personnelles ou non autorisées est prohibée.
+
Sécurité des comptes
+
Ne partagez jamais vos identifiants. Changez votre mot de passe régulièrement et en cas de suspicion d’intrusion.
+
Responsabilités de l’utilisateur
+
Vous êtes responsable des actions effectuées via votre compte. Signalez toute anomalie ou problème à l’équipe RH ou à l’administrateur.
+
Acceptation et conformité
+
En cliquant sur “J’accepte”, vous confirmez avoir lu et accepté cette politique. Le non-respect peut entraîner une suspension ou une révocation de l’accès.
+
+
\ No newline at end of file
diff --git a/SIRH/urls.py b/SIRH/urls.py
new file mode 100644
index 0000000..50b9078
--- /dev/null
+++ b/SIRH/urls.py
@@ -0,0 +1,67 @@
+"""
+URL configuration for SIRH project.
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/5.2/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+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.contrib import admin
+from django.urls import include, path
+from django.conf.urls.static import static
+from django.conf import settings
+# from simple_sso.sso_server.server import Server
+
+from . import views
+
+# server_sso = Server()
+
+urlpatterns = [
+ path(
+ '',
+ views.login_view,
+ name='index'
+ ),
+
+ path('login/',
+ views.login_view,
+ name='login'
+ ),
+ path(
+ 'deconnexion/',
+ views.deconnexion_view,
+ name='deconnexion'
+ ),
+ path(
+ 'employé/',
+ include("gestion_employe.urls")
+ ),
+ path(
+ 'gestion-conge/',
+ include("gestion_conge.urls")
+ ),
+ path(
+ 'gestion-projet/',
+ include("gestion_projet.urls")
+ ),
+ path(
+ 'gestion-salle/',
+ include("gestion_salle.urls")
+ ),
+ path(
+ 'admin/',
+ admin.site.urls
+ ),
+ # path(
+ # 'sso',
+ # include(server_sso.get_urls())
+ # )
+] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/SIRH/views.py b/SIRH/views.py
new file mode 100644
index 0000000..77a057d
--- /dev/null
+++ b/SIRH/views.py
@@ -0,0 +1,37 @@
+from django.contrib.auth import authenticate, login, logout
+from django.shortcuts import render, redirect
+from django.contrib import messages
+
+def login_view(request):
+ """
+ Gère la connexion des utilisateurs avec redirection selon le rôle et
+ vérification de l'acceptation de la politique d'utilisation.
+ """
+ if request.method == 'POST':
+ email = request.POST.get('mail')
+ password = request.POST.get('mot_de_passe')
+
+ if not (email and password):
+ messages.error(request, "Veuillez remplir tous les champs.")
+ return render(request, 'login.html')
+
+ user = authenticate(request, username=email, password=password)
+
+ if user is None:
+ messages.error(request, "Nom d’utilisateur ou mot de passe incorrect.")
+ return render(request, 'login.html')
+
+ if not user.is_active:
+ messages.error(request, "Compte inactif. Contactez l'administrateur.")
+ return render(request, 'login.html')
+
+ login(request, user)
+
+ return redirect("gestion_conges:conge")
+
+ return render(request, 'login.html')
+
+def deconnexion_view(request):
+ """Gère la déconnexion de l'utilisateur."""
+ logout(request)
+ return redirect('login')
\ No newline at end of file
diff --git a/SIRH/wsgi.py b/SIRH/wsgi.py
new file mode 100644
index 0000000..5f96c06
--- /dev/null
+++ b/SIRH/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for SIRH project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'SIRH.settings')
+
+application = get_wsgi_application()
diff --git a/fonction_utilitaire/fonctions_utilitaire.py b/fonction_utilitaire/fonctions_utilitaire.py
new file mode 100644
index 0000000..1ccdba0
--- /dev/null
+++ b/fonction_utilitaire/fonctions_utilitaire.py
@@ -0,0 +1,34 @@
+from django.utils import timezone
+from gestion_employe.models import Contrat
+from gestion_conge.models import Conge
+
+
+QUOTA_CONGE_ANNUEL = 30
+NOMBRE_PAGINATION = 8
+DEBUT_RAPPEL = 60
+DUREE_FIN_CONTRAT = 90
+
+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()
+
+ if contrat is None or not contrat.date_debut:
+ return {
+ "success": False,
+ "message": "Votre contrat de travail n'a pas été correctement renseigner. Veuillez contacter les ressources humaines."
+ }
+
+ conges = Conge.objects.filter(employe=employe, validation_direction=True, date_fin__year = timezone.now().date().year)
+ jours_conges_valider = sum([conge.nombre_jours for conge in conges])
+
+ if jours_conges_valider >= QUOTA_CONGE_ANNUEL:
+ return {
+ "success": False,
+ "message": "Vous avez atteint le nombre maximal de jours de congés. Veuillez contacter l'administration."
+ }
+
+ return {
+ "success": True,
+ "quota_annuel": QUOTA_CONGE_ANNUEL - jours_conges_valider,
+ "nombre_jours_valide": jours_conges_valider
+ }
\ No newline at end of file
diff --git a/gestion_conge/__init__.py b/gestion_conge/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gestion_conge/admin.py b/gestion_conge/admin.py
new file mode 100644
index 0000000..ea5d68b
--- /dev/null
+++ b/gestion_conge/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/gestion_conge/apps.py b/gestion_conge/apps.py
new file mode 100644
index 0000000..c320df3
--- /dev/null
+++ b/gestion_conge/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class GestionCongeConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'gestion_conge'
diff --git a/gestion_conge/forms.py b/gestion_conge/forms.py
new file mode 100644
index 0000000..65a7291
--- /dev/null
+++ b/gestion_conge/forms.py
@@ -0,0 +1,17 @@
+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']
+ widgets = {
+ 'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
+ 'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
+ 'type': forms.Select(attrs={'class': 'form-select'}),
+ }
+
+ labels = {
+ 'nombre_jours':'Nombre de jours',
+ }
\ No newline at end of file
diff --git a/gestion_conge/migrations/0001_initial.py b/gestion_conge/migrations/0001_initial.py
new file mode 100644
index 0000000..6a7e09d
--- /dev/null
+++ b/gestion_conge/migrations/0001_initial.py
@@ -0,0 +1,30 @@
+# Generated by Django 5.2.13 on 2026-04-17 12:03
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('gestion_employe', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Conge',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('date_debut', models.DateField(verbose_name='Date de Début')),
+ ('date_fin', models.DateField(verbose_name='Date de Fin')),
+ ('type', models.CharField(choices=[('conge_annuel', 'Conge Annuel')], max_length=100, verbose_name='Type de Congé')),
+ ('date_demande', models.DateField(auto_now_add=True, verbose_name='Date de Demande')),
+ ('validation_hierarchique', models.BooleanField(default=None, null=True)),
+ ('validation_direction', models.BooleanField(default=None, null=True)),
+ ('motif_refus', models.TextField(blank=True, null=True)),
+ ('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='employe', to='gestion_employe.employe')),
+ ],
+ ),
+ ]
diff --git a/gestion_conge/migrations/__init__.py b/gestion_conge/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gestion_conge/models.py b/gestion_conge/models.py
new file mode 100644
index 0000000..f72bbe8
--- /dev/null
+++ b/gestion_conge/models.py
@@ -0,0 +1,36 @@
+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'),
+ ('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,
+ on_delete=models.CASCADE,
+ related_name="employe"
+ )
+ date_debut = models.DateField(verbose_name='Date de Début')
+ date_fin = models.DateField(verbose_name='Date de Fin')
+ type = models.CharField(max_length=100, choices=TYPE_CHOICES, verbose_name='Type de Congé')
+ date_demande = models.DateField(auto_now_add=True, verbose_name="Date de Demande")
+ validation_hierarchique = models.BooleanField(default=None, null=True)
+ validation_direction = models.BooleanField(default=None, null=True)
+
+ motif_refus = models.TextField(blank=True, null=True)
+
+ @property
+ def nombre_jours(self):
+ if self.date_debut and self.date_fin:
+ jours = pd.bdate_range(start=self.date_debut, end=self.date_fin)
+ return len(jours)
\ No newline at end of file
diff --git a/gestion_conge/static/gestion_conge/js/detail_conges.js b/gestion_conge/static/gestion_conge/js/detail_conges.js
new file mode 100644
index 0000000..fd39b94
--- /dev/null
+++ b/gestion_conge/static/gestion_conge/js/detail_conges.js
@@ -0,0 +1,75 @@
+const bouton_enregistrer_detail = document.getElementById("bouton-enregistrer-detail-conge");
+
+if(bouton_enregistrer_detail){
+ bouton_enregistrer_detail.addEventListener("click", () => {
+ const form = document.getElementById("form-detail-conge");
+ const csrftoken = new FormData(form).get("csrfmiddlewaretoken");
+ const actionUrl = form.action;
+ const id_conge = document.getElementById("id_conge").value;
+ const validation_hierarchique_input = document.querySelector('input[name="validation_hierarchique"]:checked');
+ const validation_hierarchique = validation_hierarchique_input ? validation_hierarchique_input.value : null;
+ const validation_direction_input = document.querySelector('input[name="validation_direction"]:checked');
+ const validation_direction = validation_direction_input ? validation_direction_input.value : null;
+ const motif_refus = document.getElementById("motif_refus").value;
+
+ fetch(actionUrl, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken": csrftoken
+ },
+ body: JSON.stringify({
+ id_conge,
+ validation_hierarchique,
+ validation_direction,
+ motif_refus
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ alert(data.message);
+ navigation.reload();
+ });
+ })
+}
+
+if(document.getElementById("validation_hierarchique_refuse")){
+ document.getElementById("validation_hierarchique_refuse").addEventListener('click', function(){
+ if(this.checked){
+ alert("coucou");
+ document.getElementById("motif_refus_container").className="d-block form-group mt-3";
+ }else{
+ document.getElementById("motif_refus_container").className="d-none";
+ }
+ })
+}
+
+if(document.getElementById("validation_hierarchique_refuse")){
+ document.getElementById("validation_hierarchique_refuse").addEventListener('click', function(){
+ if(this.checked){
+ document.getElementById("motif_refus_container").className="d-block form-group mt-3";
+ }else{
+ document.getElementById("motif_refus_container").className="d-none";
+ }
+ })
+}
+
+if(document.getElementById("validation_hierarchique_valide")){
+ document.getElementById("validation_hierarchique_valide").addEventListener('click', function(){
+ if(this.checked){
+ document.getElementById("motif_refus_container").className="d-none";
+ }else{
+ document.getElementById("motif_refus_container").className="d-block form-group mt-3";
+ }
+ })
+}
+
+if(document.getElementById("validation_direction_valide")){
+ document.getElementById("validation_direction_valide").addEventListener('click', function(){
+ if(this.checked){
+ document.getElementById("motif_refus_container").className="d-block form-group mt-3";
+ }else{
+ document.getElementById("motif_refus_container").className="d-none";
+ }
+ })
+}
\ No newline at end of file
diff --git a/gestion_conge/static/gestion_conge/js/index.js b/gestion_conge/static/gestion_conge/js/index.js
new file mode 100644
index 0000000..1d156cf
--- /dev/null
+++ b/gestion_conge/static/gestion_conge/js/index.js
@@ -0,0 +1,71 @@
+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:{
+ inputFormat:"yyyy-MM-dd",
+ outputFormat:"dd/MM/yy",
+ }},
+ {"title": "Date de fin", "field": "date_fin", formatter:"datetime", formatterParams:{
+ inputFormat:"yyyy-MM-dd",
+ outputFormat:"dd/MM/yy",
+ }},
+ {"title": "Type de congé", "field": "type"},
+ {"title": "Date de la demande", "field": "date_demande"},
+ {"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{
+ allowEmpty : true ,
+ }},
+ {"title": "Validation par supérieur hiérarchique", "field": "validation_direction", formatter:"tickCross", formatterParams :{
+ allowEmpty : true ,
+ }},
+ ],
+ pagination: true,
+ paginationSize: 5
+})
+
+const bouton_demande_conges = $("bouton-demande-conge");
+
+bouton_demande_conges.addEventListener("click", (e) => {
+ var modalDemandeConge = new bootstrap.Modal(document.getElementById('modalDemandeConge'));
+ modalDemandeConge.show();
+})
+
+tableau_liste_demande_conge.on("rowClick", function(row, rowData) {
+ const data = rowData.getData();
+ $("id_conge").value = data.id;
+ $("employe").value = data.prenom_nom;
+ $("type_conge").value = data.type;
+ $("date_debut").value = data.date_debut;
+ $("date_fin").value = data.date_fin;
+ $("date_demande").value = data.date_demande;
+ $("nombre_jours").value = data.nombre_jours;
+ $("solde_restant").value = data.solde_conge;
+ $("motif_refus").value = data.motif_refus;
+
+ if($("validation_hierarchique_valide") & $("validation_hierarchique_refuse")){
+ $("validation_hierarchique_valide").checked = data.validation_hierarchique === true;
+ $("validation_hierarchique_refuse").checked = data.validation_hierarchique === false;
+ }
+
+ if($("validation_direction_valide") & $("validation_direction_refuse")){
+ $("validation_direction_valide").checked = data.validation_direction === true;
+ $("validation_direction_refuse").checked = data.validation_direction === false;
+ }
+
+ const modal = new bootstrap.Modal(document.getElementById('detailsCongeModal'));
+ modal.show();
+});
+
+fetch(url_liste_conge_attente)
+ .then(response => response.json())
+ .then(data => {
+ if(data.success === true){
+ tableau_liste_demande_conge.setData(data.data)
+ }else{
+ alert(data.message)
+ }
+ })
diff --git a/gestion_conge/templates/gestion_conge/index.html b/gestion_conge/templates/gestion_conge/index.html
new file mode 100644
index 0000000..17d1ff6
--- /dev/null
+++ b/gestion_conge/templates/gestion_conge/index.html
@@ -0,0 +1,51 @@
+{% extends "BASE.html" %}
+{% load static %}
+{% block 'titre_page' %} Gestion des congés {% endblock %}
+{% block 'contenu' %}
+ {% if messages %}
+ {% for message in messages %}
+
{{message}}
+ {% endfor %}
+ {% endif %}
+
+
+
+ Congés refusés
+
+
+
{{ nombre_conges_refuse }}
+
+
+
+
+ Congés en attente
+
+
+
{{ nombre_conges_en_attente }}
+
+
+
+
+ Congés Validé
+
+
+
{{ nombre_conges_valide }}
+
+
+
+
Liste des demandes de congé
+
+
+
+
+
+
+{% endblock %}
+{% block 'modal' %}
+ {% include 'gestion_conge/parts/modalDemandeConge.html' %}
+ {% include 'gestion_conge/parts/modalDetailConge.html' %}
+{% endblock %}
+{% block 'js' %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/gestion_conge/templates/gestion_conge/parts/modalDemandeConge.html b/gestion_conge/templates/gestion_conge/parts/modalDemandeConge.html
new file mode 100644
index 0000000..511d7dc
--- /dev/null
+++ b/gestion_conge/templates/gestion_conge/parts/modalDemandeConge.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Nouvelle demande de congé
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_conge/templates/gestion_conge/parts/modalDetailConge.html b/gestion_conge/templates/gestion_conge/parts/modalDetailConge.html
new file mode 100644
index 0000000..d3d3051
--- /dev/null
+++ b/gestion_conge/templates/gestion_conge/parts/modalDetailConge.html
@@ -0,0 +1,92 @@
+
+
+
+
+
Détails de la demande de congés
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_conge/templates/gestion_conge/parts/modalModificationConge.html b/gestion_conge/templates/gestion_conge/parts/modalModificationConge.html
new file mode 100644
index 0000000..7a920ca
--- /dev/null
+++ b/gestion_conge/templates/gestion_conge/parts/modalModificationConge.html
@@ -0,0 +1,48 @@
+{% for conge in conges %}
+
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+ {% endif %}
+
+
+
+ Nombre d'employés
+
{{ nombre_employes }}
+
+
+ Nombre de prestataires
+
{{ nombre_cps }}
+
+
+ Nombre de stagiaires
+
{{ nombre_stage }}
+
+
+
+
+
+
+ La liste des employés
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+{% block 'modal' %}
+ {% include "gestion_employe/parts/modalDetailEmploye.html" %}
+ {% include "gestion_employe/parts/modalCreationContrat.html" %}
+ {% include "gestion_employe/parts/modalAffectationProjet.html" %}
+ {% include "gestion_employe/parts/modalDocument.html" %}
+ {% include "gestion_employe/parts/modalListeContratExpirants.html" %}
+{% endblock %}
+{% block 'js' %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/gestion_employe/templates/gestion_employe/modifier-profil.html b/gestion_employe/templates/gestion_employe/modifier-profil.html
new file mode 100644
index 0000000..c73010e
--- /dev/null
+++ b/gestion_employe/templates/gestion_employe/modifier-profil.html
@@ -0,0 +1,105 @@
+{% extends "BASE.html" %}
+{% load static %}
+{% block 'titre_page' %} Gestion des employés - Mon profil {% endblock %}
+{% block 'contenu' %}
+
+
+
+ Modifier mon profil
+
+
+
+
+
+
+{% endblock %}
+{% block 'modal' %}
+{% endblock %}
+{% block 'js' %}
+{% endblock %}
\ No newline at end of file
diff --git a/gestion_employe/templates/gestion_employe/parts/modalAffectationProjet.html b/gestion_employe/templates/gestion_employe/parts/modalAffectationProjet.html
new file mode 100644
index 0000000..2abcf46
--- /dev/null
+++ b/gestion_employe/templates/gestion_employe/parts/modalAffectationProjet.html
@@ -0,0 +1,28 @@
+
+
+
+
+
+ Affectation de projet ()
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_employe/templates/gestion_employe/parts/modalAjoutFormation.html b/gestion_employe/templates/gestion_employe/parts/modalAjoutFormation.html
new file mode 100644
index 0000000..104b791
--- /dev/null
+++ b/gestion_employe/templates/gestion_employe/parts/modalAjoutFormation.html
@@ -0,0 +1,21 @@
+
+
+
+
+
Ajouter un certificat
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_employe/templates/gestion_employe/parts/modalCreationContrat.html b/gestion_employe/templates/gestion_employe/parts/modalCreationContrat.html
new file mode 100644
index 0000000..fa0bb2b
--- /dev/null
+++ b/gestion_employe/templates/gestion_employe/parts/modalCreationContrat.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+ Contrat de
+
+
+
+
+
+ {% comment %}
+ {% endcomment %}
+
+
+
+
\ No newline at end of file
diff --git a/gestion_employe/templates/gestion_employe/parts/modalDetailEmploye.html b/gestion_employe/templates/gestion_employe/parts/modalDetailEmploye.html
new file mode 100644
index 0000000..187ef49
--- /dev/null
+++ b/gestion_employe/templates/gestion_employe/parts/modalDetailEmploye.html
@@ -0,0 +1,136 @@
+{% load tags_personnaliser %}
+
\ No newline at end of file
diff --git a/gestion_employe/templates/gestion_employe/parts/modalDocument.html b/gestion_employe/templates/gestion_employe/parts/modalDocument.html
new file mode 100644
index 0000000..4eeb124
--- /dev/null
+++ b/gestion_employe/templates/gestion_employe/parts/modalDocument.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+ Documents RH de
+
+
+
+
+
+
+
+
+ Diplôme :
+
+
+
+ CV :
+
+
+
+ RIB :
+
+
+
+ Casier judiciaire :
+
+
+
+
+
+
+
+
+
+
diff --git a/gestion_employe/templates/gestion_employe/parts/modalListeContratExpirants.html b/gestion_employe/templates/gestion_employe/parts/modalListeContratExpirants.html
new file mode 100644
index 0000000..9051355
--- /dev/null
+++ b/gestion_employe/templates/gestion_employe/parts/modalListeContratExpirants.html
@@ -0,0 +1,17 @@
+
+
+
+
+
Contrats expirants dans un maximum de 60 jours
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_employe/templates/gestion_employe/parts/modificationMotPasse.html b/gestion_employe/templates/gestion_employe/parts/modificationMotPasse.html
new file mode 100644
index 0000000..79366ba
--- /dev/null
+++ b/gestion_employe/templates/gestion_employe/parts/modificationMotPasse.html
@@ -0,0 +1,36 @@
+
Ajout du financement au projet (Nom du projet ici)
+
+
+
+
+{% endblock %}
+{% block 'modal' %}
+ {% include "gestion_projet/parts/modalAjoutProjet.html" %}
+{% endblock %}
+{% block 'js' %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/creation_projet.html b/gestion_projet/templates/gestion_projet/creation_projet.html
new file mode 100644
index 0000000..c04c5d4
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/creation_projet.html
@@ -0,0 +1,32 @@
+{% extends "BASE.html" %}
+{% load static %}
+{% block 'titre_page' %} Gestion des projets {% endblock %}
+{% block 'contenu' %}
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+ {% endif %}
+ {% comment %}
Enregistrement d'un nouveau projet
{% endcomment %}
+
+
+
Enregistrement d'un nouveau projet
+
+
+
+
+{% endblock %}
+{% block 'modal' %}
+{% endblock %}
+{% block 'js' %}
+{% endblock %}
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/index.html b/gestion_projet/templates/gestion_projet/index.html
new file mode 100644
index 0000000..70ba584
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/index.html
@@ -0,0 +1,61 @@
+{% extends "BASE.html" %}
+{% load static %}
+{% load tags_personnaliser %}
+{% block 'titre_page' %} Gestion des projets {% endblock %}
+{% block 'contenu' %}
+
Gestion des projets
+{% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+{% endif %}
+
+
+ Projets en cours
+
{{projet_en_cours}}
+
+
+ Budget Total (GNF)
+
{{budget_total}}
+
+
+ Personnel sous projet
+
{{nombre_personnel}}
+
+
+
+
+
La liste des projets
+ {% if user|has_group:"ressource_humaine" %}
+
+
+
+
+ {% endif %}
+
+
+
+
+
+
+
+{% endblock %}
+{% block 'modal' %}
+ {% include "gestion_projet/parts/modalAjoutProjet.html" %}
+ {% include "gestion_projet/parts/modalFinancement.html" %}
+ {% include "gestion_projet/parts/creation_bailleur.html" %}
+ {% include "gestion_projet/parts/modalDetailProjet.html" %}
+{% endblock %}
+{% block 'js' %}
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/parts/creation_bailleur.html b/gestion_projet/templates/gestion_projet/parts/creation_bailleur.html
new file mode 100644
index 0000000..6fe4194
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/creation_bailleur.html
@@ -0,0 +1,20 @@
+
+
+
+
+
Ajouter un Bailleur
+
+
+
+
+
+
+
+
+
diff --git a/gestion_projet/templates/gestion_projet/parts/liste_document_projet.html b/gestion_projet/templates/gestion_projet/parts/liste_document_projet.html
new file mode 100644
index 0000000..eb63ad2
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/liste_document_projet.html
@@ -0,0 +1,16 @@
+
+
+
+
+
Liste des Documents
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/parts/modalAjoutActivite.html b/gestion_projet/templates/gestion_projet/parts/modalAjoutActivite.html
new file mode 100644
index 0000000..d253eb3
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/modalAjoutActivite.html
@@ -0,0 +1,20 @@
+
+
+
+
+
Ajouter une Activité
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/parts/modalAjoutDocument.html b/gestion_projet/templates/gestion_projet/parts/modalAjoutDocument.html
new file mode 100644
index 0000000..6787fad
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/modalAjoutDocument.html
@@ -0,0 +1,20 @@
+
+
+
+
+
Ajouter un Document
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/parts/modalAjoutLivrable.html b/gestion_projet/templates/gestion_projet/parts/modalAjoutLivrable.html
new file mode 100644
index 0000000..6b2bb68
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/modalAjoutLivrable.html
@@ -0,0 +1,20 @@
+
+
+
+
+
Ajouter un livrable - (Nom du livrable)
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/parts/modalAjoutProjet.html b/gestion_projet/templates/gestion_projet/parts/modalAjoutProjet.html
new file mode 100644
index 0000000..1be46ce
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/modalAjoutProjet.html
@@ -0,0 +1,21 @@
+
+
+
+
+
+
Ajouter un projet
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/parts/modalAnnulerActivite.html b/gestion_projet/templates/gestion_projet/parts/modalAnnulerActivite.html
new file mode 100644
index 0000000..7793aaf
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/modalAnnulerActivite.html
@@ -0,0 +1,26 @@
+
+
+
+
+
Annuler l'activité
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/parts/modalDetailActivite.html b/gestion_projet/templates/gestion_projet/parts/modalDetailActivite.html
new file mode 100644
index 0000000..fba8cd9
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/modalDetailActivite.html
@@ -0,0 +1,58 @@
+
+
+
+
+
Détails de l'activité
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Liste des livrables :
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/parts/modalDetailProjet.html b/gestion_projet/templates/gestion_projet/parts/modalDetailProjet.html
new file mode 100644
index 0000000..a7f6087
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/modalDetailProjet.html
@@ -0,0 +1,115 @@
+{% load tags_personnaliser %}
+
+
+
+
+
Détails du projet
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/parts/modalFinancement.html b/gestion_projet/templates/gestion_projet/parts/modalFinancement.html
new file mode 100644
index 0000000..a075b41
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/modalFinancement.html
@@ -0,0 +1,20 @@
+
+
+
+
+
Ajout de financement
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/parts/modalMiseAJourDepense.html b/gestion_projet/templates/gestion_projet/parts/modalMiseAJourDepense.html
new file mode 100644
index 0000000..d796bb4
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/parts/modalMiseAJourDepense.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+ Mise à jour des dépenses
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/suivi_activite.html b/gestion_projet/templates/gestion_projet/suivi_activite.html
new file mode 100644
index 0000000..24d44a6
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/suivi_activite.html
@@ -0,0 +1,64 @@
+{% extends "BASE.html" %}
+{% load static %}
+{% block 'titre_page' %} Gestion des projets {% endblock %}
+{% block 'contenu' %}
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+ {% endif %}
+
+ Suivi des Activités ({{ nom_projet }} )
+
+
+
+ Budget Total (GNF)
+
{{ budget_total }}
+
+
+ Budget RH (GNF)
+
{{ budget_RH }}
+
+
+ Budget Dépensé (GNF)
+
{{budget_depense}}
+
+
+
+
+
La liste des Activités
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+{% block 'modal' %}
+ {% include "gestion_projet/parts/modalAjoutActivite.html" %}
+ {% include "gestion_projet/parts/modalDetailActivite.html" %}
+ {% include "gestion_projet/parts/modalAjoutDocument.html" %}
+ {% include "gestion_projet/parts/modalMiseAJourDepense.html" %}
+ {% include "gestion_projet/parts/modalAjoutLivrable.html" %}
+ {% include "gestion_projet/parts/liste_document_projet.html" %}
+ {% include "gestion_projet/parts/modalAnnulerActivite.html" %}
+{% endblock %}
+{% block 'js' %}
+
+
+{% endblock %}
+
\ No newline at end of file
diff --git a/gestion_projet/tests.py b/gestion_projet/tests.py
new file mode 100644
index 0000000..de8bdc0
--- /dev/null
+++ b/gestion_projet/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/gestion_projet/urls.py b/gestion_projet/urls.py
new file mode 100644
index 0000000..6e3f087
--- /dev/null
+++ b/gestion_projet/urls.py
@@ -0,0 +1,122 @@
+from django.urls import path
+from . import views
+
+app_name = 'gestion_projet'
+
+urlpatterns = [
+ path(
+ '',
+ views.index,
+ name='index'
+ ),
+ path(
+ 'modifier-financement//',
+ views.modifier_financement_projet,
+ name='modifier-financement'
+ ),
+ path(
+ 'projet/creation/',
+ views.creation_projet,
+ name='creation-projet'
+ ),
+ path(
+ 'projet/modifier//',
+ views.modification_projet,
+ name='modifier-projet'
+ ),
+ path(
+ 'liste-projet/',
+ views.liste_projet,
+ name='liste-projet'
+ ),
+ path(
+ 'projet/suppression//',
+ views.suppression_projet,
+ name='projet-suppression'
+ ),
+ path(
+ 'projet/ajouter-financement',
+ views.ajouter_financement_projet,
+ name='ajouter_financement'
+ ),
+ path(
+ 'creation-bailleur',
+ views.creation_bailleur,
+ name='creation-bailleur'
+ ),
+ path(
+ 'projet/affectation/',
+ views.affecter_employe_projet,
+ name='affecter-employe-projet'
+ ),
+ path(
+ 'projet/ajout-de-document/',
+ views.ajouter_document_projet,
+ name='ajouter-document'
+ ),
+ path(
+ 'projet/bailleurs//',
+ views.liste_bailleurs,
+ name='liste-bailleurs'
+ ),
+ path(
+ 'activite/',
+ views.activites_projet,
+ name='activites-projet'
+ ),
+ path(
+ 'activite/ajouter/',
+ views.ajouter_activite_projet,
+ name='ajouter-activite'
+ ),
+ path(
+ 'activite/modifier//',
+ views.modifier_activite_projet,
+ name='modifier-activite'
+ ),
+ path(
+ 'activite/annuler/',
+ views.annuler_activite_projet,
+ name='annuler-activite'
+ ),
+ path(
+ 'activite/liste/',
+ 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,
+ name='liste-documents-projet'
+ ),
+ path(
+ 'activite/liste-des-livrables//',
+ views.liste_livrables_activite,
+ name='liste-livrables-activite'
+ ),
+ path(
+ 'projet/ajout-de-livrable/',
+ views.ajouter_livrables_projet,
+ name='ajouter-livrable'
+ ),
+ path(
+ 'activite/mise-a-jour-depense/',
+ views.mises_a_jour_depense_activite,
+ name='mettre-a-jour-depense'
+ ),
+ path(
+ 'projet/liste-employes-par-projet/',
+ views.liste_employes_affectes,
+ name='liste-employes-affectes'
+ ),
+ path(
+ 'projet/mises-a-jour-projet',
+ views.mises_a_jour_projet,
+ name='mises-a-jour-projet'
+ )
+]
\ No newline at end of file
diff --git a/gestion_projet/views.py b/gestion_projet/views.py
new file mode 100644
index 0000000..69bd619
--- /dev/null
+++ b/gestion_projet/views.py
@@ -0,0 +1,485 @@
+from datetime import date
+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 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,
+ DocumentProjetForm,
+ FinancementProjetFrom,
+ BailleurForm,
+ LivrablesLivresForm
+)
+
+def liste_projet(request):
+ """ Vue pour retourner la liste de tous les projet """
+ projets = Projet.objects.all().order_by('-created_at')
+
+ data = []
+ for p in projets:
+ financement = FinancementProjet.objects.filter(projet=p).select_related('bailleur')
+ data.append({
+ "id_projet": p.id_projet,
+ "nom_projet": p.nom_projet,
+ "date_debut": p.date_debut,
+ "date_fin": p.date_fin,
+ "numero_convention": p.numero_convention,
+ "description": p.description,
+ "type_projet": p.type_projet,
+ "budget": p.budget,
+ "budget_RH": p.budget_RH,
+ "created_at": p.created_at,
+ "statut": p.statut,
+ "avancement": p.avancement,
+ "domaine_recherche": [d.nom for d in p.domaine_recherche.all()],
+ "source_financement": [f.bailleur.nom_organisme for f in financement],
+ })
+
+ return JsonResponse(data, safe=False)
+
+def liste_employes_affectes(request, projet_id):
+ """ Vue pour retourner la liste des employés affectés à un projet spécifique """
+ employes = Employe.objects.filter(affectation__projet_id=projet_id).distinct()
+ data = []
+ for employe in employes:
+ data.append({
+ "employe": f"{employe.user.first_name} {employe.user.last_name}",
+ "pourcentage_affectation": Affectation.objects.get(employe=employe, projet__id_projet=projet_id).pourcentage_temps_affectation
+ })
+ return JsonResponse(data, safe=False)
+
+def liste_bailleurs(request, projet_id):
+ """ Vue pour retourner la liste des bailleurs associés à un projet spécifique """
+ bailleurs = FinancementProjet.objects.filter(projet_id=projet_id).select_related('bailleur')
+ data = []
+ for b in bailleurs:
+ data.append({
+ "bailleur": b.bailleur.nom_organisme,
+ "pourcentage_financement": b.pourcentage
+ })
+
+ return JsonResponse(data, safe=False)
+
+@login_required
+def index(request):
+ projets = Projet.objects.all().order_by('-created_at')
+ nombre_personnel = Affectation.objects.values('employe_id').distinct().count()
+ budget_total = sum([projet.budget for projet in projets if projet.budget or 0])
+ context = {
+ 'form': AffectationForm(),
+ 'form_ajout_financement': FinancementProjetFrom(),
+ 'form_ajout_bailleur': BailleurForm(),
+ 'bailleurs': Bailleur.objects.all(),
+ 'nombre_personnel': nombre_personnel,
+ 'budget_total': budget_total,
+ 'formulaire_creation_projet': ProjetForm(),
+ 'projet_en_cours': Projet.objects.filter(date_fin__gte=date.today()).count(),
+ }
+
+ return render(request, 'gestion_projet/index.html', context)
+
+@login_required
+def creation_projet(request):
+ """Vue pour créer un nouveau projet via un formulaire"""
+ formulaire_creation_projet = ProjetForm()
+ if request.method == "POST":
+ form = ProjetForm(request.POST)
+
+ if form.is_valid():
+ form.save()
+ messages.success(request, "Projet créé avec succès.")
+ else:
+ messages.error(request, "Le formulaire transmis est invalide.")
+ else:
+ form = ProjetForm()
+ return render(
+ request,
+ "gestion_projet/creation_projet.html",
+ {
+ "formulaire_creation_projet": formulaire_creation_projet
+ }
+ )
+
+@login_required
+def mises_a_jour_projet(request):
+ """ Vue de mises à jour des informations du projet """
+ if request.method == "POST":
+ try:
+ projet = Projet.objects.get(id_projet = request.POST["id_projet"])
+ except Projet.DoesNotExist:
+ messages.error(request, "Ce projet n'existe pas.")
+ else:
+ projet_form = ProjetForm(request.POST, instance=projet)
+ if projet_form.is_valid():
+ projet_form.save()
+ messages.success(request, f"Le projet d'identifiant {request.POST['id_projet']} a été mis à jour avec succès.")
+ else:
+ messages.error(request, f"Les informations de modification transmises pour la modification du projet {request.POST['id_projet']} ne sont pas valides.")
+ else:
+ messages.error(request, "La méthode de transmission des données n'est pas valide.")
+ return redirect('gestion_projet:index')
+
+@login_required
+def creation_bailleur(request):
+ form = BailleurForm(request.POST)
+ if request.method == 'POST':
+ if form.is_valid():
+ form.save()
+ return JsonResponse({'success': True})
+ return JsonResponse({'success': False})
+
+@login_required
+def ajouter_financement_projet(request):
+ """Ajoute un financement à un projet en vérifiant que le total ne dépasse pas 100%"""
+
+ if request.method == 'POST':
+ pourcentage_recuperer = request.POST.get('pourcentage')
+ bailleur_id = request.POST.get('bailleur')
+ projet_id = request.POST.get('projet')
+ try:
+ projet = Projet.objects.get(id_projet=projet_id)
+ except Projet.DoesNotExist:
+ return JsonResponse({'success': False, 'message': "Le projet spécifié n’existe pas."})
+
+ try:
+ pourcentage_nouveau = Decimal(pourcentage_recuperer) if pourcentage_recuperer else Decimal(0)
+ except (InvalidOperation, TypeError):
+ return JsonResponse({'success': False, 'message': "Le pourcentage saisi n’est pas valide."})
+
+ financement_total_actuel = sum(financement.pourcentage for financement in FinancementProjet.objects.filter(projet=projet))
+ if financement_total_actuel + pourcentage_nouveau > 100:
+ return JsonResponse({'success': False, 'message': "Le total des financements dépasse 100%."})
+
+ if bailleur_id:
+ FinancementProjet.objects.create(
+ projet=projet,
+ bailleur_id=bailleur_id,
+ pourcentage=pourcentage_nouveau
+ )
+ return JsonResponse({'success': True, 'message': "Financement ajouté avec succès."})
+ else:
+ return JsonResponse({'success': False, 'message': "Aucun bailleur sélectionné."})
+ return JsonResponse({'success': False, 'message': "Requête invalide."})
+
+@login_required
+def modification_projet(request, projet_id):
+ """Vue pour éditer un projet existant via un formulaire pré-rempli"""
+ try:
+ projet = Projet.objects.get(id=projet_id)
+ except Projet.DoesNotExist:
+ messages.error(request, "Le projet spécifié n’existe pas.")
+ return redirect('projet-index')
+
+ if request.method == "POST":
+ form = ProjetForm(request.POST)
+ if form.is_valid():
+ form.save()
+ messages.success(request, "Le projet a été modifié avec succès.")
+ return redirect('projet-index')
+ messages.error(request, "Erreur lors de la modification du projet.")
+ form = ProjetForm(instance=projet)
+ return render(request, 'gestion_projet/projet-edit.html', {'form': form, 'projets': projet})
+
+@login_required
+def suppression_projet(request, id):
+ """Vue pour supprimer un projet spécifique après confirmation de l'utilisateur"""
+ try:
+ projet = Projet.objects.get(id=id)
+ except Projet.DoesNotExist:
+ messages.error(request, "Le projet spécifié n’existe pas.")
+ return redirect('projet-index')
+
+ if request.method == "POST":
+ projet.delete()
+ messages.success(request, "Le projet a été supprimé avec succès.")
+ return redirect('projet-index')
+
+@login_required
+def affecter_employe_projet(request, projet_id):
+ """Vue pour affecter un employé à un projet avec vérification des contraintes d'affectation"""
+ try:
+ projet = Projet.objects.get(id=projet_id)
+ except Projet.DoesNotExist:
+ messages.error(request, "Le projet spécifié n’existe pas.")
+ return redirect('projet-index')
+
+ if request.method == 'POST':
+ form = AffectationForm(request.POST)
+ if form.is_valid():
+ employe = Employe.objects.get(id=form.cleaned_data['employe'].id)
+ date_fin_affectation = form.cleaned_data['date_fin_daffectation']
+ temps_nouveau = form.cleaned_data['temps_affectation']
+ date_affectation = form.cleaned_data['date_affectation']
+
+ if (date_fin_affectation and date_affectation):
+ total_affectation = (
+ Affectation.objects.filter(employe=employe)
+ .aggregate(total_pourcentage_affectation=Sum('temps_affectation'))
+ ['total_pourcentage_affectation'] or 0
+ )
+ if (date_fin_affectation < date_affectation):
+ messages.warning(request, "La date de fin d'affectation ne peut pas être antérieure à la date de début.")
+ return redirect('projet-index')
+ elif date_fin_affectation > projet.date_fin:
+ messages.warning(request, f"La date de fin de l'affectation ({date_fin_affectation}) ne peut pas dépasser la date de fin du projet ({projet.date_fin}).")
+ return redirect('projet-index')
+ 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}%)."
+ )
+ return redirect('projet-index')
+
+ Affectation.objects.update_or_create(
+ projet=projet,
+ employe=employe,
+ defaults={
+ 'date_affectation': form.cleaned_data['date_affectation'],
+ 'date_fin_daffectation': date_fin_affectation,
+ 'role': form.cleaned_data['role'],
+ 'temps_affectation': temps_nouveau
+ }
+ )
+ form = AffectationForm(initial={'projet': projet})
+ messages.error(request, "Erreur : Formulaire non valide.")
+ return redirect('projet-index')
+
+def modifier_financement_projet(request, financement_id):
+ try:
+ financement = FinancementProjet.objects.get(id=financement_id)
+ except FinancementProjet.DoesNotExist:
+ messages.error(request, "Le financement spécifié n’existe pas.")
+ return redirect('projet-index')
+ projet = financement.projet
+ if request.method == 'POST':
+ try:
+ nouveau_pourcentage = Decimal(request.POST.get('pourcentage', '0'))
+ except InvalidOperation:
+ messages.error(request, "Le pourcentage saisi est invalide.")
+ return redirect('projet-index')
+ pourcentage_total_financement = (
+ FinancementProjet.objects.filter(projet=projet)
+ .exclude(id=financement.id)
+ .aggregate(total_financement=Sum('pourcentage'))['pourcentage_total_financement'] or 0
+ )
+ if pourcentage_total_financement + nouveau_pourcentage > 100:
+ messages.error(request, f"Le total des financements dépasse 100% ({pourcentage_total_financement + nouveau_pourcentage}%).")
+ return redirect('projet-index')
+ financement.pourcentage = nouveau_pourcentage
+ financement.save()
+ messages.success(request, "Financement modifié avec succès.")
+ return redirect('projet-index')
+
+@login_required
+def activites_projet(request):
+ try:
+ employe = Employe.objects.get(user=request.user)
+ except Employe.DoesNotExist:
+ messages.error(request, "Impossible d'accéder au menu 'Suivi des activités' car votre profil Utilisateur n'est lié à aucun profil Employe. Veuillez contacter l'administrateur.")
+ return redirect("gestion_conges:conge")
+
+ try:
+ Affectation.objects.get(employe=employe, date_fin_daffectation__gte = timezone.now().date(), role='chef_projet')
+ except Affectation.DoesNotExist :
+ messages.error(request, "Seuls les chefs de projet ont accès à l'onglet 'Suivi des Activités'")
+ return redirect("gestion_conges:conge")
+
+ projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
+ if projet :
+ context = {
+ **model_to_dict(projet),
+ "nom_projet": projet.projet.nom_projet,
+ "budget_total": projet.projet.budget,
+ "budget_RH": projet.projet.budget_RH,
+ "form_ajout_activite": ActiviteProjetForm(),
+ "form_ajout_document": DocumentProjetForm(),
+ "form_ajout_livrable": LivrablesLivresForm(),
+ }
+ else :
+ context = {
+ "form_ajout_activite": ActiviteProjetForm(),
+ "form_ajout_document": DocumentProjetForm(),
+ "form_ajout_livrable": LivrablesLivresForm(),
+ }
+ return render(request, 'gestion_projet/suivi_activite.html', context)
+
+@login_required
+def ajouter_activite_projet(request):
+ """Vue pour ajouter une activité à un projet spécifique via un formulaire"""
+ employe = Employe.objects.get(user=request.user)
+ projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
+
+ if request.method == "POST":
+ form = ActiviteProjetForm(request.POST)
+ if form.is_valid():
+ activite = form.save(commit=False)
+ activite.projet = projet.projet
+ activite.budget_depense = request.POST["budget_prevu"]
+ activite.save()
+ messages.success(request, "Activité ajoutée avec succès !")
+ else:
+ messages.error(request, "Erreur : vérifiez les informations saisies.")
+ return redirect('gestion_projet:activites-projet')
+
+@login_required
+def liste_activites_projet(request):
+ """Vue pour retourner la liste des activités d'un projet spécifique"""
+ employe = Employe.objects.get(user=request.user)
+ projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
+ if projet:
+ activites = ActiviteProjet.objects.filter(projet_id=projet.projet.id_projet).order_by('-date_debut')
+ else:
+ activites = []
+ data = []
+ for a in activites:
+ data.append({
+ "id": a.id,
+ "titre": a.titre,
+ "date_debut": a.date_debut,
+ "date_fin": a.date_fin,
+ "statut": a.statut,
+ "budget_prevu": a.budget_prevu,
+ "budget_depense": a.budget_depense,
+ "motif_changement_budget": a.motif_changement_budget,
+ "besoin_ressource_materielle": a.besoin_ressource_materielle,
+ "description": a.description,
+ })
+ return JsonResponse(data, safe=False)
+
+@login_required
+def liste_livrables_activite(request, activite_id):
+ """Vue pour retourner la liste des livrables attendus d'une activité spécifique"""
+ livrables = LivrablesLivres.objects.filter(activite__id=activite_id)
+ data = []
+ for livrable in livrables:
+ print(livrable.fichier.url)
+ data.append({
+ "titre": livrable.nom,
+ "lien": livrable.fichier.url if livrable.fichier else "",
+ })
+ return JsonResponse(data, safe=False)
+
+@login_required
+def mises_a_jour_depense_activite(request):
+ """Vue pour retourner la liste des activités d'un projet spécifique avec leurs dépenses mises à jour"""
+ if request.method == "POST":
+ activite_id = request.POST.get("id_activite")
+ budget_depense = request.POST.get("budget_depense")
+ motif = request.POST.get("motif", "").strip()
+ try:
+ activite = ActiviteProjet.objects.get(id=activite_id)
+ activite.budget_depense = Decimal(budget_depense)
+ if Decimal(budget_depense) != activite.budget_prevu:
+ activite.motif_changement_budget = motif
+ else:
+ activite.motif_changement_budget = ""
+ activite.save()
+ messages.success(request, f"Dépenses mises à jour pour l’activité '{activite.titre}'.")
+ except (ActiviteProjet.DoesNotExist, InvalidOperation):
+ messages.error(request, "Erreur lors de la mise à jour des dépenses.")
+ return redirect("gestion_projet:activites-projet")
+
+@login_required
+def ajouter_livrables_projet(request):
+ """Vue pour ajouter un livrable à une activité de projet spécifique via un formulaire"""
+
+ if request.method == "POST":
+ form = LivrablesLivresForm(request.POST, request.FILES)
+ if form.is_valid():
+ form.save()
+ messages.success(request, "Livrable ajouté avec succès !")
+ else:
+ messages.error(request, "Erreur : vérifiez les informations saisies.")
+ return redirect('gestion_projet:activites-projet')
+
+@login_required
+def ajouter_document_projet(request):
+ """Ajoute un document à un projet"""
+ employe = Employe.objects.get(user=request.user)
+ projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
+ if request.method == "POST":
+ form = DocumentProjetForm(request.POST, request.FILES)
+ if form.is_valid():
+ document = form.save(commit=False)
+ document.projet = projet.projet
+ document.save()
+ messages.success(request, "Document ajouté avec succès !")
+ else:
+ messages.error(request, "Erreur : le document n’a pas pu être enregistré.")
+ return redirect('gestion_projet:activites-projet')
+
+def liste_documents_projet(request):
+ employe = Employe.objects.get(user=request.user)
+ projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
+ if projet:
+ documents = DocumentProjet.objects.filter(projet__id_projet=projet.projet.id_projet)
+ else:
+ documents = []
+ data = []
+ for d in documents:
+ data.append({
+ "nom_document": d.nom_document,
+ "numero": d.numero,
+ "date_validite": d.date_validite,
+ "lien_document": d.fichier.url if d.fichier else "",
+ })
+ return JsonResponse(data, safe=False)
+
+def modifier_activite_projet(request, id):
+ """Vue pour modifier une activité de projet spécifique via un formulaire pré-rempli"""
+ try:
+ activite = ActiviteProjet.objects.get(id=id)
+ except ActiviteProjet.DoesNotExist:
+ messages.error(request, "L'activité spécifiée n’existe pas.")
+ return redirect('activites-projet')
+ if request.method == 'POST':
+ form = ActiviteProjetForm(request.POST, instance=activite)
+ if form.is_valid():
+ activite.besoin_ressource_materielle = bool(request.POST.get("besoin_ressource_materielle"))
+ form.save()
+ messages.success(request, f"Activité « {activite.titre} » modifiée avec succès.")
+ else:
+ messages.error(request, "Erreur lors de la modification de l'activité.")
+ return redirect('activites-projet')
+
+ form = ActiviteProjetForm(instance=activite)
+ return render(
+ request,
+ 'gestion_projet/activite.html', {
+ 'form': form,
+ 'activite': activite,
+ }
+ )
+
+def annuler_activite_projet(request):
+ """Vue pour annuler une activité de projet spécifique après confirmation de l'utilisateur"""
+ print(request.POST)
+ if request.method != "POST":
+ messages.error(request, "Requête invalide.")
+ return redirect('gestion_projet:activites-projet')
+ try:
+ activite = ActiviteProjet.objects.get(id=request.POST.get('id_activite'))
+ except ActiviteProjet.DoesNotExist:
+ messages.error(request, "L'activité spécifiée n’existe pas.")
+ return redirect('gestion_projet:activites-projet')
+ if request.method == "POST":
+ activite.annuler = True
+ activite.motif_annulation = request.POST.get("motif_annulation", "").strip()
+ activite.save()
+ messages.success(request, f"L'activité '{activite.titre}' a été annulée avec succès.")
+ return redirect('gestion_projet:activites-projet')
\ No newline at end of file
diff --git a/gestion_salle/__init__.py b/gestion_salle/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gestion_salle/admin.py b/gestion_salle/admin.py
new file mode 100644
index 0000000..ea5d68b
--- /dev/null
+++ b/gestion_salle/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/gestion_salle/apps.py b/gestion_salle/apps.py
new file mode 100644
index 0000000..569d365
--- /dev/null
+++ b/gestion_salle/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class GestionSalleConfig(AppConfig):
+ name = 'gestion_salle'
diff --git a/gestion_salle/forms.py b/gestion_salle/forms.py
new file mode 100644
index 0000000..e40caf0
--- /dev/null
+++ b/gestion_salle/forms.py
@@ -0,0 +1,32 @@
+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']
+ widgets = {
+ 'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
+ 'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
+ 'heure_debut': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
+ 'heure_fin': forms.TimeInput(attrs={'type': 'time', 'class': 'form-control'}),
+ 'motif_reservation': forms.Textarea(attrs={'rows': 3, 'cols': 40, 'style':'resize:none;', 'class': 'form-control'}),
+ 'salle': forms.Select(attrs={'class': 'form-select'}),
+ }
+ besoin_zoom = forms.BooleanField(
+ required=False,
+ label="Besoin d'un lien Zoom ?",
+ widget=forms.CheckboxInput(attrs={'class': 'form-check-input', 'id': 'id_besoin_zoom'})
+ )
+ besoin_ordi = forms.BooleanField(
+ required=False,
+ label="Besoin d'ordinateur ?",
+ widget=forms.CheckboxInput(attrs={'class': 'form-check-input', 'id': 'id_besoin_ordi'})
+ )
+
+class RefusReservationForm(forms.Form):
+ motif_refus = forms.CharField(
+ label= "Motif du refus",
+ widget=forms.Textarea(attrs={'rows': 3, 'cols': 40, 'style': 'resize:none;'}),
+ required=True
+ )
diff --git a/gestion_salle/migrations/0001_initial.py b/gestion_salle/migrations/0001_initial.py
new file mode 100644
index 0000000..f477d96
--- /dev/null
+++ b/gestion_salle/migrations/0001_initial.py
@@ -0,0 +1,34 @@
+# Generated by Django 5.2.13 on 2026-04-17 12:03
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('gestion_employe', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Reservation',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('salle', models.CharField(choices=[('formation', 'Salle de formation'), ('reunion', 'Salle de réunion'), ('lien_zoom', 'Lien Zoom')], max_length=100)),
+ ('date_demande', models.DateTimeField(auto_now_add=True)),
+ ('date_debut', models.DateField()),
+ ('date_fin', models.DateField(blank=True, null=True)),
+ ('heure_debut', models.TimeField()),
+ ('heure_fin', models.TimeField()),
+ ('besoin_zoom', models.BooleanField(default=False, verbose_name="Besoin d'un lien Zoom ?")),
+ ('besoin_ordi', models.BooleanField(default=False, verbose_name="Besoin d'un ordinateur ?")),
+ ('lien_zoom', models.URLField(blank=True, null=True, verbose_name='Lien Zoom')),
+ ('motif_reservation', models.TextField()),
+ ('statut', models.CharField(choices=[('en_attente', 'En attente'), ('validee', 'Validée'), ('refusee', 'Refusée'), ('annulee', 'Annulée')], default='en_attente', max_length=25)),
+ ('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_employe.employe')),
+ ],
+ ),
+ ]
diff --git a/gestion_salle/migrations/__init__.py b/gestion_salle/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gestion_salle/models.py b/gestion_salle/models.py
new file mode 100644
index 0000000..5d1d4e2
--- /dev/null
+++ b/gestion_salle/models.py
@@ -0,0 +1,33 @@
+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 = [
+ ('formation', 'Salle de formation'),
+ ('reunion', 'Salle de réunion'),
+ ('lien_zoom', 'Lien Zoom'),
+ ]
+ 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)
+ date_demande = models.DateTimeField(auto_now_add=True)
+ date_debut = models.DateField()
+ date_fin = models.DateField(blank=True,null=True)
+ heure_debut = models.TimeField()
+ heure_fin = models.TimeField()
+ besoin_zoom = models.BooleanField(default=False, verbose_name="Besoin d'un lien Zoom ?")
+ besoin_ordi = models.BooleanField(default=False, verbose_name="Besoin d'un ordinateur ?")
+ lien_zoom = models.URLField(blank=True, null=True, verbose_name="Lien Zoom")
+ motif_reservation = models.TextField()
+ statut = models.CharField(choices=STATUT, default='en_attente', max_length=25)
+
+ def __str__(self):
+ return f"{self.salle} - {self.employe.user.first_name} {self.employe.user.last_name} le {self.date_reservation}"
\ No newline at end of file
diff --git a/gestion_salle/static/gestion_salle/js/index.js b/gestion_salle/static/gestion_salle/js/index.js
new file mode 100644
index 0000000..8b5bff8
--- /dev/null
+++ b/gestion_salle/static/gestion_salle/js/index.js
@@ -0,0 +1,193 @@
+const $ = (element) => document.getElementById(element);
+const { Schedule } = calendarjs;
+
+let dateAUtiliser = new Date().toISOString().split('T')[0];
+let currentReservationId = null;
+
+const calendrier = Schedule(document.getElementById('planning-reservation'), {
+ type: 'weekdays',
+ value: dateAUtiliser,
+ validRange: ['08:00', '18:00'],
+ ondblclick: function(self, event) {
+ const modal = new bootstrap.Modal($("modalDetailReservation"));
+ modal.show();
+ fetch (`/gestion-salle/revervation/details/${event.guid}`)
+ .then(response => response.json())
+ .then(data => {
+ currentReservationId = data.id_reservation;
+ console.log(data);
+ $("id_reservation_detail").value = data.id_reservation;
+ $("id_reservation_refus").value = data.id_reservation;
+ $("id_reservation_zoom").value = data.id_reservation;
+ $("employe").value=data.employe;
+ $("salle").value=data.salle;
+ $("statut-reservation").innerHTML=data.statut;
+ $("date_evenement").value=data.date_evenement;
+ $("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_ordinateur;
+ $("lien_zoom").value=data.lien_zoom;
+
+ if(data.statut !== "annulee"){
+ $("motif_refus_container").className = "d-none";
+ }else{
+ $("motif_refus").value=data.motif_refus;
+ }
+ })
+ }
+});
+
+$("modalReservation").addEventListener('shown.bs.modal', (e) => {
+ $("id_salle").value = $("liste-salle").value;
+})
+
+$('semaineDate').addEventListener('change', () => {
+ calendrier.value = $('semaineDate').value;
+ calendrier.render();
+})
+
+evenement_defini = null
+
+$("liste-salle").addEventListener("change", (e) => {
+ if(evenement_defini === null){
+ evenement_defini = calendrier.getData();
+ }
+ evenements = evenement_defini;
+ evenement_filtrer = evenements.filter((evenement) => {
+ if(evenement.title == $("liste-salle").value){
+ return evenement
+ }
+ })
+ calendrier.setData(evenement_filtrer);
+})
+
+function chargement_evenement(){
+ const url = $("planning-reservation").dataset.url;
+ fetch (url)
+ .then(response => response.json())
+ .then(data => {
+ calendrier.setData(data);
+ })
+}
+
+document.addEventListener("DOMContentLoaded", function () {
+ chargement_evenement()
+})
+
+$("bouton-annuler").addEventListener("click", (e) => {
+ const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
+ const url_annuler = $("formulaire-details").dataset.urlannuler;
+
+ fetch(
+ url_annuler,
+ {
+ method: "POST",
+ headers: {
+ "X-Requested-With": "XMTHttpRequest",
+ "X-CSRFToken": csrf
+ },
+ body: new FormData($("formulaire-details"))
+ }
+ )
+ .then(response => response.json())
+ .then(data => console.log(data))
+})
+
+if($("bouton-valider")){
+ $("bouton-valider").addEventListener("click", (e) => {
+ const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
+ const urlvalider = $("formulaire-details").dataset.urlvalider;
+
+ fetch(
+ urlvalider,
+ {
+ method: "POST",
+ headers: {
+ "X-Requested-With": "XMTHttpRequest",
+ "X-CSRFToken": csrf
+ },
+ body: new FormData($("formulaire-details"))
+ }
+ )
+ .then(response => response.json())
+ .then(data => console.log(data))
+ })
+}
+
+if($("ajoutZoom")){
+ $("ajoutZoom").addEventListener("click", (e) => {
+ e.preventDefault();
+ bootstrap.Modal.getOrCreateInstance($("modalDetailReservation")).hide();
+ new bootstrap.Modal($("modalZoom")).show();
+ })
+}
+
+if($("refuserReservation")){
+ $("refuserReservation").addEventListener("click", (e) => {
+ const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
+ const url = e.currentTarget.dataset.lienrefus;
+ const idRes = $("id_reservation_detail").value;
+
+ fetch(url, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "X-Requested-With": "XMLHttpRequest",
+ "X-CSRFToken": csrf
+ },
+ body: JSON.stringify({ "id_reservation": idRes })
+ })
+ .then(response => response.json())
+ .then(data => alert(data.message))
+ .catch(error => console.error("Erreur:", error));
+ });
+}
+
+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: "Heure de début", field: "heure_debut"},
+ {title: "Heure de fin", field: "heure_fin"},
+ {title: "Motif de reservation", field: "motif_reservation"},
+ ],
+ placeholder: "Aucune reservation en attente.",
+ ajaxURL : $("tableau-reservation-attente").dataset.reservationattentes
+})
+
+tableau_reservation_attente.on("rowClick", (row, rowData) => {
+ const data = rowData.getData();
+
+ console.log(data);
+
+ if(data.besoin_zoom === false){
+ $("lien_zoom_container").className = 'd-none';
+ }
+
+ if(data.statut !== "refusee"){
+ $("motif_refus_container").className = 'd-none';
+ }
+
+ $("id_reservation_detail").value = data.id;
+ $("id_reservation_refus").value = data.id;
+ $("id_reservation_zoom").value = data.id;
+
+ $("employe").value=data.employe;
+ $("salle").value=data.salle;
+ $("statut-reservation").innerHTML=data.statut;
+ $("date_evenement").value=data.date_debut;
+ $("heure_debut").value=data.heure_debut;
+ $("heure_fin").value=data.heure_fin;
+ $("motif_reservation").value=data.motif_reservation;
+ $("besoin_zoom").checked=data.besoin_zoom;
+ $("besoin_ordinateur").checked=data.besoin_ordi;
+ $("lien_zoom").value=data.lien_zoom;
+ $("motif_refus").value=data.motif_refus;
+
+ const modal = new bootstrap.Modal($("modalDetailReservation"));
+ bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
+ modal.show();
+})
\ No newline at end of file
diff --git a/gestion_salle/templates/gestion_salle/index.html b/gestion_salle/templates/gestion_salle/index.html
new file mode 100644
index 0000000..b0fa88e
--- /dev/null
+++ b/gestion_salle/templates/gestion_salle/index.html
@@ -0,0 +1,59 @@
+{% extends "BASE.html" %}
+{% load static %}
+{% block 'titre_page' %} Gestion des projets {% endblock %}
+{% block 'css' %}
+
+
+{% endblock %}
+{% block 'contenu' %}
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+ {% endif %}
+
+
+
Reservation de salle
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
+{% block 'modal' %}
+ {% include 'gestion_salle/parts/modalCreationReservation.html' %}
+ {% include 'gestion_salle/parts/ModaleAjoutLienZoom.html' %}
+ {% include 'gestion_salle/parts/ModalRefusReservation.html' %}
+ {% include 'gestion_salle/parts/modalDetailResevation.html' %}
+ {% include 'gestion_salle/parts/modalListeValidation.html' %}
+{% endblock %}
+{% block 'js' %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/gestion_salle/templates/gestion_salle/parts/ModalRefusReservation.html b/gestion_salle/templates/gestion_salle/parts/ModalRefusReservation.html
new file mode 100644
index 0000000..96d358d
--- /dev/null
+++ b/gestion_salle/templates/gestion_salle/parts/ModalRefusReservation.html
@@ -0,0 +1,22 @@
+
+
+
+
+
Motif du refus
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_salle/templates/gestion_salle/parts/ModaleAjoutLienZoom.html b/gestion_salle/templates/gestion_salle/parts/ModaleAjoutLienZoom.html
new file mode 100644
index 0000000..0c7c534
--- /dev/null
+++ b/gestion_salle/templates/gestion_salle/parts/ModaleAjoutLienZoom.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+
Ajouter/Modifier le lien Zoom
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_salle/templates/gestion_salle/parts/modalCreationReservation.html b/gestion_salle/templates/gestion_salle/parts/modalCreationReservation.html
new file mode 100644
index 0000000..0db15f8
--- /dev/null
+++ b/gestion_salle/templates/gestion_salle/parts/modalCreationReservation.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ Nouvelle reservation
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_salle/templates/gestion_salle/parts/modalDetailResevation.html b/gestion_salle/templates/gestion_salle/parts/modalDetailResevation.html
new file mode 100644
index 0000000..fb84ee8
--- /dev/null
+++ b/gestion_salle/templates/gestion_salle/parts/modalDetailResevation.html
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+ Détails de la reservation ()
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/gestion_salle/templates/gestion_salle/parts/modalListeValidation.html b/gestion_salle/templates/gestion_salle/parts/modalListeValidation.html
new file mode 100644
index 0000000..0f50e01
--- /dev/null
+++ b/gestion_salle/templates/gestion_salle/parts/modalListeValidation.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Liste des reservation en attente de validation
+
, because it just gets in the way.
+ from_box.parentNode.removeChild(p);
+ } else if (p.classList.contains("help")) {
+ // Move help text up to the top so it isn't below the select
+ // boxes or wrapped off on the side to the right of the add
+ // button:
+ from_box.parentNode.insertBefore(p, from_box.parentNode.firstChild);
+ }
+ }
+
+ //
or
+ const selector_div = quickElement('div', from_box.parentNode);
+ // Make sure the selector div is at the beginning so that the
+ // add link would be displayed to the right of the widget.
+ from_box.parentNode.prepend(selector_div);
+ selector_div.className = is_stacked ? 'selector stacked' : 'selector';
+
+ //
");
+ addButton = $this.filter(":last").next().find("a");
+ }
+ }
+ addButton.on('click', addInlineClickHandler);
+ };
+
+ const addInlineClickHandler = function(e) {
+ e.preventDefault();
+ const template = $("#" + options.prefix + "-empty");
+ const row = template.clone(true);
+ row.removeClass(options.emptyCssClass)
+ .addClass(options.formCssClass)
+ .attr("id", options.prefix + "-" + nextIndex);
+ addInlineDeleteButton(row);
+ row.find("*").each(function() {
+ updateElementIndex(this, options.prefix, totalForms.val());
+ });
+ // Insert the new form when it has been fully edited.
+ row.insertBefore($(template));
+ // Update number of total forms.
+ $(totalForms).val(parseInt(totalForms.val(), 10) + 1);
+ nextIndex += 1;
+ // Hide the add button if there's a limit and it's been reached.
+ if ((maxForms.val() !== '') && (maxForms.val() - totalForms.val()) <= 0) {
+ addButton.parent().hide();
+ }
+ // Show the remove buttons if there are more than min_num.
+ toggleDeleteButtonVisibility(row.closest('.inline-group'));
+
+ // Pass the new form to the post-add callback, if provided.
+ if (options.added) {
+ options.added(row);
+ }
+ row.get(0).dispatchEvent(new CustomEvent("formset:added", {
+ bubbles: true,
+ detail: {
+ formsetName: options.prefix
+ }
+ }));
+ };
+
+ /**
+ * The "X" button that is part of every unsaved inline.
+ * (When saved, it is replaced with a "Delete" checkbox.)
+ */
+ const addInlineDeleteButton = function(row) {
+ if (row.is("tr")) {
+ // If the forms are laid out in table rows, insert
+ // the remove button into the last table cell:
+ row.children(":last").append('