14 Commits

Author SHA1 Message Date
Fatimac2804
1da9d4ca5f Affichage des livrables sur les details de l'activité
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
SGL/sirh/pipeline/head This commit looks good
2026-09-02 17:17:23 +02:00
Fatimac2804
e81c29a586 Affichage des détails de la réservation de salle
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
SGL/sirh/pipeline/head This commit looks good
2026-09-02 14:43:27 +02:00
Fatimac2804
d28bf85556 Affichage de la liste des departements pour la modification de l'employer
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
SGL/sirh/pipeline/head This commit looks good
2026-09-02 14:33:51 +02:00
Fatimac2804
3b587d2a1a Affichage des detait de la reservation de salle
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-02 13:46:07 +02:00
Fatimac2804
e3f0befff6 Correction sur l'afficha de la liste de departement pour la modification des informations de l'employer
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-02 12:48:35 +02:00
Soriba SYLLA
55e5a2819c Ajout du contexte processor
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-08-31 09:48:21 +00:00
Soriba SYLLA
7e28b25497 Merge branch 'staging' 2026-08-23 15:14:24 +00:00
643205a4bb Premiere implementation de sso
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-08-04 09:34:31 +00:00
4934b42ed9 Implementation de sso 2026-07-29 10:26:22 +00:00
f9ca539670 Supprimer gestion_salle/migrations/0001_initial.py
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-07-14 09:17:13 +00:00
7b4778252f Supprimer gestion_projet/migrations/0001_initial.py
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-07-14 09:17:02 +00:00
2c086187e7 Supprimer gestion_employe/migrations/0001_initial.py
Some checks are pending
Organisation/sirh/pipeline/head Build queued...
2026-07-14 09:16:51 +00:00
5aff49600e Supprimer gestion_conge/migrations/0001_initial.py
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-07-14 09:16:28 +00:00
8c6e59e15a Correction enregistrement profil
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-07-14 09:03:28 +00:00
63 changed files with 477 additions and 262 deletions

View File

@@ -24,9 +24,10 @@ BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = config('SECRET_KEY') SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=False, cast=bool) DEBUG = config('DEBUG', default=True, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',') ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',')
#CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]).split(",")
# Application definition # Application definition
@@ -40,12 +41,9 @@ INSTALLED_APPS = [
'gestion_employe', 'gestion_employe',
'gestion_conge', 'gestion_conge',
'gestion_projet', 'gestion_projet',
'gestion_salle', 'gestion_salle'
'simple_sso.sso_server'
] ]
LOGIN_URL = 'login'
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
@@ -68,6 +66,7 @@ TEMPLATES = [
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'contexte_universel.contexte_universel',
], ],
}, },
}, },
@@ -80,24 +79,16 @@ WSGI_APPLICATION = 'SIRH.wsgi.application'
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases # https://docs.djangoproject.com/en/5.2/ref/settings/#databases
if config('ENVIRONMENT') == 'local': DATABASES = {
DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.sqlite3', 'ENGINE': 'django.db.backends.mysql',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 'NAME': config("DB_NAME"),
} 'USER': config("DB_USER"),
} 'PASSWORD': config("DB_PASSWORD"),
else: 'HOST': config("DB_HOST"),
DATABASES = { 'PORT': config("DB_PORT", cast=int),
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': config('DATABASE_NAME'),
'USER': config('DATABASE_USER'),
'PASSWORD': config('DATABASE_PASSWORD'),
'HOST': config('DATABASE_HOST'),
'PORT': config('DATABASE_PORT'),
}
} }
}
# Password validation # Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
@@ -150,12 +141,21 @@ MEDIA_ROOT = BASE_DIR / "media"
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Connexion sso
# LOGIN_URL = '/client/'
LOGIN_URL = 'simple-sso-login'
SSO_PRIVATE_KEY = config('SSO_PRIVATE_KEY')
SSO_PUBLIC_KEY = config('SSO_PUBLIC_KEY')
SSO_SERVER = config('SSO_SERVER')
#SSO_LOGOUT_URL = 'http://test-authentification.cerfig.org:89/deconnexion'
# Configuration de l'email # Configuration de l'email
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'ssl0.ovh.net' EMAIL_HOST = 'ssl0.ovh.net'
EMAIL_PORT = 465 EMAIL_PORT = 465
EMAIL_USE_SSL = True EMAIL_USE_SSL = True
EMAIL_USE_TLS = False EMAIL_USE_TLS = False
EMAIL_HOST_USER = 'support.it@cerfig.org' EMAIL_HOST_USER = config("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = 'Cerfig2025' EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD")
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

39
SIRH/sso.py Normal file
View File

@@ -0,0 +1,39 @@
# SGL/sso.py
from gestion_employe.models import Employe, Departement
from simple_sso.sso_client.client import Client
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
User = get_user_model()
class SIRHClient(Client):
def build_user(self, user_data):
"""Crée le User et son Profile avec les infos reçues du serveur."""
user_data_copy = user_data.copy()
print("Un petit test", user_data_copy)
user_data.pop("departement", None)
user_data.pop("chef_departement", None)
user_data.pop("groups", None)
user = super().build_user(user_data)
departement = None
if "departement" in user_data_copy:
departement = Departement.objects.get_or_create(
nom=user_data_copy.get("departement", ""))[0]
Employe.objects.update_or_create(
user = user,
defaults = {
"departement": departement,
"chef": user_data_copy.get("chef_departement", None),
}
)
for group_name in user_data_copy.get("groups", []):
group, _ = Group.objects.get_or_create(name=group_name)
user.groups.add(group)
return user

View File

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

View File

