50 Commits

Author SHA1 Message Date
99434a21e0 Fonctionnalite: verifier qu'aucun contrat n'est actif avant la creation d'un nouveau pour un employe
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-05-06 16:01:37 +00:00
f6b90e7dd0 Verification du statut avant la creation d'un contrat 2026-05-06 15:53:02 +00:00
fcfac71026 clean: remove pycache from tracking 2026-05-06 15:44:16 +00:00
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
85 changed files with 387 additions and 1217 deletions

4
.gitignore vendored
View File

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

2
Jenkinsfile vendored
View File

@@ -39,8 +39,6 @@ pipeline
python manage.py makemigrations
python manage.py migrate
echo $SUDO_PASSWORD | sudo -S rm -r staticfiles
python manage.py collectstatic --noinput
echo $SUDO_PASSWORD | sudo -S chown -R www-data:www-data /var/www/sirh
echo "Deploiement reussi"

Binary file not shown.

View File

@@ -12,7 +12,6 @@ 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'.
@@ -27,8 +26,7 @@ SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = config('DEBUG', default=False, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',')
#CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]).split(",")
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',')
# Application definition
@@ -42,9 +40,12 @@ INSTALLED_APPS = [
'gestion_employe',
'gestion_conge',
'gestion_projet',
'gestion_salle'
'gestion_salle',
'simple_sso.sso_server'
]
LOGIN_URL = 'login'
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
@@ -67,7 +68,6 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'contexte_universel.contexte_universel',
],
},
},
@@ -79,14 +79,23 @@ WSGI_APPLICATION = 'SIRH.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
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("DB_NAME"),
'USER': config("DB_USER"),
'PASSWORD': config("DB_PASSWORD"),
'HOST': config("DB_HOST"),
'PORT': config("DB_PORT", cast=int),
'NAME': config('DATABASE_NAME'),
'USER': config('DATABASE_USER'),
'PASSWORD': config('DATABASE_PASSWORD'),
'HOST': config('DATABASE_HOST'),
'PORT': config('DATABASE_PORT'),
}
}
@@ -141,21 +150,11 @@ MEDIA_ROOT = BASE_DIR / "media"
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
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'ssl0.ovh.net'
EMAIL_PORT = 465
EMAIL_USE_SSL = True
EMAIL_USE_TLS = False
EMAIL_HOST_USER = config("EMAIL_HOST_USER")
EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD")
EMAIL_HOST_USER = 'support.it@cerfig.org'
EMAIL_HOST_PASSWORD = 'Cerfig2025'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

View File

@@ -1,40 +0,0 @@
# SGL/sso.py
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from simple_sso.sso_client.client import Client
from gestion_employe.models import Departement, Employe
User = get_user_model()
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,9 +15,8 @@
<body>
<div class="container-fluid">
<div class="row flex-nowrap">
{% include 'parts/menu_application.html' %}
{% include 'parts/menu_principal.html' %}
<div class="col p-4">
<div class="col-9 p-4">
{% block 'contenu' %} {% endblock %}
</div>
</div>

View File

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

View File

