47 Commits

Author SHA1 Message Date
34a261a4af Prise en charge de la photo de profil
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-06 12:25:38 +00:00
f0e8b025a4 Prise en charge de la photo de profil
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-05-06 11:51:55 +00:00
62b2938c71 Prise en charge de la photo de profil
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-05-06 11:43:54 +00:00
06125b0900 Prise en charge de la photo de profil
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-05-06 11:31:49 +00:00
0047b1f91c Ajout de la prise en charge des photos de profil 2026-05-06 08:50:49 +00:00
7ee14e7b3f clean: remove pycache from tracking 2026-05-06 08:42:28 +00:00
d12b014b20 feature: ajout photo de profil 2026-05-05 15:57:35 +00:00
d9b45ac364 Merge pull request 'Finalisation du Jenkinsfile' (#37) from finalisation into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #37
2026-05-04 13:45:46 +00:00
44faeac222 Finalisation du Jenkinsfile
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-05-04 13:44:41 +00:00
e1436e8f4e Merge pull request 'Deploiement automatique' (#36) from env2 into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #36
2026-05-04 13:16:38 +00:00
4ee7dd9fc7 Deploiement automatique
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-05-04 13:15:38 +00:00
a9e708c778 Merge pull request 'Ajout de dependance' (#35) from env2 into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #35
2026-05-04 13:04:03 +00:00
e427561cc1 Ajout de dependance
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-05-04 13:01:17 +00:00
78fafebc4d Merge pull request 'env1' (#34) from env1 into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #34
2026-05-04 12:39:23 +00:00
ecdaa9f9f9 Config des environnement
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-05-04 12:38:33 +00:00
9951719b26 Config env 2026-05-04 12:22:15 +00:00
1391a5ea3b Derniere modification du Jenkinsfile
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
2026-05-04 09:58:34 +00:00
357b50dfa4 Merge pull request 'utilisation des credentials' (#32) from pipeline10 into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #32
2026-05-04 09:52:26 +00:00
bda80fae85 utilisation des credentials
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-05-04 09:51:25 +00:00
9c1eb543ff Merge pull request 'pipeline9' (#31) from pipeline9 into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #31
2026-04-30 16:07:01 +00:00
f55b1a6f2d pipeline9
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head This commit looks good
2026-04-30 16:06:21 +00:00
a6412341cf Merge pull request 'pipeline 8' (#30) from pipeline8 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #30
2026-04-30 15:59:17 +00:00
ab2d8b479c pipeline 8
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
2026-04-30 15:57:54 +00:00
8f412cb031 Merge pull request 'pipeline 7' (#29) from pipeline7 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #29
2026-04-30 15:44:04 +00:00
69764b74a2 pipeline 7
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head Build started...
2026-04-30 15:43:00 +00:00
e1dc510af1 Merge pull request 'pipeline 6' (#28) from pipeline6 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #28
2026-04-30 15:28:19 +00:00
fa21850c6b pipeline 6
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-04-30 15:27:29 +00:00
4a8cbf02b9 Merge pull request 'pipeline 5' (#27) from pipeline5 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #27
2026-04-30 15:21:29 +00:00
798784a163 pipeline 5
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head This commit looks good
2026-04-30 15:21:02 +00:00
86ad99de76 Merge pull request 'pipeline 4' (#25) from pipeline4 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #25
2026-04-30 15:09:54 +00:00
5680677865 pipeline 4
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head Build started...
2026-04-30 15:09:22 +00:00
886c3246af Merge pull request 'pipeline3' (#24) from pipeline3 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #24
2026-04-30 15:03:16 +00:00
7b66250832 pipeline3
Some checks are pending
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main Build queued...
2026-04-30 15:01:54 +00:00
80b3573674 Merge pull request 'pipeline 2' (#23) from pipeline2 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #23
2026-04-30 14:56:41 +00:00
efadd66483 pipeline 2
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head Build started...
2026-04-30 14:56:06 +00:00
c9431063de Merge pull request 'Pipeline 1' (#22) from pipeline1 into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #22
2026-04-30 14:51:42 +00:00
15c33efc14 Pipeline 1
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Organisation/sirh/pipeline/pr-main Build queued...
2026-04-30 14:50:58 +00:00
5257901e75 Merge pull request 'Test pipeline' (#21) from jenkins_agent_exemple into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #21
2026-04-30 14:47:05 +00:00
b3294a823e Test pipeline
Some checks failed
Organisation/sirh/pipeline/pr-main There was a failure building this commit
2026-04-30 14:45:35 +00:00
2a182830a6 Merge pull request 'Ajout de l'agent' (#20) from jenkins_agent into main
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Reviewed-on: #20
2026-04-30 13:18:30 +00:00
14631c3744 Ajout de l'agent
Some checks are pending
Organisation/sirh/pipeline/pr-main Build queued...
Organisation/sirh/pipeline/head This commit looks good
2026-04-30 13:17:43 +00:00
f0894bb66f Merge pull request 'jenkins test 3' (#18) from jenkins into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #18
2026-04-30 13:12:06 +00:00
75285a140a jenkins test 3
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Organisation/sirh/pipeline/pr-main There was a failure building this commit
2026-04-30 13:11:14 +00:00
3cc8e292b3 Merge pull request 'renommage jenkinsfile' (#16) from test_jenkins into main
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Reviewed-on: #16
2026-04-30 13:02:50 +00:00
34d1464391 renommage jenkinsfile
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
Organisation/sirh/pipeline/pr-main There was a failure building this commit
2026-04-30 13:00:26 +00:00
6c12131fab Merge pull request 'Test jenkins 1' (#15) from test_jenkins into main
Reviewed-on: #15
2026-04-30 12:48:13 +00:00
19e4675f32 Test jenkins 1 2026-04-30 12:47:18 +00:00
17 changed files with 252 additions and 546 deletions

10
.gitignore vendored
View File

@@ -1,10 +1,6 @@
# db.sqlite3
# venv/*
# media/*
__pycache__/
*.pyc
db.sqlite3
venv/
media/
staticfiles/
.env
migrations/

50
Jenkinsfile vendored Normal file
View File

@@ -0,0 +1,50 @@
pipeline
{
agent any
options {
// This is required if you want to clean before build
skipDefaultCheckout(true)
}
environment
{
SUDO_PASSWORD = credentials('sudo-password')
}
stages
{
stage ( 'checkout' )
{
steps
{
sh 'echo "Debut du pipeline"'
cleanWs()
checkout scm
}
}
stage ( 'Deploiement' )
{
when { branch 'main' }
steps {
sh '''
cd /var/www/sirh
echo $SUDO_PASSWORD | sudo -S chown -R jenkins:jenkins /var/www/sirh
git fetch origin main
git reset --hard origin/main
python3 -m venv venv
. venv/bin/activate
pip install -r requirements.txt
python manage.py makemigrations
python manage.py migrate
echo $SUDO_PASSWORD | sudo -S chown -R www-data:www-data /var/www/sirh
echo "Deploiement reussi"
echo $SUDO_PASSWORD | sudo -S supervisorctl restart sirh
'''
}
}
}
}

View File

@@ -12,21 +12,21 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
import os
from pathlib import Path
from decouple import config
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure--wdb9t(77rvyac$_q!n5gw86&0r(0&&j171v9h!-_$jahsza*5'
SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = ["https://support.cerfig.org", "support.cerfig.org"]
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',')
# Application definition
@@ -79,24 +79,25 @@ WSGI_APPLICATION = 'SIRH.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.mysql',
# 'NAME': 'sirh',
# 'USER': 'sirh',
# 'PASSWORD': 'sirh-cerfig',
# 'HOST': 'localhost',
# 'PORT': '3306',
# }
# }
DATABASES = {
if config('ENVIRONMENT') == 'local':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
}
else:
DATABASES = {
'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
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators

View File

@@ -6,13 +6,13 @@
{% if user.employe.photo %}
<img src="{{ user.employe.photo.url }}"
class="rounded-circle"
width="80"
height="80"
width="100"
height="100"
style="object-fit:cover;">
{% else %}
<i class="bi bi-person-circle text-white" style="font-size:60px;"></i>
{% endif %}
<div class="text-white mt-2">
<div class="text-white mt-2 fw-bold fs-5">
{{ user.username }}
</div>
</div>
@@ -41,15 +41,6 @@
<a href="{% url 'gestion_salle:reservation-salle' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-calendar-check"></i> Réservation
</a>
{% comment %} <a href="{% url 'rapport-rh' %}" class="text-white fw-bold text-decoration-none mb-2" style="font-size:1.2em">
<i class="bi bi-graph-up"></i> Rapports et Statistiques
</a> {% endcomment %}
{% comment %} <a href="" class="text-white fw-bold text-decoration-none mb-2" style="font-size:1.2em">
<i class="bi bi-person-gear"></i> Gestion des Utilisateurs
</a> {% endcomment %}
{% comment %} <a href="{% url 'gestion_employe:departement' %}" class="text-white fw-bold text-decoration-none mb-2" style="font-size:1.2em">
<i class="bi bi-gear"></i> Paramètres
</a> {% endcomment %}
<a href="{% url 'deconnexion' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-box-arrow-right"></i> Déconnexion
</a>

View File

@@ -129,58 +129,55 @@ def demander_conge(request):
@login_required
def liste_demande_conges(request):
"""Vue de liste des demandes de congés en attente de validation selon le statut de l'utilisateur actuel"""
try:
employe = Employe.objects.get(user=request.user)
employe = Employe.objects.get(user__username = request.user)
except Employe.DoesNotExist:
return JsonResponse({
"success": False,
"message": "Profil employé introuvable"
"message": "Votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'administrateur."
})
affectation = Affectation.objects.filter(
try:
affectation = Affectation.objects.get(
employe=employe,
date_fin_daffectation__gte=timezone.now().date()
).first()
is_direction = employe.user.groups.filter(name='direction').exists()
)
except Affectation.DoesNotExist:
affectation = None
if employe.chef:
conges = Conge.objects.filter(
Q(employe__departement=employe.departement) |
Q(employe=employe)
print("chef")
conges_en_attente = Conge.objects.filter(
employe__departement = employe.departement,
validation_hierarchique = None
).order_by('-date_demande')
elif affectation and affectation.role == "chef_projet":
employes_du_projet = Affectation.objects.filter(
projet=affectation.projet,
date_fin_daffectation__gte=timezone.now().date()
).values_list('employe', flat=True)
projet = affectation.projet,
date_fin_daffectation__gte = timezone.now().date()
).values('employe')
conges = Conge.objects.filter(
Q(employe__in=employes_du_projet) |
Q(employe=employe)
conges_en_attente = Conge.objects.filter(
employe__in = employes_du_projet,
validation_hierarchique = None
).order_by('-date_demande')
elif is_direction:
conges = Conge.objects.filter(
Q(validation_hierarchique=True) |
Q(employe__user__groups__name='direction')
).distinct().order_by('-date_demande')
elif 'direction' in employe.user.groups.values_list('name', flat=True):
conges_en_attente = Conge.objects.filter(
validation_hierarchique = True,
validation_direction = None
).order_by('-date_demande')
else:
conges = Conge.objects.filter(
employe=employe
conges_en_attente = Conge.objects.filter(
employe__user__username = request.user
).order_by('-date_demande')
return JsonResponse({
"success": True,
"data": [
"data":[
{
**model_to_dict(conge),
"prenom_nom": f"{conge.employe.user.first_name} {conge.employe.user.last_name}",
@@ -189,9 +186,9 @@ def liste_demande_conges(request):
"type": dict(conge.TYPE_CHOICES).get(conge.type),
"solde_conge": fonctions_utilitaire.solde_conge(conge.employe)["quota_annuel"]
}
for conge in conges
]
})
for conge in conges_en_attente]},
safe=False
)
@login_required
def validation_de_conge(request):

View File

@@ -15,6 +15,7 @@ class Employe(models.Model):
FONCTION_LISTE = [
('directeur', 'Directeur'),
('assistant_direction', 'Assistante de direction'),
('assistant_technique_recherche', 'Assistant technique de recherche'),
('comptable', 'Comptable'),
('raf', 'RAF'),
('data_manager', 'Data Manager'),

View File

@@ -19,13 +19,12 @@
{% endfor %}
{% endif %}
{% if not has_contrat %}
<div class="alert alert-danger mt-2">
{% if not expiration_contrat %}
<div class="alert alert-danger fade show alert-dismissible mt-2">
<strong>Important :</strong> Les informations sur votre contrat n'ont pas été renseignées, veuillez contacter les ressources humaines.
</div>
{% elif expiration_contrat %}
<div class="alert alert-warning mt-2">
{% elif contrat_nb_jours_restant %}
<div class="alert alert-danger fade show alert-dismissible mt-2">
<strong>Important :</strong> Votre contrat de travail expire dans {{ contrat_nb_jours_restant }} jours, veuillez contacter les ressources humaines.
</div>
{% endif %}
@@ -43,7 +42,7 @@
{% csrf_token %}
<div class="col">
<div class="form-group mb-2">
<label>photo</label>
<label>Photo</label>
{% if employe.photo %}
<span>Fichier actuel : <a href="{{ employe.photo.url }}">{{employe.photo}}</a></span>
{% endif %}

View File

@@ -79,21 +79,6 @@ urlpatterns = [
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,

View File

@@ -1,5 +1,5 @@
import json
from datetime import date, timedelta, datetime
from datetime import timedelta, datetime
from dateutil.relativedelta import relativedelta
from django.utils import timezone
@@ -122,41 +122,26 @@ def mon_profil(request):
try:
employe = Employe.objects.get(user__username=request.user)
except Employe.DoesNotExist:
messages.error(
request,
"Impossible d'acceder au menu 'Mon profil' car votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'Administrateur."
)
messages.error(request, "Impossible d'acceder au menu 'Mon profil' car votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'Administrateur.")
return redirect("gestion_conges:conge")
contrats = Contrat.objects.filter(employe=employe, statut='actif').first()
projets = Affectation.objects.filter(
employe=employe,
date_fin_daffectation__gte=timezone.now().date()
employe = employe,
date_fin_daffectation__gte = timezone.now().date()
).select_related('projet')
has_contrat = contrats is not None
expiration_contrat = False
contrat_nb_jours_restant = None
if contrats:
nb_jours = contrats.nombre_jours_restant
contrat_nb_jours_restant = nb_jours
expiration_contrat = nb_jours <= fonctions_utilitaire.DUREE_FIN_CONTRAT
return render(
request,
'gestion_employe/monprofil.html',
{
'employe': employe,
'contrats': [{
**model_to_dict(contrats),
"type_contrat": dict(Contrat.TYPE_CONTRAT).get(contrats.type_contrat),
"statut": dict(Contrat.STATUT_CONTRAT).get(contrats.statut),
"fichier_contrat": contrats.fichier_contrat.url if contrats.fichier_contrat else "",
}] if contrats else [],
} if contrats else []],
'projets': [
{
**model_to_dict(a.projet),
@@ -166,13 +151,12 @@ def mon_profil(request):
"pourcentage_temps_affectation": a.pourcentage_temps_affectation
} for a in projets
],
"formation_form": FormationForm(),
"has_contrat": has_contrat,
"expiration_contrat": expiration_contrat,
"contrat_nb_jours_restant": contrat_nb_jours_restant
"expiration_contrat": contrats.nombre_jours_restant <= fonctions_utilitaire.DUREE_FIN_CONTRAT if contrats else False,
"contrat_nb_jours_restant": contrats.nombre_jours_restant if contrats else None
}
)
@login_required
def modifier_mot_passe(request):
"""Vue pour permettre à un utilisateur de modifier son mot de passe et ses informations de profil"""
@@ -192,6 +176,7 @@ def modifier_mot_passe(request):
messages.success(request, "Mot de passe modifié avec succès.")
return redirect("gestion_employe:mon-profil")
def modifier_employer(request):
"""Vue pour permettre à un utilisateur de modifier les informations d'un employé"""
try:
@@ -220,13 +205,19 @@ def modifier_employer(request):
return JsonResponse({"message": "Profil mis à jour avec succès."})
def enregistrement_document(request):
"""Vue pour permettre à un utilisateur de télécharger et enregistrer des documents liés à son profil"""
employe = Employe.objects.get(user=request.user)
if request.method == "POST":
if request.FILES.get("photo"):employe.photo = request.FILES["photo"]
if "cv" in request.FILES:employe.CV = request.FILES["cv"]
if "diplome" in request.FILES: employe.diplome = request.FILES["diplome"]
if "rib" in request.FILES: employe.rib = request.FILES["rib"]
if "casier_judiciaire" in request.FILES:employe.casier_judiciaire = request.FILES["casier_judiciaire"]
if request.FILES.get("photo"):
employe.photo = request.FILES["photo"]
if "cv" in request.FILES:
employe.CV = request.FILES["cv"]
if "diplome" in request.FILES:
employe.diplome = request.FILES["diplome"]
if "rib" in request.FILES:
employe.rib = request.FILES["rib"]
if "casier_judiciaire" in request.FILES:
employe.casier_judiciaire = request.FILES["casier_judiciaire"]
employe.save()
messages.success(request, "Documents enregistrés avec succès.")
@@ -256,25 +247,14 @@ def suppression_affectation(request):
return JsonResponse({"message": "Affectation supprimée avec succès."})
def creation_contrat(request):
"""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é"""
try:
employe = Employe.objects.get(id=request.POST.get('employe_id'))
except Employe.DoesNotExist:
messages.error(request, "Employé non trouvé.")
return redirect('employe-index')
contrat_actif = Contrat.objects.filter(
employe=employe,
date_fin__gte=date.today()
).exists()
if request.method == "POST":
if contrat_actif:
messages.error(
request,
"Impossible de créer un contrat : cet employé a déjà un contrat actif."
)
return redirect('gestion_employe:index')
form = ContratForm(request.POST, request.FILES)
if form.is_valid():
contrat = form.save(commit=False)
@@ -283,13 +263,9 @@ def creation_contrat(request):
messages.success(request, "Contrat créé avec succès.")
return redirect('gestion_employe:index')
messages.error(request, "Formulaire non valide")
else:
form = ContratForm(initial={'employe': employe})
return render(request, 'gestion_employe/index.html', {
'contrat_form': form
})
return render(request, 'gestion_employe/index.html', {'contrat_form': form})
@login_required
def enregistrer_detail_employe(request):
@@ -438,41 +414,3 @@ def supprimer_formation(request, id_formation):
formation.delete()
messages.success(request, "Formation supprimée ")
return redirect("mes_formations")
# @login_required
# def creation_departement(request):
# """Gère la création d'un nouveau département via un formulaire."""
# if request.method == 'POST':
# form_departement = DepartementForm(request.POST)
# if form_departement.is_valid():
# form_departement.save()
# messages.success(request, "Département ajouté avec succès.")
# else:
# messages.error(request, "Erreur lors de l'ajout du département.")
# return redirect('parametres-rh')
# @login_required
# def modifier_departement(request, id):
# """Gère la modification d'un département existant via un formulaire pré-rempli."""
# departement = Departement.objects.get(id=id)
# form = DepartementForm(instance=departement)
# if request.method == 'POST':
# nouveau_nom_departement = request.POST.get('nom')
# if nouveau_nom_departement:
# departement.nom = nouveau_nom_departement
# departement.save()
# messages.success(request, "Département modifié avec succès.")
# return redirect('parametres-rh')
# return render(request, 'gestion_employe/edit_departement.html', {
# 'form': form,
# 'departement': departement
# })
# @login_required
# def supprimer_departement(request, id):
# """Gère la suppression d'un département existant."""
# if request.method == "POST":
# departement = Departement.objects.get(id=id)
# departement.delete()
# messages.success(request, "Département supprimé avec succès !")
# return redirect('parametres-rh')

View File

@@ -1,5 +1,4 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
let table;
btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur');
@@ -22,30 +21,3 @@ btnEnregistrerBailleur.addEventListener('click', function() {
}
});
});
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

@@ -1,51 +1,20 @@
<div class="modal fade" id="modalBailleur" tabindex="-1" aria-labelledby="modalBailleurLabel" aria-hidden="true">
<div class="modal-dialog ">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalBailleurLabel">Gestion des Bailleurs</h5>
<h5 class="modal-title" id="modalBailleurLabel">Ajouter un Bailleur</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<ul class="nav nav-tabs px-3 pt-2" id="bailleurTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="ajouter-tab" data-bs-toggle="tab"
data-bs-target="#ajouter" type="button" role="tab">
Ajouter
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="liste-tab" data-bs-toggle="tab"
data-bs-target="#liste" type="button" role="tab">
Liste
</button>
</li>
</ul>
<div class="modal-body">
<div class="tab-content pt-3">
<div class="tab-pane fade show active" id="ajouter" role="tabpanel">
<div class="modal-body p-4">
<form id="formBailleur" method="POST" action="{% url 'gestion_projet:creation-bailleur' %}">
{% csrf_token %}
{{ form_ajout_bailleur.as_p }}
<button type="submit" class="btn btn-success mt-3" id="btnEnregistrerBailleur">
<i class="bi bi-save me-1"></i> Enregistrer
</button>
</form>
</div>
<div class="tab-pane fade" id="liste" role="tabpanel">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Liste des bailleurs</h6>
</div>
<div class="row">
<div class="col">
<div id="table-bailleurs"
data-url="{% url 'gestion_projet:liste-bailleurs' %}">
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" id="btnEnregistrerBailleur" class="btn btn-success"><i class="bi bi-save me-1"></i> Enregistrer</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
</div>
</div>
</div>
</div>

View File

@@ -19,12 +19,6 @@ urlpatterns = [
views.creation_projet,
name='creation-projet'
),
path(
'bailleurs/',
views.liste_bailleur,
name='liste-bailleurs'
),
path(
'projet/modifier/<int:projet_id>/',
views.modification_projet,
@@ -90,7 +84,6 @@ urlpatterns = [
views.liste_activites_projet,
name='liste-activites-projet'
),
# path(
# 'projet/ajout-de-document/',
# views.ajouter_document_projet,
@@ -126,6 +119,4 @@ urlpatterns = [
views.mises_a_jour_projet,
name='mises-a-jour-projet'
)
]

View File

@@ -2,7 +2,6 @@ from datetime import date
from decimal import Decimal, InvalidOperation
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils import timezone
from django.contrib import messages
from django.contrib.auth.decorators import login_required
@@ -144,22 +143,6 @@ def creation_bailleur(request):
return JsonResponse({'success': True})
return JsonResponse({'success': False})
@login_required
def liste_bailleur(request):
""" Vue pour retourner la liste de tous les bailleurs """
bailleurs = Bailleur.objects.all().order_by('-id')
data = []
for b in bailleurs:
data.append({
"id": b.id,
"nom_organisme": b.nom_organisme,
"contact": b.contact,
"email": b.email,
"pays": b.pays,
})
return JsonResponse(data, safe=False)
@login_required
def ajouter_financement_projet(request):
"""Ajoute un financement à un projet en vérifiant que le total ne dépasse pas 100%"""
@@ -335,7 +318,6 @@ def activites_projet(request):
}
return render(request, 'gestion_projet/suivi_activite.html', context)
@login_required
def ajouter_activite_projet(request):
"""Vue pour ajouter une activité à un projet spécifique via un formulaire"""

View File

@@ -167,9 +167,9 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("lien_zoom_container").className = 'd-none';
}
// if(data.statut !== "refusee"){
// $("motif_refus_container").className = 'd-none';
// }
if(data.statut !== "refusee"){
$("motif_refus_container").className = 'd-none';
}
$("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id;
@@ -178,15 +178,14 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe;
$("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut;
$("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin;
$("date_evenement").value=data.date_debut;
$("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation;
$("besoin_zoom").checked=data.besoin_zoom;
$("besoin_ordinateur").checked=data.besoin_ordi;
$("lien_zoom").value=data.lien_zoom;
// $("motif_refus").value=data.motif_refus;
$("motif_refus").value=data.motif_refus;
const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();

View File

@@ -21,12 +21,8 @@
<input class="form-control" id="salle" readonly>
</div>
<div class="form-group mb-2">
<label>Date de debut :</label>
<input type='date' class="form-control" id="date_debut" readonly >
</div>
<div class="form-group mb-2">
<label>Date de fin :</label>
<input type='date' class="form-control" id="date_fin" readonly >
<label>Date de l'évènement :</label>
<input type='date' class="form-control" id="date_evenement" readonly >
</div>
<div class="form-group mb-2">
<label>Heure de début :</label>
@@ -52,16 +48,21 @@
<label label="form-check-label">Besoin d'un ordinateur</label>
<input type="checkbox" class="form-check-input" id="besoin_ordinateur" readonly >
</div>
<div class="form-group mb-2" id='motif_refus_container'>
<label>Motif de refus de la reservation :</label>
<textarea class="form-control" id="motif_refus" readonly></textarea>
</div>
<div class='d-flex justify-content-around mt-2'>
{% if appartient_au_departement_informatique %}
<button class="btn btn-primary" id="ajoutZoom">Ajout du lien zoom</button>
{% endif %}
{% if appartient_direction and reservation.statut == "en_attente" %}
<button class="btn btn-danger" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
{% endif %}
<button class="btn btn-danger" id="bouton-annuler">Annuler</button>
{% if appartient_direction %}
<button class="btn btn-success" id="bouton-valider">Valider</button>
{% endif %}
<span id="current-user-id" data-user-id="{{ request.user.id }}"></span>
<button class="btn btn-danger" id="bouton-annuler">Annuler</button>
</div>
</form>
</div>

View File

@@ -8,93 +8,63 @@ from django.forms import model_to_dict
from gestion_employe.models import Employe
from gestion_salle.forms import ReservationForm
from .models import Reservation
from datetime import timedelta
from django.contrib.auth.models import User
@login_required
def index(request: HttpRequest):
def index(request:HttpRequest):
"""Vue de gestion de la reservation de la salle"""
try:
employe = Employe.objects.get(user=request.user)
except Employe.DoesNotExist:
messages.error(request, "Profil employé introuvable.")
messages.error(request, "Impossible d'accéder au menu 'Reservation de salle' car votre profil Utilisateur n'est lié à aucun profil Employe. Veuillez contacter l'administrateur.")
return redirect('gestion_conges:conge')
if request.method == "POST":
form = ReservationForm(request.POST)
if form.is_valid():
date_debut = form.cleaned_data.get('date_debut')
date_fin = form.cleaned_data.get('date_fin')
salle = form.cleaned_data.get('salle')
heure_debut = form.cleaned_data.get('heure_debut')
heure_fin = form.cleaned_data.get('heure_fin')
motif_reservation = form.cleaned_data.get('motif_reservation')
besoin_zoom = form.cleaned_data.get('besoin_zoom')
besoin_ordi = form.cleaned_data.get('besoin_ordi')
date_debut = form.cleaned_data['date_debut']
date_fin = form.cleaned_data['date_fin']
salle = form.cleaned_data['salle']
heure_debut = form.cleaned_data['heure_debut']
heure_fin = form.cleaned_data['heure_fin']
motif = form.cleaned_data['motif_reservation']
besoin_zoom = form.cleaned_data['besoin_zoom']
besoin_ordi = form.cleaned_data['besoin_ordi']
if date_fin < date_debut:
messages.error(request, "Date fin invalide.")
return redirect('gestion_salle:reservation-salle')
if heure_fin <= heure_debut:
messages.error(request, "Heure invalide.")
return redirect('gestion_salle:reservation-salle')
if not request.user.first_name.strip() or not request.user.last_name.strip():
messages.error(
request,
"Veuillez renseigner votre nom et prénom pour pouvoir faire une réservation."
while date_debut <= date_fin :
reservation = Reservation(
employe = employe,
date_debut = date_debut,
date_fin = date_debut,
salle = salle,
heure_debut = heure_debut,
heure_fin = heure_fin,
besoin_zoom = besoin_zoom,
besoin_ordi = besoin_ordi,
motif_reservation=motif_reservation,
)
return redirect('gestion_salle:reservation-salle')
reservation.save()
date_debut = date_debut + timedelta(days=1)
created = []
current_date = date_debut
while current_date <= date_fin:
reservation = Reservation.objects.create(
employe=employe,
date_debut=current_date,
date_fin=current_date,
salle=salle,
heure_debut=heure_debut,
heure_fin=heure_fin,
besoin_zoom=besoin_zoom,
besoin_ordi=besoin_ordi,
motif_reservation=motif,
statut="en_attente"
)
created.append(reservation)
current_date += timedelta(days=1)
messages.success(request, "Réservation(s) créée(s) avec succès.")
messages.success(request, "Réservation(s) créées avec succès.")
return redirect('gestion_salle:reservation-salle')
formulaire_reservation = ReservationForm()
departement = employe.departement
appartient_direction = request.user.groups.filter(name='direction').exists()
liste_demande_reservation = Reservation.objects.filter(
employe=employe,
statut='en_attente'
).values_list('id', flat=True)
departement = Employe.objects.get(user__username=request.user).departement
appartient_direction = 'direction' in request.user.groups.values_list('name', flat=True)
liste_demande_reservation = [
reservation.id for reservation in
Reservation.objects.filter(employe=employe, statut='en_attente')
]
context = {
'formulaire_reservation': formulaire_reservation,
'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(),
'appartient_au_departement_informatique': departement and departement.nom == "Systeme informatique",
'appartient_au_departement_informatique': 'Informatique' == departement.nom if departement else False,
'appartient_direction': appartient_direction,
'liste_demande_reservation': list(liste_demande_reservation),
'liste_demande_reservation': liste_demande_reservation
}
return render(request, "gestion_salle/index.html", context)
def liste_reservation(request:HttpRequest):
"""Vue d'affichage des creneaux disponibles"""
reservations = Reservation.objects.filter(statut = "validee")
@@ -116,6 +86,7 @@ def liste_reservation(request:HttpRequest):
"end": reservation.heure_fin,
"color": color,
})
return JsonResponse(liste_reservation, safe=False)
@login_required
@@ -132,25 +103,23 @@ def liste_reservation_attente(request):
return JsonResponse(liste_reservation, safe=False)
def detail_reservation(request:HttpRequest, reservation_id:int):
reservation = Reservation.objects.get(id=reservation_id)
employe = reservation.employe.user
reservation_json = {
'id_reservation': reservation_id,
'employe': f"{employe.first_name} {employe.last_name}",
'salle': reservation.salle,
'statut': reservation.statut,
'date_debut': reservation.date_debut.strftime('%Y-%m-%d'),
'date_fin': reservation.date_fin.strftime('%Y-%m-%d'),
'date_evenement': reservation.date_debut.strftime('%Y-%m-%d'),
'heure_debut': reservation.heure_debut.strftime('%H:%M'),
'heure_fin': reservation.heure_fin.strftime('%H:%M'),
'motif_reservation': reservation.motif_reservation,
'besoin_zoom': reservation.besoin_zoom,
'besoin_ordinateur': reservation.besoin_ordi,
'lien_zoom': reservation.lien_zoom or '',
'motif_refus': reservation.motif_refus or '',
}
return JsonResponse(reservation_json, safe=True)
@login_required
@@ -196,35 +165,31 @@ def annuler_reservation(request:HttpRequest):
return redirect('gestion_salle:reservation-salle')
@login_required
def valider_reservation(request: HttpRequest):
"""Validation d'une réservation"""
def valider_reservation(request:HttpRequest):
"""Vue de gestion de l'annulation de la reservation"""
if request.method == 'POST':
reservation_id = request.POST.get('id_reservation')
reservation_id= request.POST['id_reservation']
try:
reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist:
messages.error(request, "La réservation sélectionnée n'existe pas.")
except reservation.DoesNotExist:
messages.error(request, "La resevertion selectionné n'existe pas.")
return redirect("salle")
reservation.statut = 'validee'
reservation.save()
messages.success(request, f"Réservation de {reservation.employe.get_full_name()} validée avec succès.")
return redirect('gestion_salle:reservation-salle')
@login_required
def refuser_reservation(request: HttpRequest):
"""Refuser une réservation"""
if request.method == 'POST':
reservation_id = request.POST.get('id_reservation')
def refuser_reservation(request:HttpRequest):
"""Vue de gestion de refus de la reservation"""
data = json.loads(request.body)
reservation_id = data.get("id_reservation")
try:
reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist:
messages.error(request, "La réservation n'existe pas.")
return redirect("salle")
reservation.statut = 'refusee'
except Reservation.DoesNotExist and ValueError:
return JsonResponse({"message": "La resevertion selection n'existe pas."})
else:
reservation.statut = "refusee"
reservation.save()
return redirect('gestion_salle:reservation-salle')
return JsonResponse({"message": "Réservation refusée avec succès."})

View File

@@ -1,152 +1,21 @@
anyio==4.13.0
argon2-cffi==25.1.0
argon2-cffi-bindings==25.1.0
arrow==1.4.0
asgiref==3.11.0
asttokens==3.0.1
async-lru==2.3.0
attrs==26.1.0
Automat==20.2.0
babel==2.18.0
bcrypt==3.2.0
beautifulsoup4==4.14.3
bleach==6.3.0
blinker==1.4
certifi==2026.2.25
cffi==2.0.0
chardet==4.0.0
asgiref==3.11.1
certifi==2026.4.22
charset-normalizer==3.4.7
click==8.0.3
cloud-init==25.3
colorama==0.4.4
comm==0.2.3
command-not-found==0.3
configobj==5.0.6
constantly==15.1.0
cryptography==3.4.8
dbus-python==1.2.18
debugpy==1.8.20
decorator==5.2.1
defusedxml==0.7.1
distlib==0.4.0
distro==1.7.0
distro-info==1.1+ubuntu0.2
Django==5.2.10
et_xmlfile==2.0.0
exceptiongroup==1.3.1
executing==2.2.1
fastjsonschema==2.21.2
filelock==3.20.3
fqdn==1.5.1
h11==0.16.0
httpcore==1.0.9
httplib2==0.20.2
httpx==0.28.1
hyperlink==21.0.0
idna==3.3
importlib-metadata==4.6.4
incremental==21.3.0
ipykernel==7.2.0
ipython==8.39.0
isoduration==20.11.0
jedi==0.19.2
jeepney==0.7.1
Jinja2==3.0.3
json5==0.14.0
jsonpatch==1.32
jsonpointer==2.0
jsonschema==4.26.0
jsonschema-specifications==2025.9.1
jupyter-events==0.12.0
jupyter-lsp==2.3.1
jupyter_client==8.8.0
jupyter_core==5.9.1
jupyter_server==2.17.0
jupyter_server_terminals==0.5.4
jupyterlab==4.5.6
jupyterlab_pygments==0.3.0
jupyterlab_server==2.28.0
keyring==23.5.0
lark==1.3.1
launchpadlib==1.10.16
lazr.restfulclient==0.14.4
lazr.uri==1.0.6
MarkupSafe==2.0.1
matplotlib-inline==0.2.1
mistune==3.2.0
more-itertools==8.10.0
nbclient==0.10.4
nbconvert==7.17.0
nbformat==5.10.4
nest-asyncio==1.6.0
netifaces==0.11.0
notebook_shim==0.2.4
Django==5.2.13
django-simple-sso==1.3.0
idna==3.13
itsdangerous==0.24
mysqlclient==2.2.8
numpy==2.2.6
oauthlib==3.2.0
openpyxl==3.1.5
overrides==7.7.0
packaging==26.0
pandas==2.3.3
pandocfilters==1.5.1
parso==0.8.6
pexpect==4.9.0
platformdirs==4.5.1
prometheus_client==0.24.1
prompt_toolkit==3.0.52
psutil==7.2.2
ptyprocess==0.7.0
pure_eval==0.2.3
pyasn1==0.4.8
pyasn1-modules==0.2.1
pycparser==3.0
pycurl==7.44.1
Pygments==2.20.0
PyGObject==3.42.1
PyHamcrest==2.0.2
PyJWT==2.3.0
pyOpenSSL==21.0.0
pyparsing==2.4.7
pyrsistent==0.18.1
pyserial==3.5
python-apt==2.4.0+ubuntu4.1
pillow==12.2.0
python-dateutil==2.9.0.post0
python-json-logger==4.1.0
pytz==2022.1
PyYAML==5.4.1
pyzmq==27.1.0
referencing==0.37.0
pytz==2026.1.post1
requests==2.33.1
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rfc3987-syntax==1.1.0
rpds-py==0.30.0
SecretStorage==3.3.1
Send2Trash==2.1.0
service-identity==18.1.0
six==1.16.0
soupsieve==2.8.3
six==1.17.0
sqlparse==0.5.5
ssh-import-id==5.11
stack-data==0.6.3
systemd-python==234
terminado==0.18.1
tinycss2==1.4.0
tomli==2.4.1
tornado==6.5.5
traitlets==5.14.3
Twisted==22.1.0
typing_extensions==4.15.0
tzdata==2025.3
ubuntu-pro-client==8001
ufw==0.36.1
unattended-upgrades==0.1
uri-template==1.3.0
urllib3==1.26.5
virtualenv==20.13.0+ds
wadllib==1.3.6
wcwidth==0.6.0
webcolors==25.10.0
webencodings==0.5.1
websocket-client==1.9.0
zipp==1.0.0
zope.interface==5.4.0
tzdata==2026.2
urllib3==2.6.3
python-decouple
gunicorn