@@ -24,15 +24,16 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="next" value="{{ next }}">
<div class="mb-3"> <div class="mb-3">
<label for="mail">Votre adresse email :</label> <label for="mail">Votre adresse email :</label>
<input type="text" name="mail" class="form-control" placeholder="Entrez votre e-mail" required> <input type="text" name="mail" class="form-control" placeholder="Entrez votre e-mail" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="mot_de_passe">Mot de passe</label> <label for="mot_de_passe">Mot de passe</label>
<input type="password" name="mot_de_passe" class="form-control" placeholder="Entrez votre mot de passe" required> <input type="password" name="mot_de_passe" class="form-control" placeholder="Entrez votre mot de passe" required>
<i class="bi bi-eye toggle-password" onclick="togglePassword()" <i class="bi bi-eye toggle-password" onclick="togglePassword()"
style="position:absolute; right:10px; top:38px; cursor:pointer;"></i> style="position:absolute; right:10px; top:38px; cursor:pointer;"></i>
</div> </div>
<button type="submit" class="btn btn-primary w-100">Se connecter</button> <button type="submit" class="btn btn-primary w-100">Se connecter</button>
</form> </form>

View File

@@ -0,0 +1,8 @@
<div class="col-1 bg-danger border d-flex flex-column vh-100 pt-5 sticky-top">
<a href="{{ SIRH_LIEN }}" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-people-fill fs-1 d-block"></i>SIRH
</a>
<a href="{{ SGL_LIEN }}" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-building-gear fs-1 d-block"></i> SGL
</a>
</div>

View File

@@ -13,35 +13,36 @@
<i class="bi bi-person-circle text-white" style="font-size:60px;"></i> <i class="bi bi-person-circle text-white" style="font-size:60px;"></i>
{% endif %} {% endif %}
<div class="text-white mt-2 fw-bold fs-5"> <div class="text-white mt-2 fw-bold fs-5">
{{ user.username }} {{ user.first_name }} {{ user.last_name }}
</div> </div>
</div> </div>
<a href="{% url 'gestion_employe:mon-profil' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em"> <a href="{% url 'gestion_employe:mon-profil' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<i class="bi bi-person-circle"></i> Mon profil <i class="bi bi-person-circle"></i> Mon profil
</a> </a>
{% if user|has_group:"ressource_humaine" or user|has_group:"direction" %} {% if user|has_group:"ressource_humaine" or user|has_group:"direction" %}
<a href="{% url 'gestion_employe:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em"> <a href="{% url 'gestion_employe:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<i class="bi bi-speedometer2"></i> Tableau de bord <i class="bi bi-speedometer2"></i> Tableau de bord
</a> </a>
{% endif %} {% endif %}
{% if user|has_group:"ressource_humaine" or user|has_group:"direction" %} {% if user|has_group:"ressource_humaine" or user|has_group:"direction" %}
<a href="{% url 'gestion_projet:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em"> <a href="{% url 'gestion_projet:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<i class="bi bi-folder"></i> Gestion des Projets <i class="bi bi-folder"></i> Gestion des Projets
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'gestion_conges:conge' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em"> <a href="{% url 'gestion_conges:conge' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<i class="bi bi-airplane"></i> Gestion des congés <i class="bi bi-airplane"></i> Gestion des congés
</a> </a>
{% if user|is_chef_projet %} {% if user|is_chef_projet %}
<a href="{% url 'gestion_projet:activites-projet' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em"> <a href="{% url 'gestion_projet:activites-projet' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<i class="bi bi-list-task"></i> Suivi des Activités <i class="bi bi-list-task"></i> Suivi des Activités
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'gestion_salle:reservation-salle' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em"> <a href="{% url 'gestion_salle:reservation-salle' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<i class="bi bi-calendar-check"></i> Réservation <i class="bi bi-calendar-check"></i> Réservation
</a> </a>
<a href="{% url 'deconnexion' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em"> {% comment %} {% url 'deconnexion' %} {% endcomment %}
<a href="{% url 'deconnexion' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<i class="bi bi-box-arrow-right"></i> Déconnexion <i class="bi bi-box-arrow-right"></i> Déconnexion
</a> </a>
</div> </div>

View File

@@ -16,32 +16,29 @@ Including another URLconf
""" """
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from django.conf.urls.static import static # from django.conf.urls.static import static
from django.conf import settings from django.conf import settings
# from simple_sso.sso_server.server import Server
from . import views from . import views
from .sso import SIRHClient
# server_sso = Server() client_sirh = SIRHClient(
settings.SSO_SERVER,
settings.SSO_PUBLIC_KEY,
settings.SSO_PRIVATE_KEY
)
urlpatterns = [ urlpatterns = [
path(
'',
views.login_view,
name='index'
),
path('login/', path('login/',
views.login_view, views.connexion,
name='login' name='login'
), ),
path( path(
'deconnexion/', 'deconnexion/',
views.deconnexion_view, views.deconnexion,
name='deconnexion' name='deconnexion'
), ),
path( path(
'employé/', '',
include("gestion_employe.urls") include("gestion_employe.urls")
), ),
path( path(
@@ -59,9 +56,7 @@ urlpatterns = [
path( path(
'admin/', 'admin/',
admin.site.urls admin.site.urls
), ),
# path( path("client/", include(client_sirh.get_urls())),
# 'sso', ]
# include(server_sso.get_urls()) # + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# )
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -1,37 +1,16 @@
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import logout
from django.shortcuts import render, redirect from django.shortcuts import redirect
from django.contrib import messages from django.urls import reverse_lazy
def login_view(request): from django.conf import settings
"""
Gère la connexion des utilisateurs avec redirection selon le rôle et
vérification de l'acceptation de la politique d'utilisation. def connexion(request):
""" """Redirige vers le serveur SSO pour l'authentification."""
if request.method == 'POST': return redirect(reverse_lazy("simple-sso-login"))
email = request.POST.get('mail')
password = request.POST.get('mot_de_passe') def deconnexion(request):
"""Gérez la déconnexion des utilisateurs."""
if not (email and password): logout(request)
messages.error(request, "Veuillez remplir tous les champs.") #return redirect(settings.SSO_LOGOUT_URL)
return render(request, 'login.html') return redirect("http://authentification.cerfig.org/login")
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')

7
contexte_universel.py Normal file
View File

@@ -0,0 +1,7 @@
from decouple import config
def contexte_universel(request):
return {
"SIRH_LIEN": config("SIRH_LIEN"),
"SGL_LIEN": config("SGL_LIEN")
}

View File

@@ -7,7 +7,11 @@ QUOTA_CONGE_ANNUEL = 30
NOMBRE_PAGINATION = 8 NOMBRE_PAGINATION = 8
DEBUT_RAPPEL = 60 DEBUT_RAPPEL = 60
DUREE_FIN_CONTRAT = 90 DUREE_FIN_CONTRAT = 90
EMAIL_ASSISTANTE_DE_DIRECTION = list(Employe.objects.filter(fonction="assistant_direction").values_list('user__email', flat=True))
try:
EMAIL_ASSISTANTE_DE_DIRECTION = list(Employe.objects.filter(fonction="assistant_direction").values_list('user__email', flat=True))
except:
pass
def solde_conge(employe): def solde_conge(employe):
"""Fonction de calcul du solde de congé restant l'employé""" """Fonction de calcul du solde de congé restant l'employé"""

View File

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

View File

@@ -3,7 +3,7 @@ const $ = (element) => document.getElementById(element);
const url_liste_conge_attente = $("liste-demande-conges").dataset.url const url_liste_conge_attente = $("liste-demande-conges").dataset.url
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
layout : "fitColumns", // layout : "fitColumns",
columns: [ columns: [
{"title": "Nom et Prénom", "field": "prenom_nom"}, {"title": "Nom et Prénom", "field": "prenom_nom"},
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{ {"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
@@ -15,7 +15,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
outputFormat:"dd/MM/yy", outputFormat:"dd/MM/yy",
}}, }},
{"title": "Type de congé", "field": "type"}, {"title": "Type de congé", "field": "type"},
{"title": "Date de la demande", "field": "date_demande"}, {"title": "Date de la demande", "field": "date_demande", responsive: 1},
{"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{ {"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{
allowEmpty : true , allowEmpty : true ,
}}, }},
@@ -24,7 +24,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
}}, }},
], ],
pagination: true, pagination: true,
paginationSize: 5 paginationSize: 5,
}) })
const bouton_demande_conges = $("bouton-demande-conge"); const bouton_demande_conges = $("bouton-demande-conge");