@@ -1,8 +0,0 @@
<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,36 +13,35 @@
<i class="bi bi-person-circle text-white" style="font-size:60px;"></i>
{% endif %}
<div class="text-white mt-2 fw-bold fs-5">
{{ user.first_name }} {{ user.last_name }}
{{ user.username }}
</div>
</div>
<a href="{% url 'gestion_employe:mon-profil' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<a href="{% url 'gestion_employe:mon-profil' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-person-circle"></i> Mon profil
</a>
{% 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.2em;">
<a href="{% url 'gestion_employe:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-speedometer2"></i> Tableau de bord
</a>
{% endif %}
{% 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.2em;">
<a href="{% url 'gestion_projet:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-folder"></i> Gestion des Projets
</a>
{% endif %}
<a href="{% url 'gestion_conges:conge' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<a href="{% url 'gestion_conges:conge' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-airplane"></i> Gestion des congés
</a>
{% 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.2em;">
<a href="{% url 'gestion_projet:activites-projet' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-list-task"></i> Suivi des Activités
</a>
{% endif %}
<a href="{% url 'gestion_salle:reservation-salle' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<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 %} {% url 'deconnexion' %} {% endcomment %}
<a href="{% url 'deconnexion' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<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>
</div>
</div>

View File

@@ -14,31 +14,34 @@ Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf import settings
from django.contrib import admin
from django.urls import include, path
from django.conf.urls.static import static
from django.conf import settings
# from simple_sso.sso_server.server import Server
from . import views
from .sso import SIRHClient
client_sirh = SIRHClient(
settings.SSO_SERVER,
settings.SSO_PUBLIC_KEY,
settings.SSO_PRIVATE_KEY
)
# server_sso = Server()
urlpatterns = [
urlpatterns = [
path(
'',
views.login_view,
name='index'
),
path('login/',
views.connexion,
views.login_view,
name='login'
),
path(
'deconnexion/',
views.deconnexion,
views.deconnexion_view,
name='deconnexion'
),
path(
'gestion-employe/',
'employé/',
include("gestion_employe.urls")
),
path(
@@ -56,6 +59,9 @@ urlpatterns = [
path(
'admin/',
admin.site.urls
),
path("client/", include(client_sirh.get_urls())),
]
),
# path(
# 'sso',
# include(server_sso.get_urls())
# )
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

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

View File

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

View File

@@ -1,18 +1,12 @@
from django.utils import timezone
from gestion_employe.models import Contrat
from gestion_conge.models import Conge
from gestion_employe.models import Contrat, Employe
QUOTA_CONGE_ANNUEL = 30
NOMBRE_PAGINATION = 8
DEBUT_RAPPEL = 60
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):
"""Fonction de calcul du solde de congé restant l'employé"""
@@ -37,13 +31,4 @@ def solde_conge(employe):
"success": True,
"quota_annuel": QUOTA_CONGE_ANNUEL - jours_conges_valider,
"nombre_jours_valide": jours_conges_valider
}
def destinataire_mail_demande_conges(employe):
"""Fonction de récupération des destinataires pour les mails de demande de congés"""
if employe.chef:
return EMAIL_ASSISTANTE_DE_DIRECTION
else:
if employe.departement:
chefs_departement = Employe.objects.filter(departement=employe.departement, chef=True)
return list(chefs_departement.values_list('user__email', flat=True))
}

View File

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

View File

@@ -1,16 +1,4 @@
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
# Generated by Django 5.2.13 on 2026-06-16 11:06
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 6026af5 (Bug : Affichages des conges)
import django.db.models.deletion
from django.db import migrations, models

View File

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

View File

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

View File

@@ -32,17 +32,11 @@
<h5 class="fw-bold">{{ nombre_conges_valide }}</h5>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col d-flex justify-content-between align-items-center">
<div class="d-flex justify-content-between my-4">
<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>
</div>
</div>
<div class="row">
<div class="col">
<div class="table-responsive">
<div id="liste-demande-conges" data-url="{% url 'gestion_conges:liste-des-conges' %}"></div>
</div>
</div>

View File

@@ -1,20 +1,16 @@
import json
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.forms.models import model_to_dict
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils import timezone
from fonction_utilitaire import fonctions_utilitaire
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from gestion_conge.forms import CongeForm
from gestion_employe.models import Affectation, Employe
from django.forms.models import model_to_dict
from django.utils import timezone
from django.db.models import Q
from fonction_utilitaire import fonctions_utilitaire
from .models import Conge
@login_required
def index(request):
"""Vue de gestion de l'index"""
@@ -87,6 +83,7 @@ def index(request):
Q(validation_hierarchique = True) | Q(validation_hierarchique = False)
).order_by('-date_demande')
return render(request, 'gestion_conge/index.html', {
"nombre_conges_valide": nombre_conges_valide,
"nombre_conges_refuse": nombre_conges_refuse,
@@ -119,88 +116,79 @@ def demander_conge(request):
conge_obj = form.save(commit=False)
conge_obj.employe = employe
if conge_obj.type == "conge_annuel" and retour_quota["nombre_jours_valide"] + conge_obj.nombre_jours > quota_annuel:
messages.error(request, "Quota annuel dépassé (30 jours max).")
return redirect("gestion_conges:conge")
if conge_obj.type == "conge_annuel":
if retour_quota["nombre_jours_valide"] + conge_obj.nombre_jours > quota_annuel:
messages.error(request, "Quota annuel dépassé (30 jours max).")
return redirect("gestion_conges:conge")
conge_obj.save()
messages.success(request, "Votre demande de congé a été enregistrée.")
fonctions_utilitaire.envoyer_mail(
sujet = "Demande de congé",
message = f"""Bonjour {employe.user.first_name} {employe.user.last_name}, votre demande de congé a été enregistrée. Veuillez consulter votre profil pour plus de détails.""",
destinataires = fonctions_utilitaire.destinataire_mail_demande_conges() + [employe.user.email]
)
return redirect("gestion_conges:conge")
return redirect("gestion_conges:conge")
@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(
employe=employe,
date_fin_daffectation__gte=timezone.now().date()
).first()
is_direction = employe.user.groups.filter(name='direction').exists()
try:
affectation = Affectation.objects.get(
employe=employe,
date_fin_daffectation__gte=timezone.now().date()
)
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": [
{
**model_to_dict(conge),
"prenom_nom": f"{conge.employe.user.first_name} {conge.employe.user.last_name}",
"date_demande": conge.date_demande,
"nombre_jours": conge.nombre_jours,
"type": dict(conge.TYPE_CHOICES).get(conge.type),
"solde_conge": fonctions_utilitaire.solde_conge(conge.employe)["quota_annuel"]
}
for conge in conges
]
})
"data":[
{
**model_to_dict(conge),
"prenom_nom": f"{conge.employe.user.first_name} {conge.employe.user.last_name}",
"date_demande": conge.date_demande,
"nombre_jours": conge.nombre_jours,
"type": dict(conge.TYPE_CHOICES).get(conge.type),
"solde_conge": fonctions_utilitaire.solde_conge(conge.employe)["quota_annuel"]
}
for conge in conges_en_attente]},
safe=False
)
@login_required
def validation_de_conge(request):
@@ -222,13 +210,13 @@ def validation_de_conge(request):
motif_refus = request_data.get("motif_refus", "")
if validation_hierarchique is not None:
conge.validation_hierarchique = validation_hierarchique == "valide"
conge.validation_hierarchique = True if validation_hierarchique == "valide" else False
if validation_hierarchique == "refuse" and not motif_refus:
return JsonResponse({"message": "Veuillez fournir un motif de refus."})
conge.motif_refus = motif_refus if validation_hierarchique == "refuse" else ""
if validation_direction is not None:
conge.validation_direction = validation_direction == "valide"
conge.validation_direction = True if validation_direction == "valide" else False
if validation_direction == "refuse" and not motif_refus:
return JsonResponse({"message": "Veuillez fournir un motif de refus."})
conge.motif_refus = motif_refus if validation_direction == "refuse" else ""

