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 %}
+
+ {{ projet.nom_projet }} ({{ projet.pourcentage_temps_affectation }}%) du {{ projet.date_debut|date:"d/m/Y" }} au
+ {{ projet.date_fin|date:"d/m/Y" }} en tant que {{ projet.role }}.
+
\ 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 @@
+
+
+
+
+
Modifier le mot de passe
+
+
+
+
+
+
+
+
+
diff --git a/gestion_employe/templatetags/tags_personnaliser.py b/gestion_employe/templatetags/tags_personnaliser.py
new file mode 100644
index 0000000..c1859f7
--- /dev/null
+++ b/gestion_employe/templatetags/tags_personnaliser.py
@@ -0,0 +1,24 @@
+"""Tags personnalisés pour les templates d'authentification.
+
+Ce fichier contient des tags personnalisés pour les templates du projet lié à
+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()
+
+@register.filter
+def has_group(user, group_name):
+ """Vérifiez si un utilisateur appartient à un groupe spécifique."""
+ return user.groups.filter(name=group_name).exists()
+
+@register.filter
+def is_chef_projet(user):
+ try:
+ affectation = Affectation.objects.get(employe__user__username = user, date_fin_daffectation__gte = timezone.now().date())
+ except Affectation.DoesNotExist:
+ return False
+ return affectation.role == "chef_projet"
\ No newline at end of file
diff --git a/gestion_employe/tests.py b/gestion_employe/tests.py
new file mode 100644
index 0000000..de8bdc0
--- /dev/null
+++ b/gestion_employe/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/gestion_employe/urls.py b/gestion_employe/urls.py
new file mode 100644
index 0000000..992dbd7
--- /dev/null
+++ b/gestion_employe/urls.py
@@ -0,0 +1,102 @@
+from . import views
+from django.urls import path
+
+app_name = 'gestion_employe'
+
+urlpatterns = [
+ path(
+ '',
+ views.index,
+ name='index'
+ ),
+ path(
+ 'liste-employes/',
+ views.liste_employe,
+ name='liste-employes'
+ ),
+ path(
+ 'enregistrer-detail-employe/',
+ views.enregistrer_detail_employe,
+ name='enregistrer-detail-employe'
+ ),
+ path(
+ 'affectation/supprimer/',
+ views.suppression_affectation,
+ name='supprimer-affectation'
+ ),
+ path(
+ 'contrat/creation/',
+ views.creation_contrat,
+ name='creation-contrat'
+ ),
+ path(
+ 'contrat/supprimer/',
+ views.suppression_contrat,
+ name='supprimer-contrat'
+ ),
+ path(
+ 'Affectation/affectation/',
+ views.affecter_employe_projet,
+ name='affecter_employe_projet'
+ ),
+ path(
+ 'mon-profil/',
+ views.mon_profil,
+ name='mon-profil'
+ ),
+ path(
+ 'enregistrement-document-rh',
+ views.enregistrement_document,
+ name='enregistrement-document-rh'
+ ),
+ path(
+ 'modifier-employe',
+ views.modifier_employer,
+ name='modifier-employe'
+ ),
+ path(
+ "mes-formations/ajouter/",
+ views.ajouter_formation,
+ name="ajouter_formation"
+ ),
+ path(
+ "mes-formations/liste/",
+ views.liste_formation,
+ name="liste-formation"
+ ),
+ path(
+ "mes-formations/modifier//",
+ views.modifier_formation,
+ name="modifier_formation"
+ ),
+ path(
+ "mes-formations/supprimer//",
+ views.supprimer_formation,
+ name="supprimer_formation"
+ ),
+ path(
+ 'modifier-mot-passe/',
+ views.modifier_mot_passe,
+ name='modifier-mot-passe'
+ ),
+ # path(
+ # 'creation-departement/',
+ # views.creation_departement,
+ # name='creation-departement'
+ # ),
+ # path(
+ # 'modifier-departement/',
+ # views.modifier_departement,
+ # name='modifier-departement'
+ # ),
+ # path(
+ # 'suppression-departement/',
+ # views.supprimer_departement,
+ # name='suppression-departement/'
+ # ),
+ path(
+ "liste-contrat-expirants",
+ views.liste_contrat_expirants,
+ name='liste-contrat-expirants'
+ )
+]
\ No newline at end of file
diff --git a/gestion_employe/views.py b/gestion_employe/views.py
index fa89961..f5e28f0 100644
--- a/gestion_employe/views.py
+++ b/gestion_employe/views.py
@@ -1,5 +1,9 @@
import json
+<<<<<<< HEAD
from datetime import date, timedelta, datetime
+=======
+from datetime import timedelta, datetime
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
from dateutil.relativedelta import relativedelta
from django.utils import timezone
@@ -240,13 +244,18 @@ def suppression_affectation(request):
return JsonResponse({"message": "Affectation supprimée avec succès."})
def creation_contrat(request):
+<<<<<<< HEAD
"""Créer un contrat pour un employé (avec contrôle d'existence de contrat actif)"""
+=======
+ """Vue pour permettre à un utilisateur de créer un contrat pour un employé"""
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
try:
employe = Employe.objects.get(id=request.POST.get('employe_id'))
except Employe.DoesNotExist:
messages.error(request, "Employé non trouvé.")
return redirect('employe-index')
+<<<<<<< HEAD
contrat_actif = Contrat.objects.filter(
employe=employe,
date_fin__gte=date.today()
@@ -259,6 +268,10 @@ def creation_contrat(request):
"Impossible de créer un contrat : cet employé a déjà un contrat actif."
)
return redirect('gestion_employe:index')
+=======
+
+ if request.method == "POST":
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
form = ContratForm(request.POST, request.FILES)
if form.is_valid():
contrat = form.save(commit=False)
@@ -267,6 +280,7 @@ def creation_contrat(request):
messages.success(request, "Contrat créé avec succès.")
return redirect('gestion_employe:index')
messages.error(request, "Formulaire non valide")
+<<<<<<< HEAD
else:
form = ContratForm(initial={'employe': employe})
@@ -274,6 +288,11 @@ def creation_contrat(request):
return render(request, 'gestion_employe/index.html', {
'contrat_form': form
})
+=======
+ else:
+ form = ContratForm(initial={'employe': employe})
+ return render(request, 'gestion_employe/index.html', {'contrat_form': form})
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
@login_required
def enregistrer_detail_employe(request):
diff --git a/gestion_projet/__init__.py b/gestion_projet/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gestion_projet/admin.py b/gestion_projet/admin.py
new file mode 100644
index 0000000..d0ae965
--- /dev/null
+++ b/gestion_projet/admin.py
@@ -0,0 +1,6 @@
+from django.contrib import admin
+from .models import DomaineDeRecherche
+
+@admin.register(DomaineDeRecherche)
+class DomaineDeRecherche(admin.ModelAdmin):
+ list_display = ('nom',)
diff --git a/gestion_projet/forms.py b/gestion_projet/forms.py
new file mode 100644
index 0000000..8874d67
--- /dev/null
+++ b/gestion_projet/forms.py
@@ -0,0 +1,176 @@
+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:
+ model = Projet
+ fields = (
+ 'id_projet',
+ 'nom_projet',
+ 'date_debut',
+ 'date_fin',
+ 'numero_convention',
+ 'domaine_recherche',
+ 'type_projet',
+ 'budget',
+ 'budget_RH',
+ 'description'
+ )
+ # domaine_recherche = forms.ModelMultipleChoiceField(
+ # queryset=DomaineDeRecherche.objects.all(),
+ # to_field_name="nom",
+ # required=False
+ # )
+ widgets = {
+ 'id_projet': forms.TextInput(attrs={'class': "form-control"}),
+ 'nom_projet': forms.TextInput(attrs={'class': "form-control"}),
+ 'numero_convention': forms.TextInput(attrs={'class': "form-control"}),
+ 'domaine_recherche': forms.SelectMultiple(attrs={'class': "form-control"}),
+ 'type_projet': forms.Select(attrs={'class': "form-select"}),
+ 'budget': forms.NumberInput(attrs={'class': "form-control"}),
+ 'budget_RH': forms.NumberInput(attrs={'class': "form-control"}),
+ 'description': forms.Textarea(attrs={'class': "form-control"}),
+ 'date_debut': forms.DateInput(attrs={'type': 'date', 'class': "form-control"}),
+ 'date_fin': forms.DateInput(attrs={'type': 'date', 'class': "form-control"}),
+ }
+
+class BailleurForm(forms.ModelForm):
+ """
+ Formulaire de création et de modification d'un bailleur,
+ avec validation des champs et personnalisation des labels.
+ """
+ class Meta:
+ model = Bailleur
+ fields = ('nom_organisme', 'contact', 'email', 'pays')
+ widgets = {
+ 'nom_organisme':forms.TextInput(attrs={
+ 'class':'form-control',
+ }),
+ 'contact':forms.TextInput(attrs={
+ 'class':'form-control',
+ }),
+ 'email':forms.TextInput(attrs={
+ 'class':'form-control',
+ }),
+ 'pays':forms.TextInput(attrs={
+ 'class':'form-control',
+ }),
+ }
+
+class DocumentProjetForm(forms.ModelForm):
+ """
+ Formulaire pour ajouter ou modifier un document associé à un projet,
+ avec validation des champs et personnalisation des labels.
+ """
+ class Meta:
+ model = DocumentProjet
+ fields = [
+ 'nom_document',
+ 'numero',
+ 'date_validite',
+ 'fichier',
+ 'description'
+ ]
+ widgets = {
+ 'nom_document': forms.Select(attrs={'class': 'form-select'}),
+ 'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
+ 'numero': forms.TextInput(attrs={'class': 'form-control'}),
+ 'date_validite': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
+ 'fichier': forms.ClearableFileInput(attrs={'class': 'form-control'}),
+ }
+
+class ActiviteProjetForm(forms.ModelForm):
+ """Formulaire pour créer ou modifier une activité de projet, avec validation des champs et personnalisation des widgets."""
+ class Meta:
+ model = ActiviteProjet
+ fields = (
+ 'titre',
+ 'date_debut',
+ 'date_fin',
+ 'besoin_ressource_materielle',
+ 'budget_prevu',
+ 'description',
+ )
+
+ widgets = {
+ 'titre':forms.TextInput(attrs={
+ 'class':'form-control',
+ 'placeholder':'Titre de l’activité'
+ }),
+ 'description':forms.Textarea(attrs={
+ 'class':'form-control',
+ 'rows':3,
+ 'placeholder':'Description de l’activité'
+ }),
+ 'date_debut':forms.DateInput(attrs={
+ 'class':'form-control',
+ 'type':'date'
+ }),
+ 'date_fin':forms.DateInput(attrs={
+ 'class':'form-control',
+ 'type':'date'
+ }),
+ 'besoin_ressource_materielle': forms.Textarea(attrs={
+ 'class':'form-control',
+ 'rows':3,
+ 'placeholder':'Besoin de ressources matérielles'
+ }),
+ 'budget_prevu': forms.NumberInput(attrs={
+ 'class':'form-control',
+ 'placeholder':'Budget prévu'
+ }),
+ }
+
+class FinancementProjetFrom(forms.ModelForm):
+ """Formulaire pour créer ou modifier le financement relatif à un projet."""
+ class Meta:
+ model = FinancementProjet
+ fields = (
+ 'projet',
+ 'bailleur',
+ 'pourcentage',
+ )
+
+ widgets = {
+ 'projet':forms.Select(attrs={
+ 'class':'form-select',
+ }),
+ 'bailleur':forms.Select(attrs={
+ 'class':'form-select',
+ }),
+ 'pourcentage':forms.NumberInput(attrs={
+ 'class':'form-control',
+ }),
+ }
+
+class LivrablesLivresForm(forms.ModelForm):
+ """Formulaire pour créer ou modifier un livrable livré dans le cadre d'une activité de projet."""
+ class Meta:
+ model = LivrablesLivres
+ fields = (
+ 'activite',
+ 'nom',
+ 'fichier',
+ )
+
+ widgets = {
+ 'activite': forms.Select(attrs={
+ 'class':'form-select',
+ }),
+ 'nom': forms.TextInput(attrs={
+ 'class':'form-control',
+ 'placeholder':'Nom du livrable'
+ }),
+ 'fichier': forms.ClearableFileInput(attrs={
+ 'class':'form-control',
+ }),
+ }
\ No newline at end of file
diff --git a/gestion_projet/migrations/0001_initial.py b/gestion_projet/migrations/0001_initial.py
new file mode 100644
index 0000000..c85eb6d
--- /dev/null
+++ b/gestion_projet/migrations/0001_initial.py
@@ -0,0 +1,107 @@
+# 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 = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ActiviteProjet',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('titre', models.CharField(max_length=200, verbose_name="Titre de l'activité")),
+ ('description', models.TextField(blank=True, null=True, verbose_name="Description de l'activité")),
+ ('date_debut', models.DateField(verbose_name='Date de début')),
+ ('date_fin', models.DateField(verbose_name='Date de fin')),
+ ('annuler', models.BooleanField(default=False, verbose_name="Annuler l'activité")),
+ ('motif_annulation', models.TextField(blank=True, null=True, verbose_name="Motif d'annulation")),
+ ('motif_changement_budget', models.TextField(blank=True, null=True, verbose_name='Motif de changement de budget')),
+ ('budget_prevu', models.DecimalField(decimal_places=2, default=0, max_digits=15, verbose_name='Budget prévu')),
+ ('budget_depense', models.DecimalField(decimal_places=2, default=0, max_digits=15, verbose_name='Budget dépensé')),
+ ('besoin_ressource_materielle', models.TextField(verbose_name='Besoin de ressources matérielles')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Bailleur',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('nom_organisme', models.CharField(max_length=200, unique=True)),
+ ('contact', models.CharField(blank=True, max_length=100, null=True)),
+ ('email', models.EmailField(blank=True, max_length=254, null=True)),
+ ('pays', models.CharField(blank=True, max_length=100, null=True)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='DomaineDeRecherche',
+ fields=[
+ ('nom', models.CharField(choices=[('sciences_sociales', 'Sciences sociales'), ('naturelles', 'Naturelles'), ('humaines', 'Humaines'), ('veterinaires', 'Vétérinaires')], max_length=100, primary_key=True, serialize=False, verbose_name='Domaine de recherche')),
+ ],
+ options={
+ 'verbose_name': 'Domaine de recherche',
+ 'verbose_name_plural': 'Domaines de recherche',
+ },
+ ),
+ migrations.CreateModel(
+ name='LivrablesLivres',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('nom', models.CharField(max_length=255, verbose_name='Nom du livrable')),
+ ('fichier', models.FileField(blank=True, null=True, upload_to='fichier_livrables/')),
+ ('activite', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.activiteprojet')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Projet',
+ fields=[
+ ('id_projet', models.CharField(blank=True, max_length=100, primary_key=True, serialize=False, unique=True, verbose_name='ID du projet')),
+ ('nom_projet', models.CharField(max_length=200, verbose_name='Nom du projet')),
+ ('date_debut', models.DateField(verbose_name='Date de début')),
+ ('date_fin', models.DateField(verbose_name='Date de fin')),
+ ('numero_convention', models.CharField(max_length=100, verbose_name='Numéro de convention')),
+ ('description', models.TextField(verbose_name='Description')),
+ ('type_projet', models.CharField(choices=[('laboratoire', 'Laboratoire'), ('épidémiologie', 'Épidémiologie'), ('sciences sociales', 'Sciences sociales'), ('cliniques', 'Cliniques'), ('autre', 'Autre')], default='épidémiologie', max_length=100, verbose_name='Type de projet')),
+ ('budget', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Budget')),
+ ('budget_RH', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Budget RH')),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('bailleur', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='gestion_projet.bailleur', verbose_name='Bailleur de fonds')),
+ ('domaine_recherche', models.ManyToManyField(to='gestion_projet.domainederecherche')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='DocumentProjet',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('date_ajout', models.DateTimeField(auto_now_add=True, verbose_name="Date d'ajout")),
+ ('nom_document', models.CharField(choices=[('protocole', 'Protocole d’étude'), ('ethique', "Approbation du comité d'éthique"), ('autorisation', 'Autorisation (DNLP)'), ('rapport_technique', 'Rapport technique'), ('rapport_financier', 'Rapport financier'), ('rapport_avancement', "Rapport d'avancement"), ('convention', 'Convention'), ('rapport_final', 'Rapport final'), ('autre', 'Autre')], max_length=100, verbose_name='Type de document')),
+ ('description', models.TextField(blank=True, verbose_name='Description')),
+ ('numero', models.CharField(blank=True, max_length=100, null=True, verbose_name='Numéro du document')),
+ ('date_validite', models.DateField(blank=True, null=True, verbose_name='Date de validité')),
+ ('fichier', models.FileField(upload_to='documents_projets/', verbose_name='Fichier à télécharger')),
+ ('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='gestion_projet.projet', verbose_name='Projet')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='activiteprojet',
+ name='projet',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet', verbose_name='Projet'),
+ ),
+ migrations.CreateModel(
+ name='FinancementProjet',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('pourcentage', models.DecimalField(decimal_places=2, max_digits=5)),
+ ('bailleur', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.bailleur')),
+ ('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet')),
+ ],
+ options={
+ 'unique_together': {('projet', 'bailleur')},
+ },
+ ),
+ ]
diff --git a/gestion_projet/migrations/__init__.py b/gestion_projet/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/gestion_projet/models.py b/gestion_projet/models.py
new file mode 100644
index 0000000..d5723bb
--- /dev/null
+++ b/gestion_projet/models.py
@@ -0,0 +1,309 @@
+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(
+ max_length=200,
+ unique=True
+ )
+ contact = models.CharField(
+ max_length=100,
+ blank=True,
+ null=True
+ )
+ email = models.EmailField(
+ blank=True,
+ null=True
+ )
+ pays = models.CharField(
+ max_length=100,
+ blank=True,
+ null=True
+ )
+
+ def __str__(self):
+ return self.nom_organisme
+
+class DomaineDeRecherche(models.Model):
+ """Modèle représentant les domaines de recherche"""
+
+ DOMAINE_RECHERCHE = [
+ ('sciences_sociales', 'Sciences sociales'),
+ ('naturelles', 'Naturelles'),
+ ('humaines', 'Humaines'),
+ ('veterinaires', 'Vétérinaires')
+ ]
+
+ nom = models.CharField(
+ max_length=100,
+ verbose_name="Domaine de recherche",
+ choices=DOMAINE_RECHERCHE,
+ primary_key=True
+ )
+
+ class Meta:
+ verbose_name = 'Domaine de recherche'
+ verbose_name_plural = 'Domaines de recherche'
+
+ def __str__(self):
+ return self.nom
+
+class Projet(models.Model):
+ """Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé."""
+ TYPE_PROJET = [
+ ('laboratoire', 'Laboratoire'),
+ ('épidémiologie', 'Épidémiologie'),
+ ('sciences sociales', 'Sciences sociales'),
+ ('cliniques', 'Cliniques'),
+ ('autre', 'Autre'),
+ ]
+ id_projet = models.CharField(
+ max_length=100,
+ blank=True,
+ unique=True,
+ primary_key=True,
+ verbose_name="ID du projet"
+ )
+ nom_projet = models.CharField(
+ max_length=200,
+ verbose_name="Nom du projet"
+ )
+ date_debut = models.DateField(
+ verbose_name="Date de début"
+ )
+ date_fin = models.DateField(
+ verbose_name="Date de fin"
+ )
+ numero_convention = models.CharField(
+ max_length=100,
+ verbose_name="Numéro de convention"
+ )
+ description = models.TextField(
+ verbose_name="Description"
+ )
+ type_projet = models.CharField(
+ max_length=100,
+ choices=TYPE_PROJET,
+ default='épidémiologie',
+ verbose_name="Type de projet"
+ )
+ domaine_recherche = models.ManyToManyField(DomaineDeRecherche)
+ budget=models.DecimalField(
+ max_digits=12,
+ decimal_places=2,
+ verbose_name="Budget"
+ )
+ budget_RH = models.DecimalField(
+ max_digits=12,
+ decimal_places=2,
+ verbose_name="Budget RH"
+ )
+ created_at = models.DateTimeField(auto_now_add=True)
+ bailleur = models.ForeignKey(
+ Bailleur,
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ verbose_name="Bailleur de fonds"
+ )
+
+ @property
+ def statut(self):
+ if self.date_fin < date.today():
+ return "Terminé"
+ return "En cours"
+
+ @property
+ def avancement(self):
+ aujourd_hui = 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
+ if duree_projet > 0:
+ return round((temps_ecoule / duree_projet) * 100, 2)
+
+ return 0
+
+ def __str__(self):
+ return f"{self.nom_projet}"
+
+class FinancementProjet(models.Model):
+ """
+ Modèle représentant le financement d'un projet par un bailleur,
+ avec le pourcentage de contribution.
+ """
+ projet = models.ForeignKey(
+ Projet,
+ on_delete=models.CASCADE
+ )
+ bailleur = models.ForeignKey(
+ Bailleur,
+ on_delete=models.CASCADE
+ )
+ pourcentage = models.DecimalField(
+ max_digits = 5,
+ decimal_places=2
+ )
+
+ class Meta:
+ unique_together = ('projet', 'bailleur')
+
+ def __str__(self):
+ return f"{self.bailleur.nom} - {self.projet.nom_projet} ({self.pourcentage}%)"
+
+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 = [
+ ('protocole', 'Protocole d’étude'),
+ ('ethique', "Approbation du comité d'éthique"),
+ ('autorisation', 'Autorisation (DNLP)'),
+ ('rapport_technique', 'Rapport technique'),
+ ('rapport_financier', 'Rapport financier'),
+ ('rapport_avancement', "Rapport d'avancement"),
+ ('convention', 'Convention'),
+ ('rapport_final', 'Rapport final'),
+ ('autre', 'Autre'),
+ ]
+
+ projet = models.ForeignKey(
+ Projet,
+ on_delete=models.CASCADE,
+ related_name='documents',
+ verbose_name="Projet"
+ )
+ date_ajout = models.DateTimeField(
+ auto_now_add=True,
+ verbose_name="Date d'ajout"
+ )
+ nom_document = models.CharField(
+ max_length = 100,
+ choices = NOM_DOCUMENT_CHOICES,
+ verbose_name="Type de document"
+ )
+ description = models.TextField(
+ blank = True,
+ verbose_name = "Description"
+ )
+ numero = models.CharField(
+ max_length = 100,
+ blank = True,
+ null = True,
+ verbose_name = "Numéro du document"
+ )
+ date_validite = models.DateField(
+ blank = True,
+ null = True,
+ verbose_name = "Date de validité"
+ )
+ fichier = models.FileField(
+ upload_to = 'documents_projets/',
+ verbose_name = "Fichier à télécharger"
+ )
+
+ def __str__(self):
+ return f"{self.nom_document} ({self.projet})"
+
+class ActiviteProjet(models.Model):
+ """Modèle représentant le planning d'un projet, avec des activités associées et un statut."""
+ projet = models.ForeignKey(
+ Projet,
+ on_delete = models.CASCADE,
+ verbose_name = "Projet"
+ )
+ titre = models.CharField(
+ max_length = 200,
+ verbose_name = "Titre de l'activité"
+ )
+ description = models.TextField(
+ blank = True,
+ null = True,
+ verbose_name = "Description de l'activité"
+ )
+ date_debut = models.DateField(verbose_name="Date de début")
+ date_fin = models.DateField(verbose_name="Date de fin")
+ annuler = models.BooleanField(
+ default = False,
+ verbose_name = "Annuler l'activité"
+ )
+ motif_annulation = models.TextField(
+ blank = True,
+ null = True,
+ verbose_name = "Motif d'annulation"
+ )
+
+ motif_changement_budget = models.TextField(
+ blank = True,
+ null = True,
+ verbose_name = "Motif de changement de budget"
+ )
+ budget_prevu = models.DecimalField(
+ max_digits = 15,
+ decimal_places = 2,
+ default = 0,
+ verbose_name = "Budget prévu"
+ )
+ budget_depense = models.DecimalField(
+ max_digits = 15,
+ decimal_places = 2,
+ default = 0,
+ verbose_name = "Budget dépensé"
+ )
+ besoin_ressource_materielle = models.TextField(
+ verbose_name="Besoin de ressources matérielles"
+ )
+
+ @property
+ def statut(self):
+ today = timezone.now().date()
+ if not self.annuler:
+ if self.date_fin < today:
+ return 'Terminé'
+ elif self.date_debut > today:
+ return 'À venir'
+ else:
+ return 'En cours'
+ else:
+ return 'Annulé'
+
+ def __str__(self):
+ return f"{self.titre} ({self.projet.nom_projet})"
+
+# class LivrableAttendu(models.Model):
+# """
+# Modèle représentant un livrable attendu pour une activité de projet,
+# avec des critères de validation.
+# """
+# activite = models.ForeignKey(
+# ActiviteProjet,
+# on_delete = models.CASCADE,
+# related_name = "livrables_attendus"
+# )
+# nom = models.CharField(max_length=255)
+
+# def __str__(self):
+# return f"{self.nom} (Activité: {self.activite.titre})"
+
+class LivrablesLivres(models.Model):
+ """Modèle représentant un livrable livré pour une activité de projet."""
+ activite = models.ForeignKey(
+ ActiviteProjet,
+ on_delete = models.CASCADE
+ )
+ # nom = models.ForeignKey(
+ # LivrableAttendu,
+ # on_delete = models.CASCADE
+ # )
+ nom = models.CharField(
+ max_length=255,
+ verbose_name="Nom du livrable"
+ )
+
+ fichier = models.FileField(
+ upload_to = 'fichier_livrables/',
+ blank = True,
+ null = True
+ )
+ def __str__(self):
+ return self.nom
\ No newline at end of file
diff --git a/gestion_projet/static/gestion_projet/js/creation_projet.js b/gestion_projet/static/gestion_projet/js/creation_projet.js
new file mode 100644
index 0000000..36c1a10
--- /dev/null
+++ b/gestion_projet/static/gestion_projet/js/creation_projet.js
@@ -0,0 +1,19 @@
+const boutonEnregistrerProjet = $("btnEnregistrerProjet");
+
+boutonEnregistrerProjet.addEventListener("click", function() {
+ const formulaire = $("formCreationProjet");
+ const formData = new FormData(formulaire);
+ fetch(formulaire.action, {
+ method: "POST",
+ body: formData,
+ headers: {
+ "X-CSRFToken": formData.get("csrfmiddlewaretoken")
+ }
+ })
+ .then(response => {
+ if (response.ok) {
+ window.location.reload();
+ alert("Projet enregistré avec succès !");
+ }
+ });
+});
\ No newline at end of file
diff --git a/gestion_projet/static/gestion_projet/js/enregistrement_bailleur.js b/gestion_projet/static/gestion_projet/js/enregistrement_bailleur.js
index c09f502..6c171d2 100644
--- a/gestion_projet/static/gestion_projet/js/enregistrement_bailleur.js
+++ b/gestion_projet/static/gestion_projet/js/enregistrement_bailleur.js
@@ -1,5 +1,8 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
+<<<<<<< HEAD
let table;
+=======
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur');
@@ -21,6 +24,7 @@ btnEnregistrerBailleur.addEventListener('click', function() {
alert('Ce bailleur existe déjà dans la base de données.');
}
});
+<<<<<<< HEAD
});
document.addEventListener("DOMContentLoaded", function () {
@@ -49,3 +53,6 @@ document.addEventListener("DOMContentLoaded", function () {
});
+=======
+});
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
diff --git a/gestion_projet/static/gestion_projet/js/enregistrement_financement.js b/gestion_projet/static/gestion_projet/js/enregistrement_financement.js
new file mode 100644
index 0000000..1e9646c
--- /dev/null
+++ b/gestion_projet/static/gestion_projet/js/enregistrement_financement.js
@@ -0,0 +1,22 @@
+const btn_enregistrer_financement = document.getElementById('btn_enregistrer_financement');
+
+btn_enregistrer_financement.addEventListener('click', function() {
+ const form = document.getElementById('form_financement');
+ const formData = new FormData(form);
+ fetch(form.action, {
+ method: 'POST',
+ body: formData,
+ headers: {
+ 'X-CSRFToken': formData.get('csrfmiddlewaretoken')
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ alert(data.message);
+ window.location.reload();
+ }else {
+ alert(data.message);
+ }
+ });
+});
\ No newline at end of file
diff --git a/gestion_projet/static/gestion_projet/js/index.js b/gestion_projet/static/gestion_projet/js/index.js
new file mode 100644
index 0000000..eb6285e
--- /dev/null
+++ b/gestion_projet/static/gestion_projet/js/index.js
@@ -0,0 +1,64 @@
+const $ = (element) => document.getElementById(element)
+const url_liste_projet = $("tableau-liste-projet").dataset.url;
+const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
+ fitColumns: true,
+ responsiveLayout : true,
+ columns: [
+ {title: "Projet", field: "nom_projet"},
+ {title: "Source de financement", field: "source_financement"},
+ {title: "Budget Total", field: "budget"},
+ {title: "Budget RH", field: "budget_RH"},
+ {title: "Avancement", field: "avancement", formatter: "progress"},
+ {title: "Statut", field: "statut"},
+ ],
+ ajaxURL: url_liste_projet
+})
+
+const employes_affectes_projet = new Tabulator("#employes_affectes_projet", {
+ columns: [
+ {title: "Employé", field: "employe"},
+ {title: "Pourcentage d'affectation", field: "pourcentage_affectation"},
+ ],
+ placeholder: "Aucun employé affecté pour ce projet",
+})
+
+const bailleurs_projet = new Tabulator("#bailleurs_projet", {
+ columns: [
+ {title: "Bailleur", field: "bailleur"},
+ {title: "Pourcentage de financement", field: "pourcentage_financement"},
+ ],
+ placeholder: "Aucun bailleur attribué pour ce projet",
+})
+
+tableau_liste_projet.on("rowClick", function (row, rowData) {
+ const data = rowData.getData();
+ const modal = new bootstrap.Modal($("modalDetailProjet"));
+
+ $("detail_id_projet").value = data.id_projet;
+ $("detail_nom_projet").value = data.nom_projet;
+ $("detail_date_debut").value = data.date_debut;
+ $("detail_date_fin").value = data.date_fin;
+ $("detail_numero_convention").value = data.numero_convention;
+ $("detail_type_projet").value = data.type_projet;
+ Array.from($("detail_domaine_recherche").options).forEach(option => {
+ if (data.domaine_recherche.includes(option.value)) {
+ option.selected = true;
+ } else {
+ option.selected = false;
+ }
+ });
+ $("detail_budget").value = data.budget;
+ $("detail_budget_rh").value = data.budget_RH;
+ $("detail_description").value = data.description;
+ $("detail_statut").value = data.statut;
+
+ employes_affectes_projet.setData(`projet/liste-employes-par-projet/${$("detail_id_projet").value}`);
+ bailleurs_projet.setData(`projet/bailleurs/${data.id_projet}/`);
+
+ modal.show();
+})
+
+// $('detail-projet-form').addEventListener('submit', (e) => {
+// e.preventDefault();
+// new FormData($("detail-projet-form"));
+// })
\ No newline at end of file
diff --git a/gestion_projet/static/gestion_projet/js/liste_documents_projet.js b/gestion_projet/static/gestion_projet/js/liste_documents_projet.js
new file mode 100644
index 0000000..e586ae1
--- /dev/null
+++ b/gestion_projet/static/gestion_projet/js/liste_documents_projet.js
@@ -0,0 +1,20 @@
+const urlListeDocument = document.getElementById('listeDocuments').dataset.urllistedocument;
+
+const table_liste_documents = new Tabulator(document.getElementById('listeDocuments'), {
+ layout: "fitColumns",
+ placeholder: "Aucun document trouvé",
+ columns: [
+ { title: "Nom du Document", field: "nom_document" },
+ { title: "Numéro", field: "numero" },
+ { title: "Date de Validité", field: "date_validite", formatter: "datetime", formatterParams: {
+ inputFormat: "yyyy-MM-dd",
+ outputFormat: "dd/MM/yyyy"
+ }
+ },
+ { title: "Lien vers le Document", field: "lien_document", formatter:"link", formatterParams:{
+ target:"_blank",
+ }
+ },
+ ],
+ ajaxURL: urlListeDocument,
+});
\ No newline at end of file
diff --git a/gestion_projet/static/gestion_projet/js/suivi-activites.js b/gestion_projet/static/gestion_projet/js/suivi-activites.js
new file mode 100644
index 0000000..d0775d7
--- /dev/null
+++ b/gestion_projet/static/gestion_projet/js/suivi-activites.js
@@ -0,0 +1,59 @@
+const $ = (element) => document.getElementById(element)
+
+const url_liste_activite = $("tableau-liste-activite").dataset.urllisteactivite
+const tableau_liste_activite = new Tabulator("#tableau-liste-activite", {
+ columns: [
+ {title: "Activité", field: "titre"},
+ {title: "Date début", field: "date_debut"},
+ {title: "Date fin", field: "date_fin"},
+ {title: "Budget prévu", field: "budget_prevu"},
+ {title: "Budget dépensé", field: "budget_depense"},
+ {title: "Motif de changement de budget", field: "motif_changement_budget"},
+ {title: "Statut", field: "statut"},
+ ],
+ ajaxURL: url_liste_activite,
+})
+tableau_liste_activite.on("rowClick", function (row, rowData) {
+ const data = rowData.getData();
+ $("idDetailActivite").value = data.id;
+ $("titreDetailActivite").value = data.titre;
+ $("descriptionDetailActivite").value = data.description;
+ $("date_debutDetailActivite").value = data.date_debut;
+ $("date_finDetailActivite").value = data.date_fin;
+ $("statutDetailActivite").value = data.statut;
+ $("budget_prevuDetailActivite").value = data.budget_prevu;
+ $("besoin_ressources_materiellesDetailActivite").value = data.besoin_ressource_materielle;
+ const modal = new bootstrap.Modal($("modalDetailActivite"));
+ modal.show();
+
+ fetch(`liste-des-livrables/${data.id}/`)
+ .then(response => response.json())
+ .then(livrables => {
+ tableau_liste_livrable.setData(livrables);
+ })
+})
+
+const tableau_liste_livrable = new Tabulator("#listeLivrables", {
+ columns: [
+ {title: "Livrable", field: "titre"},
+ {title: "Lien du livrable", field: "lien", formatter: "link", formatterParams: {blank: true}},
+ ],
+ placeholder: "Aucun livrable trouvé",
+})
+
+$("btnMiseAJourDepense").addEventListener("click", function() {
+ const modal = new bootstrap.Modal($("modalDepenseActivite"));
+ bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
+ const idActivite = $("idDetailActivite").value;
+ const budgetPrevu = $("budget_prevuDetailActivite").value;
+
+ $("id_activite_depense").value = idActivite;
+ $("budget_prevu").value = budgetPrevu;
+ modal.show();
+})
+
+$("btnAnnulerActivite").addEventListener("click", function(event) {
+ new bootstrap.Modal($("modalAnnulerActivite")).show();
+ $("id_activite_annulation").value = $("idDetailActivite").value;
+ bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
+})
\ No newline at end of file
diff --git a/gestion_projet/templates/gestion_projet/ajout-financement.html b/gestion_projet/templates/gestion_projet/ajout-financement.html
new file mode 100644
index 0000000..642fc29
--- /dev/null
+++ b/gestion_projet/templates/gestion_projet/ajout-financement.html
@@ -0,0 +1,37 @@
+{% extends "BASE.html" %}
+{% load static %}
+{% block 'titre_page' %} Gestion des projets {% endblock %}
+{% block 'contenu' %}
+
+
+ {% if messages %}
+ {% for message in messages %}
+
+ {{ message }}
+
+
+ {% endfor %}
+ {% endif %}
+
+
+
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
index 2f31a37..ed01d66 100644
--- a/gestion_projet/templates/gestion_projet/parts/creation_bailleur.html
+++ b/gestion_projet/templates/gestion_projet/parts/creation_bailleur.html
@@ -1,4 +1,5 @@
+<<<<<<< HEAD
@@ -44,8 +45,28 @@
+=======
+
+
+
+
Ajouter un Bailleur
+
+
+
+
+
+
+<<<<<<< HEAD
+=======
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
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
index 06e898e..da4ae90 100644
--- a/gestion_projet/urls.py
+++ b/gestion_projet/urls.py
@@ -20,12 +20,15 @@ urlpatterns = [
name='creation-projet'
),
path(
+<<<<<<< HEAD
'bailleurs/',
views.liste_bailleur,
name='liste-bailleurs'
),
path(
+=======
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
'projet/modifier//',
views.modification_projet,
name='modifier-projet'
@@ -90,7 +93,10 @@ urlpatterns = [
views.liste_activites_projet,
name='liste-activites-projet'
),
+<<<<<<< HEAD
+=======
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
# path(
# 'projet/ajout-de-document/',
# views.ajouter_document_projet,
@@ -126,6 +132,10 @@ urlpatterns = [
views.mises_a_jour_projet,
name='mises-a-jour-projet'
)
+<<<<<<< HEAD
]
+=======
+]
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
diff --git a/gestion_projet/views.py b/gestion_projet/views.py
index 80a4ba4..1017e05 100644
--- a/gestion_projet/views.py
+++ b/gestion_projet/views.py
@@ -2,7 +2,10 @@ from datetime import date
from decimal import Decimal, InvalidOperation
from django.http import JsonResponse
from django.shortcuts import redirect, render
+<<<<<<< HEAD
from django.urls import reverse
+=======
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
from django.utils import timezone
from django.contrib import messages
from django.contrib.auth.decorators import login_required
@@ -145,6 +148,7 @@ def creation_bailleur(request):
return JsonResponse({'success': False})
@login_required
+<<<<<<< HEAD
def liste_bailleur(request):
""" Vue pour retourner la liste de tous les bailleurs """
bailleurs = Bailleur.objects.all().order_by('-id')
@@ -161,6 +165,8 @@ def liste_bailleur(request):
@login_required
+=======
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
def ajouter_financement_projet(request):
"""Ajoute un financement à un projet en vérifiant que le total ne dépasse pas 100%"""
@@ -335,7 +341,10 @@ def activites_projet(request):
}
return render(request, 'gestion_projet/suivi_activite.html', context)
+<<<<<<< HEAD
+=======
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
@login_required
def ajouter_activite_projet(request):
"""Vue pour ajouter une activité à un projet spécifique via un formulaire"""
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
index 44cb922..42d753d 100644
--- a/gestion_salle/static/gestion_salle/js/index.js
+++ b/gestion_salle/static/gestion_salle/js/index.js
@@ -167,9 +167,15 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("lien_zoom_container").className = 'd-none';
}
+<<<<<<< HEAD
// if(data.statut !== "refusee"){
// $("motif_refus_container").className = 'd-none';
// }
+=======
+ if(data.statut !== "refusee"){
+ $("motif_refus_container").className = 'd-none';
+ }
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
$("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id;
@@ -178,15 +184,23 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe;
$("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut;
+<<<<<<< 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)
$("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
// $("motif_refus").value=data.motif_refus;
+=======
+ $("motif_refus").value=data.motif_refus;
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
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
index 86e1006..64eb9d9 100644
--- a/gestion_salle/templates/gestion_salle/parts/modalDetailResevation.html
+++ b/gestion_salle/templates/gestion_salle/parts/modalDetailResevation.html
@@ -21,12 +21,17 @@
+<<<<<<< HEAD
+=======
+
+
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
@@ -52,19 +57,38 @@
+<<<<<<< HEAD
+=======
+
+
+
+
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
{% if appartient_au_departement_informatique %}
{% endif %}
{% if appartient_direction and reservation.statut == "en_attente" %}
+<<<<<<< HEAD
{% endif %}
+=======
+ {% endif %}
+
+ {% if appartient_direction %}
+
+ {% endif %}
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
-
\ No newline at end of file
+<<<<<<< HEAD
+
+=======
+
+>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
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('