View File

@@ -7,6 +7,7 @@
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %}">{{message}}</div> <div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %}">{{message}}</div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<div class="row d-flex justify-content-center mb-4"> <div class="row d-flex justify-content-center mb-4">
<div class="col text-white bg-danger d-flex flex-column justify-content-center align-items-center border rounded p-4"> <div class="col text-white bg-danger d-flex flex-column justify-content-center align-items-center border rounded p-4">
<div class="card-header fw-bold"> <div class="card-header fw-bold">
@@ -32,11 +33,17 @@
<h5 class="fw-bold">{{ nombre_conges_valide }}</h5> <h5 class="fw-bold">{{ nombre_conges_valide }}</h5>
</div> </div>
</div> </div>
<div class="d-flex justify-content-between my-4"> </div>
<div class="row mb-3">
<div class="col d-flex justify-content-between align-items-center">
<h3><i class="bi bi-list-ul"></i> Liste des demandes de congé</h3> <h3><i class="bi bi-list-ul"></i> Liste des demandes de congé</h3>
<button class='btn btn-primary' id="bouton-demande-conge">Demande de congé</button> <button class='btn btn-primary' id="bouton-demande-conge">Demande de congé</button>
</div> </div>
<div class="table-responsive"> </div>
<div class="row">
<div class="col">
<div id="liste-demande-conges" data-url="{% url 'gestion_conges:liste-des-conges' %}"></div> <div id="liste-demande-conges" data-url="{% url 'gestion_conges:liste-des-conges' %}"></div>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.2.13 on 2026-04-17 12:03 # Generated by Django 5.2.13 on 2026-06-16 11:06
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings from django.conf import settings
@@ -27,7 +27,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('matricule', models.CharField(blank=True, max_length=10, null=True, unique=True)), ('matricule', models.CharField(blank=True, max_length=10, null=True, unique=True)),
('fonction', models.CharField(blank=True, choices=[('directeur', 'Directeur'), ('assistant_direction', 'Assistante de direction'), ('comptable', 'Comptable'), ('raf', 'RAF'), ('data_manager', 'Data Manager'), ('logisticien', 'Logisticien'), ('post_doctorant', 'Post-Doctorant'), ('qualiticien', 'Qualiticien'), ('technicien_surface', 'Technicien de surface'), ('chauffeur', 'Chauffeur')], max_length=50, null=True)), ('fonction', models.CharField(blank=True, choices=[('directeur', 'Directeur'), ('assistant_direction', 'Assistante de direction'), ('assistant_technique_recherche', 'Assistant technique de recherche'), ('comptable', 'Comptable'), ('raf', 'RAF'), ('data_manager', 'Data Manager'), ('logisticien', 'Logisticien'), ('post_doctorant', 'Post-Doctorant'), ('qualiticien', 'Qualiticien'), ('technicien_surface', 'Technicien de surface'), ('chauffeur', 'Chauffeur')], max_length=50, null=True)),
('date_embauche', models.DateField(blank=True, null=True)), ('date_embauche', models.DateField(blank=True, null=True)),
('adresse', models.CharField(blank=True, max_length=100, null=True)), ('adresse', models.CharField(blank=True, max_length=100, null=True)),
('telephone', models.CharField(blank=True, max_length=15, null=True)), ('telephone', models.CharField(blank=True, max_length=15, null=True)),
@@ -48,7 +48,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('numero_contrat', models.CharField(max_length=100, unique=True)), ('numero_contrat', models.CharField(max_length=100, unique=True)),
('type_contrat', models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage')], max_length=50)), ('type_contrat', models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage'), ('convention_bourse_entretien', "Convention de bourse d'entretien")], max_length=50)),
('date_debut', models.DateField()), ('date_debut', models.DateField()),
('date_fin', models.DateField(blank=True, null=True)), ('date_fin', models.DateField(blank=True, null=True)),
('salaire_mensuel', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), ('salaire_mensuel', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),