View File

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

View File

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

View File

@@ -1,16 +1,4 @@
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
# Generated by Django 5.2.13 on 2026-06-16 11:06
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 6026af5 (Bug : Affichages des conges)
import django.db.models.deletion
from django.conf import settings
@@ -39,19 +27,7 @@ class Migration(migrations.Migration):
fields=[
('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)),
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
('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)),
=======
('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)),
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
('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)),
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
('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)),
>>>>>>> 6026af5 (Bug : Affichages des conges)
('date_embauche', models.DateField(blank=True, null=True)),
('adresse', models.CharField(blank=True, max_length=100, null=True)),
('telephone', models.CharField(blank=True, max_length=15, null=True)),
@@ -72,19 +48,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('numero_contrat', models.CharField(max_length=100, unique=True)),
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
('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)),
=======
('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)),
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
('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)),
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
('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)),
>>>>>>> 6026af5 (Bug : Affichages des conges)
('date_debut', models.DateField()),
('date_fin', models.DateField(blank=True, null=True)),
('salaire_mensuel', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),

View File

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

View File

@@ -12,7 +12,6 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
// ajaxURL: url_liste_employe,
pagination: true,
paginationSize: 10,
responsiveLayout : "hide"
})
fetch(url_liste_employe)
@@ -261,73 +260,4 @@ const listeContratExpirant = new Tabulator("#listeContratExpirant", {
},
],
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
})
let tabContrats = null;
document.getElementById("modalContratsEmploye")
.addEventListener("show.bs.modal", function () {
const employeId = document.getElementById("detail-id").value;
console.log("Employe ID :", employeId);
if (!employeId) {
alert("Aucun employé sélectionné");
return;
}
fetch(`/employé/employe/${employeId}/contrats/`)
.then(response => {
if (!response.ok) {
throw new Error("Erreur HTTP " + response.status);
}
return response.json();
})
.then(data => {
document.getElementById("nom-employe-contrat").innerText =
data.employe;
if (tabContrats) {
tabContrats.destroy();
}
tabContrats = new Tabulator(
"#table-contrats-tabulator",
{
data: data.contrats,
layout: "fitColumns",
placeholder: "Aucun contrat",
columns: [
{ title: "Numéro", field: "numero_contrat" },
{ title: "Type", field: "type_contrat" },
{ title: "Date début", field: "date_debut" },
{ title: "Date fin", field: "date_fin" },
{ title: "Salaire", field: "salaire_mensuel" },
{ title: "Statut", field: "statut" },
{
title: "Contrat",
field: "fichier",
formatter: function(cell) {
const url = cell.getValue();
if (!url) {
return "<span class='text-danger'>Aucun fichier</span>";
}
return `<a href="${url}" target="_blank" class="btn btn-sm btn-primary">
<i class="bi bi-file-earmark-pdf"></i> Ouvrir
</a>`;
}
}
]
}
);
})
.catch(error => {
console.error("Erreur :", error);
});
});
})

View File

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

View File

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

View File

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

View File

@@ -93,8 +93,8 @@
</h5>
{% if user|has_group:"ressource_humaine" %}
<div>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalContratsEmploye">
Liste contrat
<button class="btn btn-secondary" disabled>
<i class="bi bi-file-earmark-lock"></i> Contrat actif
</button>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modalContrat{{ item.employe.id }}">
<i class="bi bi-file-earmark-text"></i> Créer contrat

View File

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

View File

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

View File

@@ -1,29 +1,25 @@
import json
from datetime import datetime, timedelta
from datetime import date, timedelta, datetime
from dateutil.relativedelta import relativedelta
from django.contrib import messages
from django.contrib.auth import authenticate
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.db.models import Sum
from django.forms.models import model_to_dict
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils import timezone
from django.contrib import messages
from django.contrib.auth.models import User
from django.contrib.auth import authenticate
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from fonction_utilitaire import fonctions_utilitaire
from django.http import JsonResponse
from django.forms.models import model_to_dict
from django.db.models import Sum
from .models import Employe, Contrat, Affectation, Formation
from .forms import AffectationForm, ContratForm, FormationForm
from .models import Affectation, Contrat, Employe, Formation
from fonction_utilitaire import fonctions_utilitaire
@login_required
def index(request):
"""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')
nombre_employes = Employe.objects.count()
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
@@ -98,7 +94,7 @@ def affecter_employe_projet(request):
elif total_affectation + temps_nouveau > 100:
messages.warning(
request,
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}%)."
f"Les pourcentages d'affectation de l'employé {employe.first_name} {employe.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)."
)
return redirect('gestion_employe:index')
@@ -113,13 +109,6 @@ def affecter_employe_projet(request):
}
)
messages.success(request, f"L'employé {employe.user.first_name} {employe.user.last_name} a été affecté au projet {projet.nom_projet}.")
fonctions_utilitaire.envoyer_mail(
sujet = "Affectation à un projet",
message = f"""Bonjour {employe.user.first_name} {employe.user.last_name}, vous avez été affecté au projet {projet.nom_projet.upper()} pour la période du {form.cleaned_data['date_affectation'].strftime('%d/%m/%Y')} au {date_fin_affectation.strftime('%d/%m/%Y')} en tant que {dict(Affectation.ROLE_CHOICES).get(form.cleaned_data['role'])}.
Veuillez consulter votre profil pour plus de détails.""",
destinataires = [employe.user.email]
)
return redirect('gestion_employe:index')
else:
messages.error(request, "Erreur : Formulaire non valide.")
@@ -133,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',
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),
@@ -177,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"""
@@ -211,26 +184,25 @@ def modifier_employer(request):
except Employe.DoesNotExist:
return JsonResponse({"message": "Employé non trouvé."})
if request.method == "POST":
data = json.loads(request.body)
user = User.objects.get(username=request.user)
user.last_name = request.POST['nom']
user.first_name = request.POST['prenom']
user.email = request.POST['email']
employe.telephone = request.POST['telephone']
employe.adresse = request.POST['adresse']
employe.sexe = request.POST['sexe']
user.last_name = data['nom']
user.first_name = data['prenom']
user.email = data['email']
employe.telephone = data['telephone']
employe.adresse = data['adresse']
employe.sexe = data['sexe']
if request.FILES.get("photo"):
employe.photo = request.FILES["photo"]
if request.POST['date_naissance']:
difference = relativedelta(timezone.now().date(), datetime.strptime(request.POST['date_naissance'], "%Y-%m-%d").date())
if data['date_naissance']:
difference = relativedelta(timezone.now().date(), datetime.strptime(data['date_naissance'], "%Y-%m-%d").date())
if difference.years >= 18:
employe.date_naissance = request.POST['date_naissance']
employe.date_naissance = data['date_naissance']
else:
messages.error(request, "Veuillez entrez une date de naissance correcte.")
return JsonResponse({"message": "Veuillez entrez une date de naissance correcte."})
employe.save()
user.save()
messages.success(request, "Profil mis à jour avec succès.")
return redirect("gestion_employe:mon-profil")
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"""
@@ -282,10 +254,9 @@ def creation_contrat(request):
except Employe.DoesNotExist:
messages.error(request, "Employé non trouvé.")
return redirect('employe-index')
contrat_actif = Contrat.objects.filter(
employe=employe,
date_fin__gte=datetime.date.today()
date_fin__gte=date.today()
).exists()
if request.method == "POST":
@@ -311,45 +282,6 @@ def creation_contrat(request):
'contrat_form': form
})
@login_required
def contrats_employe(request, employe_id):
try:
employe = Employe.objects.get(id=employe_id)
except Employe.DoesNotExist:
return JsonResponse(
{"error": "Employé non trouvé"},
status=404
)
contrats = Contrat.objects.filter(
employe=employe
).order_by("-date_debut")
data = [
{
"id": contrat.id,
"numero_contrat": contrat.numero_contrat,
"type_contrat": dict(Contrat.TYPE_CONTRAT).get(
contrat.type_contrat
),
"date_debut": contrat.date_debut.strftime("%Y-%m-%d"),
"date_fin": contrat.date_fin.strftime("%Y-%m-%d") if contrat.date_fin else "",
"salaire_mensuel": contrat.salaire_mensuel,
"statut": dict(Contrat.STATUT_CONTRAT).get(
contrat.statut
),
"fichier": contrat.fichier_contrat.url if contrat.fichier_contrat else "",
}
for contrat in contrats
]
return JsonResponse({
"employe": employe.user.get_full_name(),
"contrats": data
})
@login_required
def enregistrer_detail_employe(request):
"""Vue pour permettre à un utilisateur de modifier les détails d'un employé via une requête AJAX"""
@@ -368,7 +300,6 @@ def enregistrer_detail_employe(request):
else:
return JsonResponse({"message": "Méthode non autorisée."}, status=405)
@login_required
def liste_employe(request):
""" Vue pour retourner la liste de tous les employés """

View File

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

View File

@@ -1,16 +1,4 @@
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
# Generated by Django 5.2.13 on 2026-06-16 11:06
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 6026af5 (Bug : Affichages des conges)
import django.db.models.deletion
from django.db import migrations, models

View File

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

View File

@@ -1,7 +1,5 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
let table;
btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur');
const formData = new FormData(form);
@@ -22,27 +20,4 @@ btnEnregistrerBailleur.addEventListener('click', function() {
alert('Ce bailleur existe déjà dans la base de données.');
}
});
});
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

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

View File

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

View File

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

View File

@@ -1,16 +1,4 @@
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
# Generated by Django 5.2.13 on 2026-06-16 11:06
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
# Generated by Django 5.2.13 on 2026-04-17 12:03
>>>>>>> 6026af5 (Bug : Affichages des conges)
import django.db.models.deletion
from django.db import migrations, models

View File

@@ -1,21 +1,20 @@
from django.db import models
from gestion_employe.models import Employe
class Reservation(models.Model):
"""Modèle de création des réservations"""
TYPE_CHOICES = (
TYPE_CHOICES = [
('formation', 'Salle de formation'),
('reunion', 'Salle de réunion'),
('lien_zoom', 'Lien Zoom'),
)
STATUT = (
]
STATUT = [
('en_attente', 'En attente'),
('validee', 'Validée'),
('refusee', 'Refusée'),
('annulee', 'Annulée'),
)
]
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
salle = models.CharField(max_length=100, choices=TYPE_CHOICES)
@@ -31,4 +30,4 @@ class Reservation(models.Model):
statut = models.CharField(choices=STATUT, default='en_attente', max_length=25)
def __str__(self):
return f"{self.salle} - {self.employe.user.first_name} {self.employe.user.last_name}"
return f"{self.salle} - {self.employe.user.first_name} {self.employe.user.last_name} le {self.date_reservation}"

View File

@@ -15,6 +15,7 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
.then(response => response.json())
.then(data => {
currentReservationId = data.id_reservation;
console.log(data);
$("id_reservation_detail").value = data.id_reservation;
$("id_reservation_refus").value = data.id_reservation;
$("id_reservation_zoom").value = data.id_reservation;
@@ -27,18 +28,13 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("motif_reservation").value=data.motif_reservation;
$("besoin_zoom").checked=data.besoin_zoom;
$("besoin_ordinateur").checked=data.besoin_ordinateur;
$("lien_zoom").value=data.lien_zoom;
const zoomContainer = $("lien_zoom_container");
if (!data.besoin_zoom) {
zoomContainer.classList.add("d-none");
} else {
zoomContainer.classList.remove("d-none");
}
if (data.besoin_zoom === false){
$("lien_zoom_container").className = "d-none";
}
$("lien_zoom").value=data.lien_zoom;
if(data.statut !== "annulee"){
$("motif_refus_container").className = "d-none";
}else{
$("motif_refus").value=data.motif_refus;
}
})
}
});
@@ -81,16 +77,9 @@ document.addEventListener("DOMContentLoaded", function () {
})
$("bouton-annuler").addEventListener("click", (e) => {
e.preventDefault();
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url_annuler = $("formulaire-details").dataset.urlannuler;
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault();
fetch(
url_annuler,
{
@@ -137,15 +126,10 @@ if($("ajoutZoom")){
if($("refuserReservation")){
$("refuserReservation").addEventListener("click", (e) => {
e.preventDefault();
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url = e.currentTarget.dataset.lienrefus;
const idRes = $("id_reservation_detail").value;
console.log(idRes);
e.preventDefault();
fetch(url, {
method: "POST",
headers: {
@@ -177,11 +161,16 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
tableau_reservation_attente.on("rowClick", (row, rowData) => {
const data = rowData.getData();
console.log(data);
if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none';
}
const id_user = $("current-user-id").dataset.userid;
if(data.statut !== "refusee"){
$("motif_refus_container").className = 'd-none';
}
$("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id;
$("id_reservation_zoom").value = data.id;
@@ -189,20 +178,16 @@ 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;
if (id_user != data.employe_id){
$("bouton-annuler").className = "d-none";
}
$("motif_refus").value=data.motif_refus;
const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
modal.show();
})
})

View File

@@ -26,9 +26,9 @@
<div class="form-group col-5 me-2">
<label>Selectionner une salle :</label>
<select class = "form-select" id="liste-salle">
<option value='Salle de formation'>Salle de formation</option>
<option value='Salle de réunion'>Salle de réunion</option>
<option value='Lien Zoom'>Lien Zoom</option>
<option value='formation'>Salle de formation</option>
<option value='reunion'>Salle de réunion</option>
<option value='lien_zoom'>Lien Zoom</option>
</select>
</div>
</div>

View File

@@ -1,5 +1,4 @@
<!-- Modal d'affichage des détails d'une reservation -->
{% load tags_personnaliser %}
<div class="modal fade" id="modalDetailReservation" tabindex="-1" aria-labelledby="modalDetailReservationLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
@@ -12,7 +11,7 @@
<div class="modal-body">
<form method="POST" data-urlannuler="{% url 'gestion_salle:annuler-reservation' %}" data-urlvalider="{% url 'gestion_salle:valider-reservation' %}" id="formulaire-details">
{% csrf_token %}
<input type='' class="form-control" id="id_reservation_detail" name='id_reservation'>
<input type='hidden' class="form-control" id="id_reservation_detail" name='id_reservation'>
<div class="form-group mb-2">
<label>Employé :</label>
<input class="form-control" id="employe" readonly>
@@ -22,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>
@@ -50,22 +45,27 @@
<input type="url" class="form-control" id="lien_zoom" readonly >
</div>
<div class="form-check mb-2">
<label class="form-check-label">Besoin d'un ordinateur</label>
<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>
<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>
<button class="btn btn-success" id="bouton-valider">Valider</button>
<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>
</div>
</div>
</div>
</div>

View File

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

View File

@@ -1,107 +1,68 @@
import json
from datetime import timedelta
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.forms import model_to_dict
from django.http import HttpRequest, JsonResponse
from django.shortcuts import redirect, render
from fonction_utilitaire import fonctions_utilitaire
from django.http import JsonResponse, HttpRequest
from django.forms import model_to_dict
from gestion_employe.models import Employe
from gestion_salle.forms import ReservationForm
from .models import Reservation
@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['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')
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')
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.")
if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
fonctions_utilitaire.envoyer_mail(
sujet = "Reservation de salle",
message = f"""
Une nouvelle demande de réservation de la {dict(Reservation.TYPE_CHOICES).get(salle)} a été effectuée par {employe.user.first_name} {employe.user.last_name} du {form.cleaned_data.get('date_debut').strftime('%d/%m/%Y')} au {form.cleaned_data.get('date_fin').strftime('%d/%m/%Y')} pour motif "{motif}".
Veuillez vous connecter à la plateforme pour plus de détails.""",
destinataires = fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION
)
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):
@@ -109,7 +70,6 @@ def liste_reservation(request:HttpRequest):
reservations = Reservation.objects.filter(statut = "validee")
liste_reservation = []
for reservation in reservations:
print(reservation.statut)
color = None
if reservation.statut == "en_attente":
color = "#ffc107"
@@ -126,6 +86,7 @@ def liste_reservation(request:HttpRequest):
"end": reservation.heure_fin,
"color": color,
})
return JsonResponse(liste_reservation, safe=False)
@login_required
@@ -135,25 +96,20 @@ def liste_reservation_attente(request):
{
**model_to_dict(reservation),
"employe": f"{reservation.employe.user.first_name} {reservation.employe.user.last_name}",
"employe_id": reservation.employe.user.id,
"salle": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
'statut': dict(Reservation.STATUT).get(reservation.statut),
"salle": dict(Reservation.TYPE_CHOICES).get(reservation.salle)
} for reservation in reservations
]
return JsonResponse(liste_reservation, safe=False)
def detail_reservation(request:HttpRequest, reservation_id:int):
reservation = Reservation.objects.get(id=reservation_id)
employe = reservation.employe.user
reservation_json = {
'id_reservation': reservation_id,
'employe': f"{employe.first_name} {employe.last_name}",
'salle': reservation.salle,
'statut': dict(Reservation.STATUT).get(reservation.statut),
'statut': reservation.statut,
'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'),
@@ -161,6 +117,7 @@ def detail_reservation(request:HttpRequest, reservation_id:int):
'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)
@@ -208,27 +165,19 @@ 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.")
reservation = Reservation.objects.get(id=reservation_id)
except reservation.DoesNotExist:
messages.error(request, "La resevertion selectionné n'existe pas.")
return redirect("salle")
reservation.statut = 'validee'
reservation.save()
fonctions_utilitaire.envoyer_mail(
sujet = "Reservation de salle",
message = f"""Bonjour {request.user.first_name} {request.user.last_name}, votre reservation de la salle {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} au {reservation.date_fin.strftime('%d/%m/%Y')} pour motif "{reservation.motif_reservation}" a été validée. Veuillez vous connecter à la plateforme pour plus de détails.""",
destinataires = [reservation.employe.user.email]
)
messages.success(request, f"La réservation de {request.user.first_name} {request.user.last_name} validée avec succès.")
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
@@ -238,16 +187,9 @@ def refuser_reservation(request:HttpRequest):
reservation_id = data.get("id_reservation")
try:
reservation = Reservation.objects.get(id=reservation_id)
except Reservation.DoesNotExist:
except Reservation.DoesNotExist and ValueError:
return JsonResponse({"message": "La resevertion selectionné n'existe pas."})
else:
reservation.statut = "refusee"
reservation.save()
fonctions_utilitaire.envoyer_mail(
sujet = "Reservation de salle",
message = f"""Bonjour {request.user.first_name} {request.user.last_name}, votre reservation de la salle {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} au {reservation.date_fin.strftime('%d/%m/%Y')} pour motif "{reservation.motif_reservation}" a été refusée. Veuillez vous connecter à l'Assistante de Direction pour plus de détails.""",
destinataires = [reservation.employe.user.email]
)
return JsonResponse({"message": "Réservation refusée avec succès."})
return JsonResponse({"message": "Réservation refusée avec succès."})

View File

@@ -1,4 +1,3 @@
<<<<<<< HEAD
asgiref==3.11.1
certifi==2026.4.22
charset-normalizer==3.4.7
@@ -11,7 +10,6 @@ numpy==2.2.6
pandas==2.3.3
pillow==12.2.0
python-dateutil==2.9.0.post0
python-decouple==3.8
pytz==2026.1.post1
requests==2.33.1
six==1.17.0
@@ -19,161 +17,5 @@ sqlparse==0.5.5
typing_extensions==4.15.0
tzdata==2026.2
urllib3==2.6.3
webservices==0.7
webservices.naudit==0.7.2
python-decouple
gunicorn
=======
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
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
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
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
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
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
>>>>>>> c28b14f (clean: remove pycache from tracking)

View File

@@ -3,19 +3,7 @@ const $ = (element) => document.getElementById(element);
const url_liste_conge_attente = $("liste-demande-conges").dataset.url
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
// layout : "fitColumns",
=======
layout : "fitColumns",
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
layout : "fitColumns",
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
layout : "fitColumns",
>>>>>>> 6026af5 (Bug : Affichages des conges)
columns: [
{"title": "Nom et Prénom", "field": "prenom_nom"},
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
@@ -27,19 +15,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
outputFormat:"dd/MM/yy",
}},
{"title": "Type de congé", "field": "type"},
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
{"title": "Date de la demande", "field": "date_demande", responsive: 1},
=======
{"title": "Date de la demande", "field": "date_demande"},
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
{"title": "Date de la demande", "field": "date_demande"},
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
{"title": "Date de la demande", "field": "date_demande"},
>>>>>>> 6026af5 (Bug : Affichages des conges)
{"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{
allowEmpty : true ,
}},
@@ -48,19 +24,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
}},
],
pagination: true,
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
paginationSize: 5,
=======
paginationSize: 5
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
paginationSize: 5
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
paginationSize: 5
>>>>>>> 6026af5 (Bug : Affichages des conges)
})
const bouton_demande_conges = $("bouton-demande-conge");

View File

@@ -12,16 +12,6 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
// ajaxURL: url_liste_employe,
pagination: true,
paginationSize: 10,
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
responsiveLayout : "hide"
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
})
fetch(url_liste_employe)

View File

@@ -16,42 +16,6 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
],
ajaxURL: url_certificat,
})
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData();
formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value);
formData.append("telephone", $("telephone").value);
formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value);
fetch(url, {
method: "POST",
headers: {
"X-CSRFToken": csrftoken
},
body: formData
})
.then(response => response.json())
.then(data => alert(data.message))
.catch(error => console.error("Erreur:", error));
});
=======
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => {
@@ -75,13 +39,4 @@ enregistrerProfil.addEventListener("click", (e) => {
})
.then(response => response.json())
.then(data => alert(data.message))
<<<<<<< HEAD
<<<<<<< HEAD
})
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
})
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
})
>>>>>>> 6026af5 (Bug : Affichages des conges)
})

View File

@@ -1,14 +1,4 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
let table;
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur');
@@ -30,43 +20,4 @@ btnEnregistrerBailleur.addEventListener('click', function() {
alert('Ce bailleur existe déjà dans la base de données.');
}
});
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
});
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);
}
}
});
});
=======
});
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
});
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
});
>>>>>>> 6026af5 (Bug : Affichages des conges)
});