View File

@@ -1,18 +0,0 @@
# Generated by Django 6.0.4 on 2026-04-29 10:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gestion_employe', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='contrat',
name='type_contrat',
field=models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage'), ('convention_bourse_entretien', "Convention de bourse d'entretien")], max_length=50),
),
]

View File

@@ -12,6 +12,7 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
// ajaxURL: url_liste_employe, // ajaxURL: url_liste_employe,
pagination: true, pagination: true,
paginationSize: 10, paginationSize: 10,
responsiveLayout : "hide"
}) })
fetch(url_liste_employe) fetch(url_liste_employe)

View File

@@ -18,29 +18,34 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
}) })
const enregistrerProfil = $("enregistrerProfil"); const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => { // enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url; // const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value; // const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData(); // const formData = new FormData();
formData.append("nom", $("nom").value); // formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value); // formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value); // formData.append("email", $("email").value);
formData.append("telephone", $("telephone").value); // formData.append("telephone", $("telephone").value);
formData.append("adresse", $("adresse").value); // formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value); // formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value); // formData.append("date_naissance", $("date_naissance").value);
fetch(url, {
method: "POST", // console.log("---------------------------------------------")
headers: { // console.log("Données du formulaire:", url);
"X-CSRFToken": csrftoken // console.log("Données du formulaire:", formData);
},
body: formData // fetch(url, {
}) // method: "POST",
.then(response => response.json()) // headers: {
.then(data => alert(data.message)) // "X-CSRFToken": csrftoken
.catch(error => console.error("Erreur:", error)); // },
}); // body: formData
// })
// .then(response => response.json())
// .then(data => alert(data.message))
// .catch(error => console.error("Erreur:", error));
// });

View File

@@ -38,7 +38,7 @@
</h2> </h2>
<div id="collapseProfil" class="accordion-collapse" aria-labelledby="headingContrats" data-bs-parent="#accordionInformationEmploye"> <div id="collapseProfil" class="accordion-collapse" aria-labelledby="headingContrats" data-bs-parent="#accordionInformationEmploye">
<div class="accordion-body"> <div class="accordion-body">
<div class="row" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}"> <form class="row" action = "{% url 'gestion_employe:modifier-employe' %}" method = "POST" enctype = "multipart/form-data" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}">
{% csrf_token %} {% csrf_token %}
<div class="col"> <div class="col">
<div class="form-group mb-2"> <div class="form-group mb-2">
@@ -50,19 +50,19 @@
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Matricule :</label> <label>Matricule :</label>
<input type="text" class="form-control" id="matricule" value="{{ employe.matricule|default:'' }}" readonly> <input type="text" class="form-control" id="matricule" name = 'matricule' value="{{ employe.matricule|default:'' }}" readonly>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Nom :</label> <label>Nom :</label>
<input type="text" class="form-control" id="nom" value="{{ employe.user.last_name }}"> <input type="text" class="form-control" id="nom" name = 'nom' value="{{ employe.user.last_name }}">
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Prénom :</label> <label>Prénom :</label>
<input type="text" class="form-control" id="prenom" value="{{ employe.user.first_name }}"> <input type="text" class="form-control" id="prenom" name = 'prenom' value="{{ employe.user.first_name }}">
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Sexe :</label> <label>Sexe :</label>
<select id='sexe' class="form-select"> <select id='sexe' name = 'sexe' class="form-select">
<option value='h' {% if employe.sexe == 'h' %}selected{% endif %}>Homme</option> <option value='h' {% if employe.sexe == 'h' %}selected{% endif %}>Homme</option>
<option value='f' {% if employe.sexe == 'f' %}selected{% endif %}>Femme</option> <option value='f' {% if employe.sexe == 'f' %}selected{% endif %}>Femme</option>
</select> </select>
@@ -72,27 +72,27 @@
<div class="col"> <div class="col">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Date de naissance :</label> <label>Date de naissance :</label>
<input type="date" class="form-control" id="date_naissance" value="{{ employe.date_naissance|date:'Y-m-d' }}"> <input type="date" class="form-control" id="date_naissance" name = 'date_naissance' value="{{ employe.date_naissance|date:'Y-m-d' }}">
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Département :</label> <label>Département :</label>
<input type="text" class="form-control" id="departement" value="{{ employe.departement.nom|default:'' }}" readonly> <input type="text" class="form-control" id="departement" name = 'departement' value="{{ employe.departement.nom|default:'' }}" readonly>
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Email :</label> <label>Email :</label>
<input type="email" class="form-control" id="email" value="{{ employe.user.email|default:'' }}"> <input type="email" class="form-control" id="email" name = 'email' value="{{ employe.user.email|default:'' }}">
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Téléphone :</label> <label>Téléphone :</label>
<input type="text" class="form-control" id="telephone" value="{{ employe.telephone|default:'' }}"> <input type="text" class="form-control" id="telephone" name = 'telephone' value="{{ employe.telephone|default:'' }}">
</div> </div>
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Adresse :</label> <label>Adresse :</label>
<input type="text" class="form-control" id="adresse" value="{{ employe.adresse|default:'' }}"> <input type="text" class="form-control" id="adresse" name = 'adresse' value="{{ employe.adresse|default:'' }}">
</div> </div>
</div> </div>
</div> <button type="submit" class="btn btn-success d-block m-auto" id="enregistrerProfil">Enregistrer</button>
<button type="submit" class="btn btn-success d-block m-auto" id="enregistrerProfil">Enregistrer</button> </form>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -20,6 +20,9 @@ from fonction_utilitaire import fonctions_utilitaire
@login_required @login_required
def index(request): def index(request):
"""Vue d'index""" """Vue d'index"""
if not request.user.groups.filter(name="ressource_humaine").exists():
return redirect('gestion_employe:mon-profil')
employes = Employe.objects.all().order_by('-user__date_joined') employes = Employe.objects.all().order_by('-user__date_joined')
nombre_employes = Employe.objects.count() nombre_employes = Employe.objects.count()
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count() nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
@@ -94,7 +97,7 @@ def affecter_employe_projet(request):
elif total_affectation + temps_nouveau > 100: elif total_affectation + temps_nouveau > 100:
messages.warning( messages.warning(
request, request,
f"Les pourcentages d'affectation de l'employé {employe.first_name} {employe.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)." f"Les pourcentages d'affectation de l'employé {employe.user.first_name} {employe.user.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)."
) )
return redirect('gestion_employe:index') return redirect('gestion_employe:index')
@@ -191,25 +194,26 @@ def modifier_employer(request):
except Employe.DoesNotExist: except Employe.DoesNotExist:
return JsonResponse({"message": "Employé non trouvé."}) return JsonResponse({"message": "Employé non trouvé."})
if request.method == "POST": if request.method == "POST":
data = json.loads(request.body)
user = User.objects.get(username=request.user) user = User.objects.get(username=request.user)
user.last_name = data['nom'] user.last_name = request.POST['nom']
user.first_name = data['prenom'] user.first_name = request.POST['prenom']
user.email = data['email'] user.email = request.POST['email']
employe.telephone = data['telephone'] employe.telephone = request.POST['telephone']
employe.adresse = data['adresse'] employe.adresse = request.POST['adresse']
employe.sexe = data['sexe'] employe.sexe = request.POST['sexe']
if request.FILES.get("photo"): if request.FILES.get("photo"):
employe.photo = request.FILES["photo"] employe.photo = request.FILES["photo"]
if data['date_naissance']: if request.POST['date_naissance']:
difference = relativedelta(timezone.now().date(), datetime.strptime(data['date_naissance'], "%Y-%m-%d").date()) difference = relativedelta(timezone.now().date(), datetime.strptime(request.POST['date_naissance'], "%Y-%m-%d").date())
if difference.years >= 18: if difference.years >= 18:
employe.date_naissance = data['date_naissance'] employe.date_naissance = request.POST['date_naissance']
else: else:
return JsonResponse({"message": "Veuillez entrez une date de naissance correcte."}) messages.error(request, "Veuillez entrez une date de naissance correcte.")
employe.save() employe.save()
user.save() user.save()
return JsonResponse({"message": "Profil mis à jour avec succès."}) messages.success(request, "Profil mis à jour avec succès.")
return redirect("gestion_employe:mon-profil")
def enregistrement_document(request): def enregistrement_document(request):
"""Vue pour permettre à un utilisateur de télécharger et enregistrer des documents liés à son profil""" """Vue pour permettre à un utilisateur de télécharger et enregistrer des documents liés à son profil"""

View File

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

View File