View File

@@ -15,18 +15,7 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
.then(response => response.json())
.then(data => {
currentReservationId = data.id_reservation;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
console.log(data);
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
console.log(data);
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
console.log(data);
>>>>>>> 6026af5 (Bug : Affichages des conges)
$("id_reservation_detail").value = data.id_reservation;
$("id_reservation_refus").value = data.id_reservation;
$("id_reservation_zoom").value = data.id_reservation;
@@ -41,30 +30,11 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
$("besoin_ordinateur").checked=data.besoin_ordinateur;
$("lien_zoom").value=data.lien_zoom;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
if (data.besoin_zoom === false){
$("lien_zoom_container").className = "d-none";
}
=======
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
if(data.statut !== "annulee"){
$("motif_refus_container").className = "d-none";
}else{
$("motif_refus").value=data.motif_refus;
}
<<<<<<< HEAD
<<<<<<< HEAD
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
})
}
});
@@ -110,20 +80,6 @@ $("bouton-annuler").addEventListener("click", (e) => {
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
const url_annuler = $("formulaire-details").dataset.urlannuler;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
console.log("URL d'annulation :");
console.log($("formulaire-details"))
e.preventDefault();
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
fetch(
url_annuler,
{
@@ -174,18 +130,6 @@ if($("refuserReservation")){
const url = e.currentTarget.dataset.lienrefus;
const idRes = $("id_reservation_detail").value;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
console.log(idRes);
e.preventDefault();
=======
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
fetch(url, {
method: "POST",
headers: {
@@ -217,45 +161,16 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
tableau_reservation_attente.on("rowClick", (row, rowData) => {
const data = rowData.getData();
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
=======
console.log(data);
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
console.log(data);
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
console.log(data);
>>>>>>> 6026af5 (Bug : Affichages des conges)
if(data.besoin_zoom === false){
$("lien_zoom_container").className = 'd-none';
}
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
const id_user = $("current-user-id").dataset.userid;
=======
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
if(data.statut !== "refusee"){
$("motif_refus_container").className = 'd-none';
}
<<<<<<< HEAD
<<<<<<< HEAD
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
>>>>>>> 6026af5 (Bug : Affichages des conges)
$("id_reservation_detail").value = data.id;
$("id_reservation_refus").value = data.id;
$("id_reservation_zoom").value = data.id;
@@ -263,44 +178,16 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
$("employe").value=data.employe;
$("salle").value=data.salle;
$("statut-reservation").innerHTML=data.statut;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
$("date_debut").value = data.date_debut;
$("date_fin").value = data.date_fin;
=======
$("date_evenement").value=data.date_debut;
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
$("date_evenement").value=data.date_debut;
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
$("date_evenement").value=data.date_debut;
>>>>>>> 6026af5 (Bug : Affichages des conges)
$("heure_debut").value=data.heure_debut;
$("heure_fin").value=data.heure_fin;
$("motif_reservation").value=data.motif_reservation;
$("besoin_zoom").checked=data.besoin_zoom;
$("besoin_ordinateur").checked=data.besoin_ordi;
$("lien_zoom").value=data.lien_zoom;
<<<<<<< HEAD
<<<<<<< HEAD
<<<<<<< HEAD
if (id_user != data.employe_id){
$("bouton-annuler").className = "d-none";
}
=======
$("motif_refus").value=data.motif_refus;
>>>>>>> 0047b1f (Ajout de la prise en charge des photos de profil)
=======
$("motif_refus").value=data.motif_refus;
>>>>>>> f6b90e7 (Verification du statut avant la creation d'un contrat)
=======
$("motif_refus").value=data.motif_refus;
>>>>>>> 6026af5 (Bug : Affichages des conges)
const modal = new bootstrap.Modal($("modalDetailReservation"));
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
modal.show();
})
})