@@ -15,6 +15,11 @@ const tableau_liste_activite = new Tabulator("#tableau-liste-activite", {
}) })
tableau_liste_activite.on("rowClick", function (row, rowData) { tableau_liste_activite.on("rowClick", function (row, rowData) {
const data = rowData.getData(); const data = rowData.getData();
console.log('=== ACTIVITÉ SÉLECTIONNÉE ===');
console.log('ID:', data.id);
console.log('Budget prévu:', data.budget_prevu);
console.log('Budget dépensé:', data.budget_depense);
$("idDetailActivite").value = data.id; $("idDetailActivite").value = data.id;
$("titreDetailActivite").value = data.titre; $("titreDetailActivite").value = data.titre;
$("descriptionDetailActivite").value = data.description; $("descriptionDetailActivite").value = data.description;
@@ -22,14 +27,79 @@ tableau_liste_activite.on("rowClick", function (row, rowData) {
$("date_finDetailActivite").value = data.date_fin; $("date_finDetailActivite").value = data.date_fin;
$("statutDetailActivite").value = data.statut; $("statutDetailActivite").value = data.statut;
$("budget_prevuDetailActivite").value = data.budget_prevu; $("budget_prevuDetailActivite").value = data.budget_prevu;
$("budget_depenseDetailActivite").value = data.budget_depense || 0;
// Vérifier si l'activité est annulée
const isAnnulee = data.statut && data.statut.toLowerCase().includes('annul');
console.log('Activité annulée?', isAnnulee);
// Masquer/Afficher les boutons d'action selon le statut
const btnAnnuler = $("btnAnnulerActivite");
const btnMiseAJour = $("btnMiseAJourDepense");
const btnAjouterLivrable = $("btnOuvrirAjouterLivrable");
if (isAnnulee) {
// Activité annulée : masquer les boutons
if (btnAnnuler) btnAnnuler.style.display = "none";
if (btnMiseAJour) btnMiseAJour.style.display = "none";
if (btnAjouterLivrable) btnAjouterLivrable.style.display = "none";
console.log('✓ Boutons masqués - activité annulée');
} else {
// Activité active : afficher les boutons
if (btnAnnuler) btnAnnuler.style.display = "block";
if (btnMiseAJour) btnMiseAJour.style.display = "block";
if (btnAjouterLivrable) btnAjouterLivrable.style.display = "block";
console.log('✓ Boutons affichés - activité active');
}
// Vérifier si les budgets sont différents
const budgetPrevu = parseFloat(data.budget_prevu) || 0;
const budgetDepense = parseFloat(data.budget_depense) || 0;
const isDifferent = budgetDepense !== budgetPrevu;
console.log('Budget prévu:', budgetPrevu);
console.log('Budget dépensé:', budgetDepense);
console.log('Sont-ils différents?', isDifferent);
// Masquer/Afficher le conteneur du motif
const motifContainer = $("motif_container");
if (motifContainer) {
if (isDifferent) {
motifContainer.style.display = "block";
$("motif_changement_budgetDetailActivite").value = data.motif_changement_budget || "";
console.log('✓ Motif AFFICHÉ - budgets différents');
} else {
motifContainer.style.display = "none";
$("motif_changement_budgetDetailActivite").value = "";
console.log('✓ Motif MASQUÉ - budgets identiques');
}
} else {
console.error('❌ Élément motif_container introuvable!');
}
$("besoin_ressources_materiellesDetailActivite").value = data.besoin_ressource_materielle; $("besoin_ressources_materiellesDetailActivite").value = data.besoin_ressource_materielle;
const modal = new bootstrap.Modal($("modalDetailActivite")); const modal = new bootstrap.Modal($("modalDetailActivite"));
modal.show(); modal.show();
fetch(`liste-des-livrables/${data.id}/`) const url_livrables = `/gestion-projet/activite/liste-des-livrables/${data.id}/`;
.then(response => response.json()) console.log('URL des livrables:', url_livrables);
fetch(url_livrables)
.then(response => {
console.log('Réponse status:', response.status);
return response.json();
})
.then(livrables => { .then(livrables => {
console.log('Livrables reçus:', livrables);
console.log('Nombre de livrables:', livrables.length);
tableau_liste_livrable.setData(livrables); tableau_liste_livrable.setData(livrables);
$("nombreLivrables").textContent = livrables.length;
console.log('Tableau mis à jour avec', livrables.length, 'livrables');
})
.catch(error => {
console.error(' Erreur lors du chargement des livrables:', error);
tableau_liste_livrable.setData([]);
$("nombreLivrables").textContent = 0;
}) })
}) })
@@ -40,6 +110,7 @@ const tableau_liste_livrable = new Tabulator("#listeLivrables", {
], ],
placeholder: "Aucun livrable trouvé", placeholder: "Aucun livrable trouvé",
}) })
console.log('✓ Tableau des livrables initialisé');
$("btnMiseAJourDepense").addEventListener("click", function() { $("btnMiseAJourDepense").addEventListener("click", function() {
const modal = new bootstrap.Modal($("modalDepenseActivite")); const modal = new bootstrap.Modal($("modalDepenseActivite"));
@@ -56,4 +127,5 @@ $("btnAnnulerActivite").addEventListener("click", function(event) {
new bootstrap.Modal($("modalAnnulerActivite")).show(); new bootstrap.Modal($("modalAnnulerActivite")).show();
$("id_activite_annulation").value = $("idDetailActivite").value; $("id_activite_annulation").value = $("idDetailActivite").value;
bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide(); bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
}) })

View File

@@ -1,58 +1,111 @@
<div class="modal fade" id="modalDetailActivite" tabindex="-1"> <div class="modal fade" id="modalDetailActivite" tabindex="-1"
<div class="modal-dialog"> aria-labelledby="modalDetailActiviteLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header bg-orange-dark"> <div class="modal-header bg-orange-dark text-black">
<h5 class="modal-title">Détails de l'activité</h5> <h5 class="modal-title" id="modalDetailActiviteLabel">
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <i class="bi bi-clipboard-check me-2"></i>
Détails de l'activité
</h5>
<button type="button"class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Fermer">
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body" data-url-livrables="{% url 'gestion_projet:liste-livrables-activite' activite_id=0 %}">
<input type="hidden" id="idDetailActivite">
<div class="row g-3"> <div class="row g-3">
<div class=""> <div class="col-12">
<input type="hidden" id="idDetailActivite"> <label for="titreDetailActivite" class="form-label fw-bold">
<div class="form-group mb-2"> Titre :
<label>Titre :</label> </label>
<input type="text" class="form-control" id="titreDetailActivite"> <input type="text"class="form-control" id="titreDetailActivite"readonly>
</div>
<div class="col-12">
<label for="descriptionDetailActivite" class="form-label fw-bold">
Description :
</label>
<textarea class="form-control"id="descriptionDetailActivite" rows="3" readonly></textarea>
</div>
<div class="col-md-6">
<label for="date_debutDetailActivite"class="form-label fw-bold">
Date de début :
</label>
<input type="date"class="form-control"id="date_debutDetailActivite"readonly>
</div>
<div class="col-md-6">
<label for="date_finDetailActivite"class="form-label fw-bold">
Date de fin :
</label>
<input type="date"class="form-control"id="date_finDetailActivite"readonly>
</div>
<div class="col-md-6">
<label for="statutDetailActivite" class="form-label fw-bold">
Statut :
</label>
<input type="text"class="form-control"id="statutDetailActivite"readonly>
</div>
<div class="col-md-6">
<label for="budget_prevuDetailActivite"class="form-label fw-bold">
Budget prévu :
</label>
<input type="number"class="form-control" id="budget_prevuDetailActivite"readonly>
</div>
<div class="col-md-6">
<label for="budget_depenseDetailActivite"class="form-label fw-bold">
Montant dépensé (GNF) :
</label>
<input type="number"class="form-control" id="budget_depenseDetailActivite"readonly>
</div>
<div class="col-12" id="motif_container">
<label for="motif_changement_budgetDetailActivite"class="form-label fw-bold">
Motif de différence :
</label>
<textarea class="form-control"id="motif_changement_budgetDetailActivite"rows="2"readonly></textarea>
</div>
<div class="col-12">
<label for="besoin_ressources_materiellesDetailActivite"class="form-label fw-bold">
Besoin de ressources matérielles :
</label>
<textarea class="form-control"id="besoin_ressources_materiellesDetailActivite"rows="3"readonly></textarea>
</div>
<div class="col-12 mt-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0 fw-bold">
<i class="bi bi-box-seam me-2"></i>
Liste des livrables
</h5>
<span class="badge bg-primary" id="nombreLivrables">
0
</span>
</div> </div>
<div class="form-group mb-2">
<label>Description :</label>
<textarea class="form-control" id="descriptionDetailActivite"></textarea>
</div>
<div class="form-group mb-2">
<label>Date de début :</label>
<input type="date" class="form-control" id="date_debutDetailActivite">
</div>
<div class="form-group mb-2">
<label>Date de fin :</label>
<input type="date" class="form-control" id="date_finDetailActivite">
</div>
<div class="form-group mb-2">
<label>Statut:</label>
<input type="text" class="form-control" id="statutDetailActivite">
</div>
<div class="form-group mb-2">
<label>Budget prévu :</label>
<input type="number" class="form-control" id="budget_prevuDetailActivite">
</div>
<div class="form-group mb-2">
<label>Besoin de ressources matérielles:</label>
<textarea class="form-control" id="besoin_ressources_materiellesDetailActivite"></textarea>
</div>
<p><strong>Liste des livrables :</strong></p>
<div id="listeLivrables"></div> <div id="listeLivrables"></div>
<button class="btn btn-danger d-block mx-auto" id="btnAnnulerActivite"> </div>
<div class="col-12 mt-3 text-center">
<button type="button"class="btn btn-danger"id="btnAnnulerActivite">
<i class="bi bi-x-circle me-2"></i>
Annuler cette activité Annuler cette activité
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-success" id="btnMiseAJourDepense"> <button type="button"class="btn btn-success"id="btnMiseAJourDepense">
<i class="bi bi-plus-circle"></i> Mise à jour du budget <i class="bi bi-cash-stack me-2"></i>
Mise à jour du budget
</button> </button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjouterLivrable"> <button type="button"class="btn btn-primary"id="btnOuvrirAjouterLivrable"data-bs-toggle="modal"
<i class="bi bi-plus-circle"></i> Ajouter un livrable data-bs-target="#modalAjouterLivrable">
<i class="bi bi-plus-circle me-2"></i>
Ajouter un livrable
</button>
<button type="button" class="btn btn-secondary"data-bs-dismiss="modal">
Fermer
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -381,15 +381,32 @@ def liste_activites_projet(request):
@login_required @login_required
def liste_livrables_activite(request, activite_id): def liste_livrables_activite(request, activite_id):
"""Vue pour retourner la liste des livrables attendus d'une activité spécifique""" """Vue pour retourner la liste des livrables attendus d'une activité spécifique"""
livrables = LivrablesLivres.objects.filter(activite__id=activite_id) try:
data = [] livrables = LivrablesLivres.objects.filter(activite__id=activite_id)
for livrable in livrables: data = []
print(livrable.fichier.url)
data.append({ for livrable in livrables:
"titre": livrable.nom, lien = ""
"lien": livrable.fichier.url if livrable.fichier else "", if livrable.fichier:
}) try:
return JsonResponse(data, safe=False) lien = livrable.fichier.url
except Exception as e:
print(f"[DEBUG] Erreur accès URL fichier {livrable.nom}: {e}")
lien = ""
data.append({
"titre": livrable.nom,
"lien": lien,
})
print(f"[DEBUG] ✓ Livrables activité {activite_id}: {len(data)} trouvés")
return JsonResponse(data, safe=False)
except Exception as e:
print(f"[DEBUG] Erreur liste_livrables_activite: {e}")
import traceback
traceback.print_exc()
return JsonResponse({"error": f"Erreur serveur: {str(e)}"}, status=500)
@login_required @login_required
def mises_a_jour_depense_activite(request): def mises_a_jour_depense_activite(request):

View File

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

View File

@@ -10,6 +10,7 @@ numpy==2.2.6
pandas==2.3.3 pandas==2.3.3
pillow==12.2.0 pillow==12.2.0
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
python-decouple==3.8
pytz==2026.1.post1 pytz==2026.1.post1
requests==2.33.1 requests==2.33.1
six==1.17.0 six==1.17.0
@@ -17,5 +18,5 @@ sqlparse==0.5.5
typing_extensions==4.15.0 typing_extensions==4.15.0
tzdata==2026.2 tzdata==2026.2
urllib3==2.6.3 urllib3==2.6.3
python-decouple webservices==0.7
gunicorn webservices.naudit==0.7.2

View File

@@ -3,7 +3,7 @@ const $ = (element) => document.getElementById(element);
const url_liste_conge_attente = $("liste-demande-conges").dataset.url const url_liste_conge_attente = $("liste-demande-conges").dataset.url
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
layout : "fitColumns", // layout : "fitColumns",
columns: [ columns: [
{"title": "Nom et Prénom", "field": "prenom_nom"}, {"title": "Nom et Prénom", "field": "prenom_nom"},
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{ {"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
@@ -15,7 +15,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
outputFormat:"dd/MM/yy", outputFormat:"dd/MM/yy",
}}, }},
{"title": "Type de congé", "field": "type"}, {"title": "Type de congé", "field": "type"},
{"title": "Date de la demande", "field": "date_demande"}, {"title": "Date de la demande", "field": "date_demande", responsive: 1},
{"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{ {"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{
allowEmpty : true , allowEmpty : true ,
}}, }},
@@ -24,7 +24,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
}}, }},
], ],
pagination: true, pagination: true,
paginationSize: 5 paginationSize: 5,
}) })
const bouton_demande_conges = $("bouton-demande-conge"); const bouton_demande_conges = $("bouton-demande-conge");

View File

@@ -12,6 +12,7 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
// ajaxURL: url_liste_employe, // ajaxURL: url_liste_employe,
pagination: true, pagination: true,
paginationSize: 10, paginationSize: 10,
responsiveLayout : "hide"
}) })
fetch(url_liste_employe) fetch(url_liste_employe)

View File

@@ -16,27 +16,31 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
], ],
ajaxURL: url_certificat, ajaxURL: url_certificat,
}) })
const enregistrerProfil = $("enregistrerProfil"); const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => { enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url; const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value; const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData();
formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value);
formData.append("telephone", $("telephone").value);
formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value);
fetch(url, { fetch(url, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json', "X-CSRFToken": csrftoken
'X-CSRFToken': csrftoken
}, },
body: JSON.stringify({ body: formData
"nom": $("nom").value,
"prenom": $("prenom").value,
"email": $("email").value,
"telephone": $("telephone").value,
"adresse": $("adresse").value,
"sexe": $("sexe").value,
"date_naissance": $("date_naissance").value,
})
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => alert(data.message)) .then(data => alert(data.message))
}) .catch(error => console.error("Erreur:", error));
});

View File

@@ -1,4 +1,5 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur'); const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
let table;
btnEnregistrerBailleur.addEventListener('click', function() { btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur'); const form = document.getElementById('formBailleur');
@@ -20,4 +21,31 @@ btnEnregistrerBailleur.addEventListener('click', function() {
alert('Ce bailleur existe déjà dans la base de données.'); alert('Ce bailleur existe déjà dans la base de données.');
} }
}); });
}); });
document.addEventListener("DOMContentLoaded", function () {
table = new Tabulator("#table-bailleurs", {
ajaxURL: "/gestion-projet/bailleurs/",
layout: "fitColumns",
pagination: "local",
paginationSize: 5,
columns: [
{title: "#", formatter: "rownum", width: 60},
{title: "Organisme", field: "nom_organisme"},
{title: "Contact", field: "contact"},
{title: "Email", field: "email"},
{title: "Pays", field: "pays"},
],
rowDblClick: function(e, row) {
const data = row.getData();
if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) {
supprimerBailleur(data.id);
}
}
});
});

View File

@@ -15,7 +15,6 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
currentReservationId = data.id_reservation; currentReservationId = data.id_reservation;
console.log(data);
$("id_reservation_detail").value = data.id_reservation; $("id_reservation_detail").value = data.id_reservation;
$("id_reservation_refus").value = data.id_reservation; $("id_reservation_refus").value = data.id_reservation;
$("id_reservation_zoom").value = data.id_reservation; $("id_reservation_zoom").value = data.id_reservation;
@@ -30,11 +29,10 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("besoin_ordinateur").checked=data.besoin_ordinateur; $("besoin_ordinateur").checked=data.besoin_ordinateur;
$("lien_zoom").value=data.lien_zoom; $("lien_zoom").value=data.lien_zoom;
if(data.statut !== "annulee"){ if (data.besoin_zoom === false){
$("motif_refus_container").className = "d-none"; $("lien_zoom_container").className = "d-none";
}else{
$("motif_refus").value=data.motif_refus;
} }
}) })
} }
}); });
@@ -80,6 +78,11 @@ $("bouton-annuler").addEventListener("click", (e) => {
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value; const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url_annuler = $("formulaire-details").dataset.urlannuler; const url_annuler = $("formulaire-details").dataset.urlannuler;
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault();
fetch( fetch(
url_annuler, url_annuler,
{ {
@@ -130,6 +133,9 @@ if($("refuserReservation")){
const url = e.currentTarget.dataset.lienrefus; const url = e.currentTarget.dataset.lienrefus;
const idRes = $("id_reservation_detail").value; const idRes = $("id_reservation_detail").value;
console.log(idRes);
e.preventDefault();
fetch(url, { fetch(url, {
method: "POST", method: "POST",
headers: { headers: {
@@ -161,16 +167,11 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
tableau_reservation_attente.on("rowClick", (row, rowData) => { tableau_reservation_attente.on("rowClick", (row, rowData) => {
const data = rowData.getData(); const data = rowData.getData();
console.log(data);
if(data.besoin_zoom === false){ if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none'; $("lien_zoom_container").className = 'd-none';
} }
if(data.statut !== "refusee"){ const id_user = $("current-user-id").dataset.userid;
$("motif_refus_container").className = 'd-none';
}
$("id_reservation_detail").value = data.id; $("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id; $("id_reservation_refus").value = data.id;
$("id_reservation_zoom").value = data.id; $("id_reservation_zoom").value = data.id;
@@ -178,16 +179,20 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe; $("employe").value=data.employe;
$("salle").value=data.salle; $("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut; $("statut-reservation").innerHTML=data.statut;
$("date_evenement").value=data.date_debut; $("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin;
$("heure_debut").value=data.heure_debut; $("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin; $("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation; $("motif_reservation").value=data.motif_reservation;
$("besoin_zoom").checked=data.besoin_zoom; $("besoin_zoom").checked=data.besoin_zoom;
$("besoin_ordinateur").checked=data.besoin_ordi; $("besoin_ordinateur").checked=data.besoin_ordi;
$("lien_zoom").value=data.lien_zoom; $("lien_zoom").value=data.lien_zoom;
$("motif_refus").value=data.motif_refus;
if (id_user != data.employe_id){
$("bouton-annuler").className = "d-none";
}
const modal = new bootstrap.Modal($("modalDetailReservation")); const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide(); bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
modal.show(); modal.show();
}) })