Compare commits
94 Commits
reglage_co
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 48ad8c772c | |||
| 42a5db25d6 | |||
| eea085af2e | |||
|
|
fd5b3b86f7 | ||
|
|
0abbb26149 | ||
| f061f482d9 | |||
| 622b412a21 | |||
| 54dc65f0d8 | |||
|
|
29a42ee6be | ||
| 904954c735 | |||
| 99e764ab4d | |||
| 0eb745d5f3 | |||
| 097648c03e | |||
| 1a21998c39 | |||
| 900343a4f8 | |||
| 3c5c58b5d1 | |||
| 8b9e73f83b | |||
| 8f822fbe85 | |||
| 411f2c43e3 | |||
| 7826f517f5 | |||
| 0936d6ad02 | |||
| 472774d78c | |||
| d5a0f1104e | |||
| b9d6ab1186 | |||
| 620a01ec27 | |||
| f672b9e9ba | |||
| 7b3ba53e97 | |||
| cb4470e481 | |||
| dfb262809a | |||
| 8f91a0ec87 | |||
|
|
65ecef52b5 | ||
| 13e9d7dd19 | |||
| 88de14aabc | |||
| 0576443f02 | |||
| e306b1d67d | |||
| f4663c6429 | |||
| 6497d5ce90 | |||
| a10d52e2df | |||
| 2a3a330d38 | |||
| b4fb5bbd91 | |||
| 098097ea54 | |||
| 3abdf9ad6f | |||
| 4338204f48 | |||
| ac4e21c336 | |||
| c51ea4d334 | |||
| b88a251694 | |||
| 15b48d3db6 | |||
| c69cf6c8f3 | |||
| 8456a39226 | |||
| bb4d47c6b1 | |||
| d721af865a | |||
| fd74c06251 | |||
| be357aefa5 | |||
| e06ee9a434 | |||
| a89fdd7216 | |||
| 955083da2b | |||
| f6715d62d3 | |||
| 17f0b4f8dc | |||
| e5309927a5 | |||
| 16b4489088 | |||
| 117bbe255b | |||
| c35cfc50d4 | |||
| d464d19981 | |||
| f7ce6065ac | |||
| 9a44d5440a | |||
| cad7c94541 | |||
| ef86e03c37 | |||
| c14170caef | |||
| 559158257e | |||
| a916e12584 | |||
| 63543143c5 | |||
| 12b0390306 | |||
| 8478262c80 | |||
| 078f164c8b | |||
| 39d9dc810f | |||
| b14ec02434 | |||
| 6bb978ab7e | |||
| 81e8fa3901 | |||
| 2948f12b89 | |||
| b88a21dc5d | |||
| 486ba88885 | |||
| 5fcc163dae | |||
| 6857c8b4ac | |||
| 31389e677e | |||
| 500479fd46 | |||
| b3ead5eb73 | |||
| d9670dd1b3 | |||
| f65ac75710 | |||
| 741519c341 | |||
| 48f069506a | |||
| 3693ef29f9 | |||
| c28b14fb98 | |||
| 29a93e9bfe | |||
| a97c233ddb |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,7 +1,8 @@
|
|||||||
db.sqlite3
|
|
||||||
venv/
|
venv/
|
||||||
media/
|
media/
|
||||||
staticfiles/
|
staticfiles/
|
||||||
.env
|
.env
|
||||||
migrations/
|
migrations/
|
||||||
*.pyc
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
db.sqlite3
|
||||||
|
|||||||
3
Jenkinsfile
vendored
3
Jenkinsfile
vendored
@@ -2,7 +2,6 @@ pipeline
|
|||||||
{
|
{
|
||||||
agent any
|
agent any
|
||||||
options {
|
options {
|
||||||
// This is required if you want to clean before build
|
|
||||||
skipDefaultCheckout(true)
|
skipDefaultCheckout(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,7 +16,7 @@ pipeline
|
|||||||
{
|
{
|
||||||
steps
|
steps
|
||||||
{
|
{
|
||||||
sh 'echo "Debut du pipeline"'
|
sh 'echo "Debut du pipeline test"'
|
||||||
cleanWs()
|
cleanWs()
|
||||||
checkout scm
|
checkout scm
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
BIN
SIRH/__pycache__/sso.cpython-310.pyc
Normal file
BIN
SIRH/__pycache__/sso.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/5.2/ref/settings/
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from decouple import config
|
from decouple import config
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
@@ -24,7 +25,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent
|
|||||||
SECRET_KEY = config('SECRET_KEY')
|
SECRET_KEY = config('SECRET_KEY')
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = config('DEBUG', default=True, cast=bool)
|
DEBUG = config('DEBUG', default=False, cast=bool)
|
||||||
|
|
||||||
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',')
|
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',')
|
||||||
#CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]).split(",")
|
#CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]).split(",")
|
||||||
@@ -78,17 +79,16 @@ WSGI_APPLICATION = 'SIRH.wsgi.application'
|
|||||||
# Database
|
# Database
|
||||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||||
|
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
'ENGINE': 'django.db.backends.mysql',
|
||||||
'NAME': config("DB_NAME"),
|
'NAME': config("DB_NAME"),
|
||||||
'USER': config("DB_USER"),
|
'USER': config("DB_USER"),
|
||||||
'PASSWORD': config("DB_PASSWORD"),
|
'PASSWORD': config("DB_PASSWORD"),
|
||||||
'HOST': config("DB_HOST"),
|
'HOST': config("DB_HOST"),
|
||||||
'PORT': config("DB_PORT", cast=int),
|
'PORT': config("DB_PORT", cast=int),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# SGL/sso.py
|
# SGL/sso.py
|
||||||
from gestion_employe.models import Employe, Departement
|
|
||||||
from simple_sso.sso_client.client import Client
|
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
|
from gestion_employe.models import Departement, Employe
|
||||||
|
from simple_sso.sso_client.client import Client
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
@@ -10,8 +10,6 @@ class SIRHClient(Client):
|
|||||||
def build_user(self, user_data):
|
def build_user(self, user_data):
|
||||||
"""Crée le User et son Profile avec les infos reçues du serveur."""
|
"""Crée le User et son Profile avec les infos reçues du serveur."""
|
||||||
user_data_copy = user_data.copy()
|
user_data_copy = user_data.copy()
|
||||||
|
|
||||||
print("Un petit test", user_data_copy)
|
|
||||||
|
|
||||||
user_data.pop("departement", None)
|
user_data.pop("departement", None)
|
||||||
user_data.pop("chef_departement", None)
|
user_data.pop("chef_departement", None)
|
||||||
|
|||||||
@@ -14,10 +14,10 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
{% include 'parts/menu_application.html' %}
|
||||||
<div class="row flex-nowrap">
|
<div class="row flex-nowrap">
|
||||||
{% include 'parts/menu_application.html' %}
|
|
||||||
{% include 'parts/menu_principal.html' %}
|
{% include 'parts/menu_principal.html' %}
|
||||||
<div class="col p-4">
|
<div class="col p-1 p-sm-4">
|
||||||
{% block 'contenu' %} {% endblock %}
|
{% block 'contenu' %} {% endblock %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,99 @@
|
|||||||
<div class="col-1 bg-danger border d-flex flex-column vh-100 pt-5 sticky-top">
|
{% load static %}
|
||||||
<a href="{{ SIRH_LIEN }}" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em">
|
{% load tags_personnaliser %}
|
||||||
<i class="bi bi-people-fill fs-1 d-block"></i>SIRH
|
|
||||||
</a>
|
<nav class="navbar d-none d-lg-block navbar-expand-lg bg-body-tertiary">
|
||||||
<a href="{{ SGL_LIEN }}" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em">
|
<div class="container-fluid">
|
||||||
<i class="bi bi-building-gear fs-1 d-block"></i> SGL
|
<a class="navbar-brand" href="#">
|
||||||
</a>
|
<img src="{% static 'img/cerfig.jpg' %}" alt="Logo" width="85" height="30" class="d-inline-block align-text-top">
|
||||||
</div>
|
</a>
|
||||||
|
<div class="collapse navbar-collapse">
|
||||||
|
<ul class="nav nav-tabs justify-content-end w-100">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" aria-current="page" href="{{ SIRH_LIEN }}">SIRH</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ SGL_LIEN }}">SGL</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#">Laboratoire</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<nav class="navbar d-lg-none navbar-expand-lg bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">
|
||||||
|
<img src="{% static 'img/cerfig.jpg' %}" alt="Logo" width="85" height="30" class="d-inline-block align-text-top">
|
||||||
|
</a>
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavDropdown" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNavDropdown">
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" aria-current="page" href="{% url 'gestion_employe:mon-profil' %}">
|
||||||
|
<i class="bi bi-person-circle"></i>
|
||||||
|
Mon profil
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% if user|has_group:"ressource_humaine" or user|has_group:"direction" %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" aria-current="page" href="{% url 'gestion_employe:index' %}">
|
||||||
|
<i class="bi bi-speedometer2"></i>
|
||||||
|
Tableau de bord
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if user|has_group:"ressource_humaine" or user|has_group:"direction" %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" aria-current="page" href="{% url 'gestion_projet:index' %}">
|
||||||
|
<i class="bi bi-speedometer2"></i>
|
||||||
|
Gestion des projets
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'gestion_conges:conge' %}">
|
||||||
|
<i class="bi bi-airplane"></i>
|
||||||
|
Gestion des congés
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% if user|is_chef_projet %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'gestion_projet:activites-projet' %}">
|
||||||
|
<i class="bi bi-list-task"></i>
|
||||||
|
Suivi des Activités
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'gestion_salle:reservation-salle' %}">
|
||||||
|
<i class="bi bi-calendar-check"></i>
|
||||||
|
Réservation
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'deconnexion' %}">
|
||||||
|
<i class="bi bi-box-arrow-right"></i> Déconnexion
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="container-fluid d-flex d-sm-none justify-content-center">
|
||||||
|
<ul class="nav nav-tabs">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" aria-current="page" href="{{ SIRH_LIEN }}">SIRH</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{{ SGL_LIEN }}">SGL</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="#">Laboratoire</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% load tags_personnaliser %}
|
{% load tags_personnaliser %}
|
||||||
|
|
||||||
<div class="col-3 bg-danger d-flex flex-column vh-100 pt-5 sticky-top">
|
<div class="col-3 bg-danger d-none d-sm-flex flex-column vh-100 pt-5 sticky-top">
|
||||||
<div class="text-center mb-4">
|
<div class="text-center mb-4">
|
||||||
{% if user.employe.photo %}
|
{% if user.employe.photo %}
|
||||||
<img src="{{ user.employe.photo.url }}"
|
<img src="{{ user.employe.photo.url }}"
|
||||||
@@ -41,7 +41,6 @@
|
|||||||
<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.2em;">
|
||||||
<i class="bi bi-calendar-check"></i> Réservation
|
<i class="bi bi-calendar-check"></i> Réservation
|
||||||
</a>
|
</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.2em;">
|
||||||
<i class="bi bi-box-arrow-right"></i> Déconnexion
|
<i class="bi bi-box-arrow-right"></i> Déconnexion
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ Including another URLconf
|
|||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
# from django.conf.urls.static import static
|
|
||||||
from django.conf import settings
|
|
||||||
from . import views
|
from . import views
|
||||||
from .sso import SIRHClient
|
from .sso import SIRHClient
|
||||||
|
|
||||||
@@ -59,4 +59,3 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path("client/", include(client_sirh.get_urls())),
|
path("client/", include(client_sirh.get_urls())),
|
||||||
]
|
]
|
||||||
# + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ from django.contrib.auth import logout
|
|||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
def connexion(request):
|
def connexion(request):
|
||||||
"""Redirige vers le serveur SSO pour l'authentification."""
|
"""Redirige vers le serveur SSO pour l'authentification."""
|
||||||
@@ -12,5 +10,4 @@ def connexion(request):
|
|||||||
def deconnexion(request):
|
def deconnexion(request):
|
||||||
"""Gérez la déconnexion des utilisateurs."""
|
"""Gérez la déconnexion des utilisateurs."""
|
||||||
logout(request)
|
logout(request)
|
||||||
#return redirect(settings.SSO_LOGOUT_URL)
|
|
||||||
return redirect("http://authentification.cerfig.org/login")
|
return redirect("http://authentification.cerfig.org/login")
|
||||||
|
|||||||
BIN
__pycache__/contexte_universel.cpython-310.pyc
Normal file
BIN
__pycache__/contexte_universel.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,9 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.core.mail import send_mail
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from gestion_employe.models import Contrat, Employe
|
|
||||||
from gestion_conge.models import Conge
|
|
||||||
|
|
||||||
|
from gestion_conge.models import Conge
|
||||||
|
from gestion_employe.models import Contrat, Employe
|
||||||
|
|
||||||
QUOTA_CONGE_ANNUEL = 30
|
QUOTA_CONGE_ANNUEL = 30
|
||||||
NOMBRE_PAGINATION = 8
|
NOMBRE_PAGINATION = 8
|
||||||
@@ -13,6 +15,21 @@ try:
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
EMAIL_EQUIPE_INFORMATIQUE = list(
|
||||||
|
Employe.objects.filter(departement__nom="Systeme informatique").values_list('user__email', flat=True)
|
||||||
|
) or ["support@cerfig.org"]
|
||||||
|
except:
|
||||||
|
EMAIL_EQUIPE_INFORMATIQUE = ["support@cerfig.org"]
|
||||||
|
|
||||||
|
def est_equipe_informatique(employe):
|
||||||
|
"""Vérifie si un employé appartient au département informatique"""
|
||||||
|
return employe.departement and employe.departement.nom == "Systeme informatique"
|
||||||
|
|
||||||
|
def est_assistante_direction(employe):
|
||||||
|
"""Vérifie si un employé est l'assistante de direction"""
|
||||||
|
return employe.fonction == "assistant_direction"
|
||||||
|
|
||||||
def solde_conge(employe):
|
def solde_conge(employe):
|
||||||
"""Fonction de calcul du solde de congé restant l'employé"""
|
"""Fonction de calcul du solde de congé restant l'employé"""
|
||||||
contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first()
|
contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first()
|
||||||
@@ -38,19 +55,6 @@ def solde_conge(employe):
|
|||||||
"nombre_jours_valide": jours_conges_valider
|
"nombre_jours_valide": jours_conges_valider
|
||||||
}
|
}
|
||||||
|
|
||||||
def envoyer_mail(sujet, message, destinataires):
|
|
||||||
"""Fonction d'envoi de mail"""
|
|
||||||
from django.core.mail import send_mail
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
send_mail(
|
|
||||||
sujet,
|
|
||||||
message,
|
|
||||||
settings.EMAIL_HOST_USER,
|
|
||||||
destinataires,
|
|
||||||
fail_silently=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def destinataire_mail_demande_conges(employe):
|
def destinataire_mail_demande_conges(employe):
|
||||||
"""Fonction de récupération des destinataires pour les mails de demande de congés"""
|
"""Fonction de récupération des destinataires pour les mails de demande de congés"""
|
||||||
if employe.chef:
|
if employe.chef:
|
||||||
@@ -59,3 +63,14 @@ def destinataire_mail_demande_conges(employe):
|
|||||||
if employe.departement:
|
if employe.departement:
|
||||||
chefs_departement = Employe.objects.filter(departement=employe.departement, chef=True)
|
chefs_departement = Employe.objects.filter(departement=employe.departement, chef=True)
|
||||||
return list(chefs_departement.values_list('user__email', flat=True))
|
return list(chefs_departement.values_list('user__email', flat=True))
|
||||||
|
|
||||||
|
def envoyer_mail(sujet, message, destinataires):
|
||||||
|
"""Fonction d'envoi de mail"""
|
||||||
|
|
||||||
|
send_mail(
|
||||||
|
sujet,
|
||||||
|
message,
|
||||||
|
settings.EMAIL_HOST_USER,
|
||||||
|
destinataires,
|
||||||
|
fail_silently=False,
|
||||||
|
)
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,11 +1,13 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from .models import Conge
|
from .models import Conge
|
||||||
|
|
||||||
|
|
||||||
class CongeForm(forms.ModelForm):
|
class CongeForm(forms.ModelForm):
|
||||||
"""Formulaire de demande de congé."""
|
"""Formulaire de demande de congé."""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Conge
|
model = Conge
|
||||||
fields =['type', 'date_debut', 'date_fin']
|
fields =('type', 'date_debut', 'date_fin')
|
||||||
widgets = {
|
widgets = {
|
||||||
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||||
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.13 on 2026-06-16 11:06
|
# Generated by Django 5.2.13 on 2026-09-03 14:33
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from gestion_employe.models import Employe
|
from gestion_employe.models import Employe
|
||||||
|
|
||||||
|
|
||||||
class Conge(models.Model):
|
class Conge(models.Model):
|
||||||
"""Modèle de création des congés"""
|
"""Modèle de création des congés"""
|
||||||
TYPE_CHOICES = [
|
TYPE_CHOICES = (
|
||||||
# ('maladie', 'Maladie'),
|
|
||||||
('conge_annuel', 'Conge Annuel'),
|
('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 = models.ForeignKey(
|
||||||
Employe,
|
Employe,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ const $ = (element) => document.getElementById(element);
|
|||||||
const url_liste_conge_attente = $("liste-demande-conges").dataset.url
|
const url_liste_conge_attente = $("liste-demande-conges").dataset.url
|
||||||
|
|
||||||
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
|
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
|
||||||
// layout : "fitColumns",
|
|
||||||
columns: [
|
columns: [
|
||||||
{"title": "Nom et Prénom", "field": "prenom_nom"},
|
{"title": "Nom et Prénom", "field": "prenom_nom"},
|
||||||
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
|
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="row d-flex justify-content-center mb-4">
|
<div class="row d-flex flex-column flex-lg-row justify-content-center mb-4">
|
||||||
<div class="col text-white bg-danger d-flex flex-column justify-content-center align-items-center border rounded p-4">
|
<div class="col text-white bg-danger d-flex flex-column justify-content-center align-items-center border rounded p-4">
|
||||||
<div class="card-header fw-bold">
|
<div class="card-header fw-bold">
|
||||||
<i class="bi bi-x-circle me-2"></i> Congés refusés
|
<i class="bi bi-x-circle me-2"></i> Congés refusés
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
<h5 class="fw-bold">{{ nombre_conges_refuse }}</h5>
|
<h5 class="fw-bold">{{ nombre_conges_refuse }}</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-white bg-warning d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-2">
|
<div class="col my-2 my-lg-0 text-white bg-warning d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-0 mx-lg-2">
|
||||||
<div class="card-header fw-bold">
|
<div class="card-header fw-bold">
|
||||||
<i class="bi bi-hourglass-split me-2"></i> Congés en attente
|
<i class="bi bi-hourglass-split me-2"></i> Congés en attente
|
||||||
</div>
|
</div>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col d-flex justify-content-between align-items-center">
|
<div class="col d-flex flex-column flex-lg-row justify-content-between align-items-center">
|
||||||
<h3><i class="bi bi-list-ul"></i> Liste des demandes de congé</h3>
|
<h3><i class="bi bi-list-ul"></i> Liste des demandes de congé</h3>
|
||||||
<button class='btn btn-primary' id="bouton-demande-conge">Demande de congé</button>
|
<button class='btn btn-primary' id="bouton-demande-conge">Demande de congé</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
import json
|
import json
|
||||||
from django.http import JsonResponse
|
|
||||||
from django.shortcuts import redirect, render
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
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 gestion_conge.forms import CongeForm
|
from gestion_conge.forms import CongeForm
|
||||||
from gestion_employe.models import Affectation, Employe
|
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
|
from .models import Conge
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def index(request):
|
def index(request):
|
||||||
"""Vue de gestion de l'index"""
|
"""Vue de gestion de l'index"""
|
||||||
@@ -115,10 +119,9 @@ def demander_conge(request):
|
|||||||
conge_obj = form.save(commit=False)
|
conge_obj = form.save(commit=False)
|
||||||
conge_obj.employe = employe
|
conge_obj.employe = employe
|
||||||
|
|
||||||
if conge_obj.type == "conge_annuel":
|
if conge_obj.type == "conge_annuel" and retour_quota["nombre_jours_valide"] + conge_obj.nombre_jours > quota_annuel:
|
||||||
if retour_quota["nombre_jours_valide"] + conge_obj.nombre_jours > quota_annuel:
|
messages.error(request, "Quota annuel dépassé (30 jours max).")
|
||||||
messages.error(request, "Quota annuel dépassé (30 jours max).")
|
return redirect("gestion_conges:conge")
|
||||||
return redirect("gestion_conges:conge")
|
|
||||||
|
|
||||||
conge_obj.save()
|
conge_obj.save()
|
||||||
messages.success(request, "Votre demande de congé a été enregistrée.")
|
messages.success(request, "Votre demande de congé a été enregistrée.")
|
||||||
@@ -126,7 +129,7 @@ def demander_conge(request):
|
|||||||
fonctions_utilitaire.envoyer_mail(
|
fonctions_utilitaire.envoyer_mail(
|
||||||
sujet = "Demande de congé",
|
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.""",
|
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]
|
destinataires = fonctions_utilitaire.destinataire_mail_demande_conges(employe)
|
||||||
)
|
)
|
||||||
|
|
||||||
return redirect("gestion_conges:conge")
|
return redirect("gestion_conges:conge")
|
||||||
@@ -219,13 +222,13 @@ def validation_de_conge(request):
|
|||||||
motif_refus = request_data.get("motif_refus", "")
|
motif_refus = request_data.get("motif_refus", "")
|
||||||
|
|
||||||
if validation_hierarchique is not None:
|
if validation_hierarchique is not None:
|
||||||
conge.validation_hierarchique = True if validation_hierarchique == "valide" else False
|
conge.validation_hierarchique = validation_hierarchique == "valide"
|
||||||
if validation_hierarchique == "refuse" and not motif_refus:
|
if validation_hierarchique == "refuse" and not motif_refus:
|
||||||
return JsonResponse({"message": "Veuillez fournir un motif de refus."})
|
return JsonResponse({"message": "Veuillez fournir un motif de refus."})
|
||||||
conge.motif_refus = motif_refus if validation_hierarchique == "refuse" else ""
|
conge.motif_refus = motif_refus if validation_hierarchique == "refuse" else ""
|
||||||
|
|
||||||
if validation_direction is not None:
|
if validation_direction is not None:
|
||||||
conge.validation_direction = True if validation_direction == "valide" else False
|
conge.validation_direction = validation_direction == "valide"
|
||||||
if validation_direction == "refuse" and not motif_refus:
|
if validation_direction == "refuse" and not motif_refus:
|
||||||
return JsonResponse({"message": "Veuillez fournir un motif de refus."})
|
return JsonResponse({"message": "Veuillez fournir un motif de refus."})
|
||||||
conge.motif_refus = motif_refus if validation_direction == "refuse" else ""
|
conge.motif_refus = motif_refus if validation_direction == "refuse" else ""
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,8 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Departement, Employe
|
from .models import Departement, Employe
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Departement)
|
@admin.register(Departement)
|
||||||
class DepartementAdmin(admin.ModelAdmin):
|
class DepartementAdmin(admin.ModelAdmin):
|
||||||
list_display = ("nom", )
|
list_display = ("nom", )
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from .models import Contrat, Departement, Employe,Affectation,Formation
|
|
||||||
|
from .models import Affectation, Contrat, Departement, Employe, Formation
|
||||||
|
|
||||||
|
|
||||||
class EmployeForm(forms.ModelForm):
|
class EmployeForm(forms.ModelForm):
|
||||||
"""Formulaire pour modifier les informations de profil d'un employé"""
|
"""Formulaire pour modifier les informations de profil d'un employé"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Employe
|
model = Employe
|
||||||
fields = (
|
fields = (
|
||||||
|
'departement',
|
||||||
|
'fonction',
|
||||||
'adresse',
|
'adresse',
|
||||||
'telephone',
|
'telephone',
|
||||||
'CV',
|
'CV',
|
||||||
@@ -13,7 +17,18 @@ class EmployeForm(forms.ModelForm):
|
|||||||
'rib',
|
'rib',
|
||||||
'photo',
|
'photo',
|
||||||
'casier_judiciaire'
|
'casier_judiciaire'
|
||||||
)
|
)
|
||||||
|
widgets = {
|
||||||
|
'departement': forms.Select(attrs={'class': 'form-select'}),
|
||||||
|
'fonction': forms.Select(attrs={'class': 'form-select'}),
|
||||||
|
'adresse': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'telephone': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'CV': forms.FileInput(attrs={'class': 'form-control'}),
|
||||||
|
'diplome': forms.FileInput(attrs={'class': 'form-control'}),
|
||||||
|
'rib': forms.FileInput(attrs={'class': 'form-control'}),
|
||||||
|
'photo': forms.FileInput(attrs={'class': 'form-control'}),
|
||||||
|
'casier_judiciaire': forms.FileInput(attrs={'class': 'form-control'}),
|
||||||
|
}
|
||||||
|
|
||||||
class AffectationForm(forms.ModelForm):
|
class AffectationForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -37,7 +52,7 @@ class AffectationForm(forms.ModelForm):
|
|||||||
class ContratForm(forms.ModelForm):
|
class ContratForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Contrat
|
model = Contrat
|
||||||
fields = [
|
fields = (
|
||||||
"numero_contrat",
|
"numero_contrat",
|
||||||
"type_contrat",
|
"type_contrat",
|
||||||
"date_debut",
|
"date_debut",
|
||||||
@@ -45,7 +60,7 @@ class ContratForm(forms.ModelForm):
|
|||||||
"salaire_mensuel",
|
"salaire_mensuel",
|
||||||
"statut",
|
"statut",
|
||||||
"fichier_contrat"
|
"fichier_contrat"
|
||||||
]
|
)
|
||||||
|
|
||||||
widgets = {
|
widgets = {
|
||||||
'numero_contrat': forms.TextInput(attrs={'class': 'form-control'}),
|
'numero_contrat': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
@@ -60,19 +75,19 @@ class ContratForm(forms.ModelForm):
|
|||||||
class DepartementForm(forms.ModelForm):
|
class DepartementForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Departement
|
model = Departement
|
||||||
fields = ['nom']
|
fields = ('nom', )
|
||||||
|
|
||||||
class FormationForm(forms.ModelForm):
|
class FormationForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Formation
|
model = Formation
|
||||||
fields = [
|
fields = (
|
||||||
'titre',
|
'titre',
|
||||||
'organisme',
|
'organisme',
|
||||||
'date_obtention',
|
'date_obtention',
|
||||||
'date_fin',
|
'date_fin',
|
||||||
'description',
|
'description',
|
||||||
'certificat'
|
'certificat'
|
||||||
]
|
)
|
||||||
|
|
||||||
widgets = {
|
widgets = {
|
||||||
'titre': forms.TextInput(attrs={"class": "form-control"}),
|
'titre': forms.TextInput(attrs={"class": "form-control"}),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.13 on 2026-06-16 11:06
|
# Generated by Django 5.2.13 on 2026-09-03 14:33
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from django.db import models
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from gestion_projet.models import Projet
|
from gestion_projet.models import Projet
|
||||||
|
|
||||||
|
|
||||||
class Departement(models.Model):
|
class Departement(models.Model):
|
||||||
"""Modèle représentant un département de l'entreprise."""
|
"""Modèle représentant un département de l'entreprise."""
|
||||||
nom = models.CharField(max_length=100)
|
nom = models.CharField(max_length=100)
|
||||||
@@ -12,7 +14,7 @@ class Departement(models.Model):
|
|||||||
|
|
||||||
class Employe(models.Model):
|
class Employe(models.Model):
|
||||||
"""Modèle représentant un employé de l'entreprise."""
|
"""Modèle représentant un employé de l'entreprise."""
|
||||||
FONCTION_LISTE = [
|
FONCTION_LISTE = (
|
||||||
('directeur', 'Directeur'),
|
('directeur', 'Directeur'),
|
||||||
('assistant_direction', 'Assistante de direction'),
|
('assistant_direction', 'Assistante de direction'),
|
||||||
('assistant_technique_recherche', 'Assistant technique de recherche'),
|
('assistant_technique_recherche', 'Assistant technique de recherche'),
|
||||||
@@ -24,7 +26,7 @@ class Employe(models.Model):
|
|||||||
('qualiticien', 'Qualiticien'),
|
('qualiticien', 'Qualiticien'),
|
||||||
('technicien_surface', 'Technicien de surface'),
|
('technicien_surface', 'Technicien de surface'),
|
||||||
('chauffeur', 'Chauffeur'),
|
('chauffeur', 'Chauffeur'),
|
||||||
]
|
)
|
||||||
|
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
matricule = models.CharField(max_length=10, unique=True, null=True, blank=True)
|
matricule = models.CharField(max_length=10, unique=True, null=True, blank=True)
|
||||||
@@ -52,20 +54,21 @@ class Employe(models.Model):
|
|||||||
|
|
||||||
class Contrat(models.Model):
|
class Contrat(models.Model):
|
||||||
"""Modèle représentant un contrat de travail d'un employé."""
|
"""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_determinee', 'Contrat à Durée Déterminée'),
|
||||||
('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'),
|
('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'),
|
||||||
('contrat_prestation', 'Contrat de Prestation de Service'),
|
('contrat_prestation', 'Contrat de Prestation de Service'),
|
||||||
('contrat_stage', 'Contrat de Stage'),
|
('contrat_stage', 'Contrat de Stage'),
|
||||||
('convention_bourse_entretien', 'Convention de bourse d\'entretien'),
|
('convention_bourse_entretien', 'Convention de bourse d\'entretien'),
|
||||||
]
|
)
|
||||||
|
|
||||||
STATUT_CONTRAT = [
|
STATUT_CONTRAT = (
|
||||||
('actif', 'Actif'),
|
('actif', 'Actif'),
|
||||||
('termine', 'Terminé'),
|
('termine', 'Terminé'),
|
||||||
('suspendu', 'Suspendu'),
|
('suspendu', 'Suspendu'),
|
||||||
('rupture_contrat', 'Rupture de Contrat'),
|
('rupture_contrat', 'Rupture de Contrat'),
|
||||||
]
|
)
|
||||||
|
|
||||||
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
|
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
|
||||||
numero_contrat = models.CharField(max_length=100, unique=True)
|
numero_contrat = models.CharField(max_length=100, unique=True)
|
||||||
type_contrat = models.CharField(
|
type_contrat = models.CharField(
|
||||||
@@ -89,7 +92,7 @@ class Contrat(models.Model):
|
|||||||
|
|
||||||
class Affectation(models.Model):
|
class Affectation(models.Model):
|
||||||
"""Modèle représentant l'affectation d'un employé à un projet avec un rôle spécifique."""
|
"""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'),
|
('chef_projet', 'Chef de projet'),
|
||||||
('doctorant','Doctorant'),
|
('doctorant','Doctorant'),
|
||||||
('mastorant','Mastorant'),
|
('mastorant','Mastorant'),
|
||||||
@@ -98,7 +101,7 @@ class Affectation(models.Model):
|
|||||||
('laborantin','Laborantin'),
|
('laborantin','Laborantin'),
|
||||||
('medecin','Médecin'),
|
('medecin','Médecin'),
|
||||||
('autre','Autre'),
|
('autre','Autre'),
|
||||||
]
|
)
|
||||||
|
|
||||||
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
|
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
|
||||||
projet = models.ForeignKey(Projet, on_delete=models.CASCADE)
|
projet = models.ForeignKey(Projet, on_delete=models.CASCADE)
|
||||||
@@ -120,4 +123,4 @@ class Formation(models.Model):
|
|||||||
date_fin = models.DateField(blank=True, null=True, verbose_name="Date de fin de validité")
|
date_fin = models.DateField(blank=True, null=True, verbose_name="Date de fin de validité")
|
||||||
certificat = models.FileField(upload_to='documents/formations/', blank=True, null=True, verbose_name="Certificat (PDF/Image)")
|
certificat = models.FileField(upload_to='documents/formations/', blank=True, null=True, verbose_name="Certificat (PDF/Image)")
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.titre
|
return self.titre
|
||||||
|
|||||||
@@ -9,17 +9,15 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
|
|||||||
{"title": "Projet", "field": "projet"},
|
{"title": "Projet", "field": "projet"},
|
||||||
{"title": "Téléphone", "field": "telephone"},
|
{"title": "Téléphone", "field": "telephone"},
|
||||||
],
|
],
|
||||||
// ajaxURL: url_liste_employe,
|
|
||||||
pagination: true,
|
pagination: true,
|
||||||
paginationSize: 10,
|
paginationSize: 10
|
||||||
responsiveLayout : "hide"
|
// responsiveLayout : "hide"
|
||||||
})
|
})
|
||||||
|
|
||||||
fetch(url_liste_employe)
|
fetch(url_liste_employe)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success){
|
if (data.success){
|
||||||
console.log(data.data)
|
|
||||||
tableau_liste_employe.setData(data.data);
|
tableau_liste_employe.setData(data.data);
|
||||||
}else{
|
}else{
|
||||||
alert(data.message);
|
alert(data.message);
|
||||||
@@ -29,6 +27,7 @@ fetch(url_liste_employe)
|
|||||||
|
|
||||||
tableau_liste_employe.on("rowClick", function (row, rowData) {
|
tableau_liste_employe.on("rowClick", function (row, rowData) {
|
||||||
const data = rowData.getData();
|
const data = rowData.getData();
|
||||||
|
|
||||||
document.getElementById('detail-id').value = data.id;
|
document.getElementById('detail-id').value = data.id;
|
||||||
document.getElementById('detail-matricule').value = data.matricule;
|
document.getElementById('detail-matricule').value = data.matricule;
|
||||||
document.getElementById('detail-employe').value = data.employe;
|
document.getElementById('detail-employe').value = data.employe;
|
||||||
@@ -211,23 +210,31 @@ $("enregistrerDetail").addEventListener("click", function() {
|
|||||||
const fonction = document.getElementById('detail-fonction').value;
|
const fonction = document.getElementById('detail-fonction').value;
|
||||||
const dateEmbauche = document.getElementById('detail-dateEmbauche').value;
|
const dateEmbauche = document.getElementById('detail-dateEmbauche').value;
|
||||||
const matricule = document.getElementById('detail-matricule').value;
|
const matricule = document.getElementById('detail-matricule').value;
|
||||||
|
const departement = document.getElementById('detail-departement').value;
|
||||||
|
|
||||||
const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
|
const url_enregistrer_detail = document.getElementById("modalDetailEmployeBody").dataset.url;
|
||||||
|
|
||||||
|
console.log("Le lien de l'enregistrement :", document.getElementById("modalDetailEmployeBody").dataset)
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
"id": id_,
|
||||||
|
"fonction": fonction,
|
||||||
|
"date_embauche": dateEmbauche,
|
||||||
|
"matricule": matricule,
|
||||||
|
"departement": departement
|
||||||
|
};
|
||||||
|
|
||||||
fetch(url_enregistrer_detail, {
|
fetch(url_enregistrer_detail, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
|
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(payload)
|
||||||
"id": id_,
|
|
||||||
"fonction": fonction,
|
|
||||||
"date_embauche": dateEmbauche,
|
|
||||||
"matricule": matricule
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
console.log('Réponse du serveur:', data);
|
||||||
alert(data.message);
|
alert(data.message);
|
||||||
location.reload();
|
location.reload();
|
||||||
})
|
})
|
||||||
@@ -261,4 +268,70 @@ const listeContratExpirant = new Tabulator("#listeContratExpirant", {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
|
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/* let tabContrats = null;
|
||||||
|
|
||||||
|
document.getElementById("modalContratsEmploye").addEventListener("show.bs.modal", function () {
|
||||||
|
|
||||||
|
const employeId = document.getElementById("detail-id").value;
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}); */
|
||||||
|
|||||||
@@ -13,39 +13,40 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
|
|||||||
inputFormat:"yyyy-MM-dd",
|
inputFormat:"yyyy-MM-dd",
|
||||||
outputFormat:"dd/MM/yy",
|
outputFormat:"dd/MM/yy",
|
||||||
}},
|
}},
|
||||||
|
{"title": "Fichier", "field": "certificat", formatter:"link", formatterParams:{
|
||||||
|
label:"Voir le fichier",
|
||||||
|
target:"_blank",
|
||||||
|
}},
|
||||||
],
|
],
|
||||||
ajaxURL: url_certificat,
|
ajaxURL: url_certificat,
|
||||||
})
|
})
|
||||||
const enregistrerProfil = $("enregistrerProfil");
|
const enregistrerProfil = $("enregistrerProfil");
|
||||||
|
|
||||||
// enregistrerProfil.addEventListener("click", (e) => {
|
enregistrerProfil.addEventListener("click", (e) => {
|
||||||
|
const url = $("information-personnelles").dataset.url;
|
||||||
|
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
|
||||||
|
|
||||||
// const url = $("information-personnelles").dataset.url;
|
const formData = new FormData();
|
||||||
// const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
|
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();
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
// formData.append("nom", $("nom").value);
|
headers: {
|
||||||
// formData.append("prenom", $("prenom").value);
|
"X-CSRFToken": csrftoken
|
||||||
// formData.append("email", $("email").value);
|
},
|
||||||
// formData.append("telephone", $("telephone").value);
|
body: formData
|
||||||
// formData.append("adresse", $("adresse").value);
|
})
|
||||||
// formData.append("sexe", $("sexe").value);
|
.then(response => response.json())
|
||||||
// formData.append("date_naissance", $("date_naissance").value);
|
.then(data => alert(data.message))
|
||||||
|
.catch(error => console.error("Erreur:", error));
|
||||||
|
});
|
||||||
// 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));
|
|
||||||
// });
|
|
||||||
@@ -15,12 +15,12 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row d-flex flex-column flex-lg-row">
|
||||||
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
|
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
|
||||||
<span class="fs-5">Nombre d'employés</span>
|
<span class="fs-5">Nombre d'employés</span>
|
||||||
<h3>{{ nombre_employes }}</h3>
|
<h3>{{ nombre_employes }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-white bg-info d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-3">
|
<div class="col text-white bg-info d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-0 mx-lg-3 my-1 my-lg-0">
|
||||||
<span class="fs-5">Nombre de prestataires</span>
|
<span class="fs-5">Nombre de prestataires</span>
|
||||||
<h3>{{ nombre_cps }}</h3>
|
<h3>{{ nombre_cps }}</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col d-flex justify-content-between mb-3">
|
<div class="col d-flex flex-column flex-lg-row justify-content-between mb-3">
|
||||||
<h3>
|
<h3>
|
||||||
<i class="bi bi-people"></i>
|
<i class="bi bi-people"></i>
|
||||||
La liste des employés
|
La liste des employés
|
||||||
@@ -64,4 +64,4 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block 'js' %}
|
{% block 'js' %}
|
||||||
<script type="text/javascript" src="{% static 'gestion_employe/js/index.js' %}"></script>
|
<script type="text/javascript" src="{% static 'gestion_employe/js/index.js' %}"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
{% block 'titre_page' %} Gestion des employés - Mon profil {% endblock %}
|
{% block 'titre_page' %} Gestion des employés - Mon profil {% endblock %}
|
||||||
{% block 'contenu' %}
|
{% block 'contenu' %}
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex flex-column flex-lg-row justify-content-between">
|
||||||
<h5 class="fw-bold">
|
<h5 class="fw-bold">
|
||||||
<i class="bi bi-pencil-square me-2"></i> Les informations de mon profil
|
<i class="bi bi-pencil-square me-2"></i> Les informations de mon profil
|
||||||
</h5>
|
</h5>
|
||||||
@@ -12,19 +12,20 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
|
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show mt-2" role="alert">
|
||||||
{{ message }}
|
{{ message }}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if not expiration_contrat %}
|
{% if not has_contrat %}
|
||||||
<div class="alert alert-danger fade show alert-dismissible mt-2">
|
<div class="alert alert-danger mt-2">
|
||||||
<strong>Important :</strong> Les informations sur votre contrat n'ont pas été renseignées, veuillez contacter les ressources humaines.
|
<strong>Important :</strong> Les informations sur votre contrat n'ont pas été renseignées, veuillez contacter les ressources humaines.
|
||||||
</div>
|
</div>
|
||||||
{% elif contrat_nb_jours_restant %}
|
|
||||||
<div class="alert alert-danger fade show alert-dismissible mt-2">
|
{% elif expiration_contrat %}
|
||||||
|
<div class="alert alert-warning mt-2">
|
||||||
<strong>Important :</strong> Votre contrat de travail expire dans {{ contrat_nb_jours_restant }} jours, veuillez contacter les ressources humaines.
|
<strong>Important :</strong> Votre contrat de travail expire dans {{ contrat_nb_jours_restant }} jours, veuillez contacter les ressources humaines.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
<div class="accordion-body">
|
<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' %}">
|
<form class="row" action = "{% url 'gestion_employe:modifier-employe' %}" method = "POST" enctype = "multipart/form-data" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="col">
|
<div class="col-12 col-sm-6">
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>Photo</label>
|
<label>Photo</label>
|
||||||
{% if employe.photo %}
|
{% if employe.photo %}
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col-12 col-sm-6">
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>Date de naissance :</label>
|
<label>Date de naissance :</label>
|
||||||
<input type="date" class="form-control" id="date_naissance" name = 'date_naissance' value="{{ employe.date_naissance|date:'Y-m-d' }}">
|
<input type="date" class="form-control" id="date_naissance" name = 'date_naissance' value="{{ employe.date_naissance|date:'Y-m-d' }}">
|
||||||
@@ -171,9 +172,9 @@
|
|||||||
<form method="POST" action="{% url 'gestion_employe:enregistrement-document-rh' %}" enctype="multipart/form-data">
|
<form method="POST" action="{% url 'gestion_employe:enregistrement-document-rh' %}" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col-12 col-sm-6">
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>photo</label>
|
<label>Photo</label>
|
||||||
{% if employe.photo %}
|
{% if employe.photo %}
|
||||||
<span>Fichier actuel : <a href="{{ employe.photo.url }}">{{employe.photo}}</a></span>
|
<span>Fichier actuel : <a href="{{ employe.photo.url }}">{{employe.photo}}</a></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -194,7 +195,7 @@
|
|||||||
<input type="file" class="form-control" name="diplome">
|
<input type="file" class="form-control" name="diplome">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col-12 col-sm-6">
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>Casier judiciaire</label>
|
<label>Casier judiciaire</label>
|
||||||
{% if employe.casier_judiciaire %}
|
{% if employe.casier_judiciaire %}
|
||||||
|
|||||||
@@ -25,4 +25,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
@@ -54,4 +54,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>Département :</label>
|
<label>Département :</label>
|
||||||
<input type="text" class="form-control" id="detail-departement">
|
<select class="form-select" id="detail-departement">
|
||||||
|
<option value="">-- Sélectionner --</option>
|
||||||
|
{% for dept in departements %}
|
||||||
|
<option value="{{ dept.id }}">{{ dept.nom }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>Sexe :</label>
|
<label>Sexe :</label>
|
||||||
@@ -93,9 +98,6 @@
|
|||||||
</h5>
|
</h5>
|
||||||
{% if user|has_group:"ressource_humaine" %}
|
{% if user|has_group:"ressource_humaine" %}
|
||||||
<div>
|
<div>
|
||||||
<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 }}">
|
<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
|
<i class="bi bi-file-earmark-text"></i> Créer contrat
|
||||||
</button>
|
</button>
|
||||||
@@ -133,4 +135,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form method="POST" action="{% url 'gestion_employe:modifier-mot-passe' %}">
|
<form method="POST" action="{% url 'gestion_employe:modifier-mot-passe' %}" id="formulaire_mot_de_passe">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>Votre mot de passe actuel :</label>
|
<label>Votre mot de passe actuel :</label>
|
||||||
|
|||||||
Binary file not shown.
@@ -6,6 +6,7 @@ la gestion de l'accès des utilisateurs aux différentes pages.
|
|||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from gestion_employe.models import Affectation
|
from gestion_employe.models import Affectation
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from . import views
|
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
app_name = 'gestion_employe'
|
app_name = 'gestion_employe'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -29,11 +30,17 @@ urlpatterns = [
|
|||||||
views.creation_contrat,
|
views.creation_contrat,
|
||||||
name='creation-contrat'
|
name='creation-contrat'
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
'employe/<int:employe_id>/contrats/'
|
||||||
|
, views.contrats_employe,
|
||||||
|
name="contrats-employe"
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
'contrat/supprimer/',
|
'contrat/supprimer/',
|
||||||
views.suppression_contrat,
|
views.suppression_contrat,
|
||||||
name='supprimer-contrat'
|
name='supprimer-contrat'
|
||||||
),
|
),
|
||||||
|
|
||||||
path(
|
path(
|
||||||
'Affectation/affectation/',
|
'Affectation/affectation/',
|
||||||
views.affecter_employe_projet,
|
views.affecter_employe_projet,
|
||||||
@@ -84,4 +91,4 @@ urlpatterns = [
|
|||||||
views.liste_contrat_expirants,
|
views.liste_contrat_expirants,
|
||||||
name='liste-contrat-expirants'
|
name='liste-contrat-expirants'
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,28 +1,44 @@
|
|||||||
import json
|
import json
|
||||||
from datetime import date, timedelta, datetime
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import requests
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from decouple import config
|
||||||
from django.utils import timezone
|
|
||||||
from django.contrib import messages
|
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 django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.http import JsonResponse
|
from django.contrib.auth.password_validation import validate_password
|
||||||
from django.forms.models import model_to_dict
|
from django.core.exceptions import ValidationError
|
||||||
from django.db.models import Sum
|
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 .models import Employe, Contrat, Affectation, Formation
|
|
||||||
from .forms import AffectationForm, ContratForm, FormationForm
|
|
||||||
from fonction_utilitaire import fonctions_utilitaire
|
from fonction_utilitaire import fonctions_utilitaire
|
||||||
|
|
||||||
|
from .forms import AffectationForm, ContratForm, FormationForm
|
||||||
|
from .models import Affectation, Contrat, Departement, Employe, Formation
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def index(request):
|
def index(request):
|
||||||
"""Vue d'index"""
|
"""Vue d'index"""
|
||||||
if not request.user.groups.filter(name="ressource_humaine").exists():
|
if not request.user.groups.filter(name="ressource_humaine").exists():
|
||||||
return redirect('gestion_employe:mon-profil')
|
return redirect('gestion_employe:mon-profil')
|
||||||
|
|
||||||
|
response = requests.get(f"{config('SERVEUR_AUTHENTIFICATION')}recuperer-list-departement/")
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
try:
|
||||||
|
departements_data = response.json()
|
||||||
|
for dept in departements_data:
|
||||||
|
Departement.objects.update_or_create(
|
||||||
|
nom = dept['nom']
|
||||||
|
)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"[ERROR] Erreur lors du décodage JSON : {e}")
|
||||||
|
|
||||||
employes = Employe.objects.all().order_by('-user__date_joined')
|
employes = Employe.objects.all().order_by('-user__date_joined')
|
||||||
nombre_employes = Employe.objects.count()
|
nombre_employes = Employe.objects.count()
|
||||||
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
|
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
|
||||||
@@ -41,7 +57,8 @@ def index(request):
|
|||||||
'nombre_stage': nombre_stage,
|
'nombre_stage': nombre_stage,
|
||||||
'nombre_expirants': nombre_expirants,
|
'nombre_expirants': nombre_expirants,
|
||||||
'affectation_form': AffectationForm(),
|
'affectation_form': AffectationForm(),
|
||||||
'contrat_form': ContratForm()
|
'contrat_form': ContratForm(),
|
||||||
|
'departements': Departement.objects.all()
|
||||||
})
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -132,26 +149,32 @@ def mon_profil(request):
|
|||||||
try:
|
try:
|
||||||
employe = Employe.objects.get(user__username=request.user)
|
employe = Employe.objects.get(user__username=request.user)
|
||||||
except Employe.DoesNotExist:
|
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")
|
return redirect("gestion_conges:conge")
|
||||||
|
|
||||||
contrats = Contrat.objects.filter(employe=employe, statut='actif').first()
|
contrats = Contrat.objects.filter(employe=employe, statut='actif').first()
|
||||||
|
|
||||||
projets = Affectation.objects.filter(
|
projets = Affectation.objects.filter(
|
||||||
employe = employe,
|
employe=employe,
|
||||||
date_fin_daffectation__gte = timezone.now().date()
|
date_fin_daffectation__gte=timezone.now().date()
|
||||||
).select_related('projet')
|
).select_related('projet')
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
'gestion_employe/monprofil.html',
|
'gestion_employe/monprofil.html',
|
||||||
{
|
{
|
||||||
'employe': employe,
|
'employe': employe,
|
||||||
|
|
||||||
'contrats': [{
|
'contrats': [{
|
||||||
**model_to_dict(contrats),
|
**model_to_dict(contrats),
|
||||||
"type_contrat": dict(Contrat.TYPE_CONTRAT).get(contrats.type_contrat),
|
"type_contrat": dict(Contrat.TYPE_CONTRAT).get(contrats.type_contrat),
|
||||||
"statut": dict(Contrat.STATUT_CONTRAT).get(contrats.statut),
|
"statut": dict(Contrat.STATUT_CONTRAT).get(contrats.statut),
|
||||||
"fichier_contrat": contrats.fichier_contrat.url if contrats.fichier_contrat else "",
|
"fichier_contrat": contrats.fichier_contrat.url if contrats.fichier_contrat else "",
|
||||||
} if contrats else []],
|
}] if contrats else [],
|
||||||
|
|
||||||
'projets': [
|
'projets': [
|
||||||
{
|
{
|
||||||
**model_to_dict(a.projet),
|
**model_to_dict(a.projet),
|
||||||
@@ -161,32 +184,14 @@ def mon_profil(request):
|
|||||||
"pourcentage_temps_affectation": a.pourcentage_temps_affectation
|
"pourcentage_temps_affectation": a.pourcentage_temps_affectation
|
||||||
} for a in projets
|
} for a in projets
|
||||||
],
|
],
|
||||||
|
|
||||||
"formation_form": FormationForm(),
|
"formation_form": FormationForm(),
|
||||||
|
"has_contrat": contrats is not None,
|
||||||
"expiration_contrat": contrats.nombre_jours_restant <= fonctions_utilitaire.DUREE_FIN_CONTRAT if contrats else False,
|
"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
|
"contrat_nb_jours_restant": max(0, contrats.nombre_jours_restant if contrats else 0)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@login_required
|
|
||||||
def modifier_mot_passe(request):
|
|
||||||
"""Vue pour permettre à un utilisateur de modifier son mot de passe et ses informations de profil"""
|
|
||||||
user = User.objects.get(username=request.user)
|
|
||||||
if request.method == "POST":
|
|
||||||
ancien_mdp = request.POST["ancien-mdp"]
|
|
||||||
nouveau_mdp = request.POST["nouveau-mdp"]
|
|
||||||
confirmation_mdp = request.POST["confirmation-mdp"]
|
|
||||||
|
|
||||||
if authenticate(request, username=request.user, password=ancien_mdp) is None:
|
|
||||||
messages.error(request, "Ancien mot de passe incorrect.")
|
|
||||||
elif nouveau_mdp != confirmation_mdp:
|
|
||||||
messages.error(request, "Les deux nouveaux ne correspondent pas.")
|
|
||||||
else:
|
|
||||||
user.set_password(nouveau_mdp)
|
|
||||||
user.save()
|
|
||||||
messages.success(request, "Mot de passe modifié avec succès.")
|
|
||||||
|
|
||||||
return redirect("gestion_employe:mon-profil")
|
|
||||||
|
|
||||||
def modifier_employer(request):
|
def modifier_employer(request):
|
||||||
"""Vue pour permettre à un utilisateur de modifier les informations d'un employé"""
|
"""Vue pour permettre à un utilisateur de modifier les informations d'un employé"""
|
||||||
try:
|
try:
|
||||||
@@ -213,7 +218,6 @@ def modifier_employer(request):
|
|||||||
user.save()
|
user.save()
|
||||||
messages.success(request, "Profil mis à jour avec succès.")
|
messages.success(request, "Profil mis à jour avec succès.")
|
||||||
return redirect("gestion_employe:mon-profil")
|
return redirect("gestion_employe:mon-profil")
|
||||||
|
|
||||||
|
|
||||||
def enregistrement_document(request):
|
def enregistrement_document(request):
|
||||||
"""Vue pour permettre à un utilisateur de télécharger et enregistrer des documents liés à son profil"""
|
"""Vue pour permettre à un utilisateur de télécharger et enregistrer des documents liés à son profil"""
|
||||||
@@ -265,9 +269,10 @@ def creation_contrat(request):
|
|||||||
except Employe.DoesNotExist:
|
except Employe.DoesNotExist:
|
||||||
messages.error(request, "Employé non trouvé.")
|
messages.error(request, "Employé non trouvé.")
|
||||||
return redirect('employe-index')
|
return redirect('employe-index')
|
||||||
|
|
||||||
contrat_actif = Contrat.objects.filter(
|
contrat_actif = Contrat.objects.filter(
|
||||||
employe=employe,
|
employe=employe,
|
||||||
date_fin__gte=date.today()
|
date_fin__gte=timezone.now().date()
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
@@ -293,6 +298,45 @@ def creation_contrat(request):
|
|||||||
'contrat_form': form
|
'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
|
@login_required
|
||||||
def enregistrer_detail_employe(request):
|
def enregistrer_detail_employe(request):
|
||||||
"""Vue pour permettre à un utilisateur de modifier les détails d'un employé via une requête AJAX"""
|
"""Vue pour permettre à un utilisateur de modifier les détails d'un employé via une requête AJAX"""
|
||||||
@@ -303,10 +347,27 @@ def enregistrer_detail_employe(request):
|
|||||||
except Employe.DoesNotExist:
|
except Employe.DoesNotExist:
|
||||||
return JsonResponse({"error": "Employé non trouvé."}, status=404)
|
return JsonResponse({"error": "Employé non trouvé."}, status=404)
|
||||||
|
|
||||||
employe.fonction = data['fonction']
|
employe.fonction = data.get('fonction')
|
||||||
employe.date_embauche = data['date_embauche']
|
employe.date_embauche = data.get('date_embauche')
|
||||||
employe.matricule = data['matricule']
|
employe.matricule = data.get('matricule')
|
||||||
|
|
||||||
|
# Traiter le département
|
||||||
|
dept_value = data.get('departement')
|
||||||
|
|
||||||
|
if dept_value and str(dept_value).strip():
|
||||||
|
try:
|
||||||
|
dept_id = int(dept_value)
|
||||||
|
departement = Departement.objects.get(id=dept_id)
|
||||||
|
employe.departement = departement
|
||||||
|
except Departement.DoesNotExist:
|
||||||
|
employe.departement = None
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
employe.departement = None
|
||||||
|
else:
|
||||||
|
employe.departement = None
|
||||||
|
|
||||||
employe.save()
|
employe.save()
|
||||||
|
|
||||||
return JsonResponse({"message": "Détails de l'employé mis à jour avec succès."})
|
return JsonResponse({"message": "Détails de l'employé mis à jour avec succès."})
|
||||||
else:
|
else:
|
||||||
return JsonResponse({"message": "Méthode non autorisée."}, status=405)
|
return JsonResponse({"message": "Méthode non autorisée."}, status=405)
|
||||||
@@ -322,8 +383,8 @@ def liste_employe(request):
|
|||||||
projets = [
|
projets = [
|
||||||
", ".join([
|
", ".join([
|
||||||
a.projet.nom_projet for a in Affectation.objects.filter(
|
a.projet.nom_projet for a in Affectation.objects.filter(
|
||||||
employe=employe,
|
employe=employe
|
||||||
date_fin_daffectation__gte=timezone.now().date()
|
#, date_fin_daffectation__gte=timezone.now().date()
|
||||||
)
|
)
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
@@ -347,14 +408,14 @@ def liste_employe(request):
|
|||||||
"salaire_mensuel": contrat.salaire_mensuel,
|
"salaire_mensuel": contrat.salaire_mensuel,
|
||||||
"statut": contrat.statut,
|
"statut": contrat.statut,
|
||||||
"fichier_contrat": contrat.fichier_contrat.url if contrat.fichier_contrat else "",
|
"fichier_contrat": contrat.fichier_contrat.url if contrat.fichier_contrat else "",
|
||||||
} for contrat in Contrat.objects.filter(employe=employe, statut='actif')
|
} for contrat in Contrat.objects.filter(employe=employe)#, statut='actif')
|
||||||
]
|
]
|
||||||
|
|
||||||
affectations = [
|
affectations = [
|
||||||
{**model_to_dict(affectation), "projet": affectation.projet.nom_projet}
|
{**model_to_dict(affectation), "projet": affectation.projet.nom_projet}
|
||||||
for affectation in Affectation.objects.filter(
|
for affectation in Affectation.objects.filter(
|
||||||
employe=employe,
|
employe=employe
|
||||||
date_fin_daffectation__gte=timezone.now().date()
|
#, date_fin_daffectation__gte=timezone.now().date()
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -368,7 +429,7 @@ def liste_employe(request):
|
|||||||
"affectations": affectations,
|
"affectations": affectations,
|
||||||
"projet": projets,
|
"projet": projets,
|
||||||
"contrats": contrats,
|
"contrats": contrats,
|
||||||
"departement": employe.departement.nom if employe.departement else "",
|
"departement": employe.departement.id if employe.departement else "",
|
||||||
"fonction": employe.fonction,
|
"fonction": employe.fonction,
|
||||||
"date_embauche": employe.date_embauche,
|
"date_embauche": employe.date_embauche,
|
||||||
"adresse": employe.adresse,
|
"adresse": employe.adresse,
|
||||||
@@ -440,3 +501,47 @@ def supprimer_formation(request, id_formation):
|
|||||||
formation.delete()
|
formation.delete()
|
||||||
messages.success(request, "Formation supprimée ")
|
messages.success(request, "Formation supprimée ")
|
||||||
return redirect("mes_formations")
|
return redirect("mes_formations")
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def modifier_mot_passe(request):
|
||||||
|
"""Vue pour permettre à un employé de modifier son mot de passe"""
|
||||||
|
if request.method == "POST":
|
||||||
|
username = request.user.username
|
||||||
|
ancien_mdp = request.POST.get("ancien-mdp")
|
||||||
|
nouveau_mdp = request.POST.get("nouveau-mdp")
|
||||||
|
confirmation_mdp = request.POST.get("confirmation-mdp")
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate_password(nouveau_mdp, user=request.user)
|
||||||
|
except ValidationError as e:
|
||||||
|
messages.error(request, f"Erreur : {' '.join(e.messages)}")
|
||||||
|
return redirect("gestion_employe:mon-profil")
|
||||||
|
|
||||||
|
donnees = {
|
||||||
|
"username": username,
|
||||||
|
"ancien_mdp": ancien_mdp,
|
||||||
|
"nouveau_mdp": nouveau_mdp,
|
||||||
|
"confirmation_mdp": confirmation_mdp
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
f"{config('SERVEUR_AUTHENTIFICATION')}modifier-mot-de-passe/",
|
||||||
|
json=donnees,
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
cle = next(iter(data.keys()))
|
||||||
|
valeur = next(iter(data.values()))
|
||||||
|
|
||||||
|
if cle == "success":
|
||||||
|
messages.success(request, valeur)
|
||||||
|
else:
|
||||||
|
messages.error(request, valeur)
|
||||||
|
|
||||||
|
return redirect("gestion_employe:mon-profil")
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,14 +1,16 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from gestion_projet.models import Projet
|
from gestion_projet.models import Projet
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
ActiviteProjet,
|
ActiviteProjet,
|
||||||
Bailleur,
|
Bailleur,
|
||||||
DocumentProjet,
|
DocumentProjet,
|
||||||
FinancementProjet,
|
FinancementProjet,
|
||||||
LivrablesLivres,
|
LivrablesLivres,
|
||||||
DomaineDeRecherche
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProjetForm(forms.ModelForm):
|
class ProjetForm(forms.ModelForm):
|
||||||
"""Formulaire de création et de modification d'un projet, avec validation des dates et personnalisation des champs."""
|
"""Formulaire de création et de modification d'un projet, avec validation des dates et personnalisation des champs."""
|
||||||
class Meta:
|
class Meta:
|
||||||
@@ -20,7 +22,6 @@ class ProjetForm(forms.ModelForm):
|
|||||||
'date_fin',
|
'date_fin',
|
||||||
'numero_convention',
|
'numero_convention',
|
||||||
'domaine_recherche',
|
'domaine_recherche',
|
||||||
'type_projet',
|
|
||||||
'budget',
|
'budget',
|
||||||
'budget_RH',
|
'budget_RH',
|
||||||
'description'
|
'description'
|
||||||
@@ -35,7 +36,6 @@ class ProjetForm(forms.ModelForm):
|
|||||||
'nom_projet': forms.TextInput(attrs={'class': "form-control"}),
|
'nom_projet': forms.TextInput(attrs={'class': "form-control"}),
|
||||||
'numero_convention': forms.TextInput(attrs={'class': "form-control"}),
|
'numero_convention': forms.TextInput(attrs={'class': "form-control"}),
|
||||||
'domaine_recherche': forms.SelectMultiple(attrs={'class': "form-control"}),
|
'domaine_recherche': forms.SelectMultiple(attrs={'class': "form-control"}),
|
||||||
'type_projet': forms.Select(attrs={'class': "form-select"}),
|
|
||||||
'budget': forms.NumberInput(attrs={'class': "form-control"}),
|
'budget': forms.NumberInput(attrs={'class': "form-control"}),
|
||||||
'budget_RH': forms.NumberInput(attrs={'class': "form-control"}),
|
'budget_RH': forms.NumberInput(attrs={'class': "form-control"}),
|
||||||
'description': forms.Textarea(attrs={'class': "form-control"}),
|
'description': forms.Textarea(attrs={'class': "form-control"}),
|
||||||
@@ -173,4 +173,4 @@ class LivrablesLivresForm(forms.ModelForm):
|
|||||||
'fichier': forms.ClearableFileInput(attrs={
|
'fichier': forms.ClearableFileInput(attrs={
|
||||||
'class':'form-control',
|
'class':'form-control',
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.13 on 2026-06-16 11:06
|
# Generated by Django 5.2.13 on 2026-09-03 14:33
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from datetime import date
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
class Bailleur(models.Model):
|
class Bailleur(models.Model):
|
||||||
"""Modèle représentant un bailleur de fonds pour les projets de recherche."""
|
"""Modèle représentant un bailleur de fonds pour les projets de recherche."""
|
||||||
nom_organisme = models.CharField(
|
nom_organisme = models.CharField(
|
||||||
@@ -29,12 +29,14 @@ class Bailleur(models.Model):
|
|||||||
class DomaineDeRecherche(models.Model):
|
class DomaineDeRecherche(models.Model):
|
||||||
"""Modèle représentant les domaines de recherche"""
|
"""Modèle représentant les domaines de recherche"""
|
||||||
|
|
||||||
DOMAINE_RECHERCHE = [
|
DOMAINE_RECHERCHE = (
|
||||||
('sciences_sociales', 'Sciences sociales'),
|
('sciences_sociales', 'Sciences sociales'),
|
||||||
('naturelles', 'Naturelles'),
|
|
||||||
('humaines', 'Humaines'),
|
('humaines', 'Humaines'),
|
||||||
('veterinaires', 'Vétérinaires')
|
('epidemiologie', 'Epidémiologie'),
|
||||||
]
|
('sciences_biomedicales', 'Sciences biomédicales'),
|
||||||
|
('service_pedagogique', 'Service Pédagogique'),
|
||||||
|
('recherche_clinique', 'Recherche clinique')
|
||||||
|
)
|
||||||
|
|
||||||
nom = models.CharField(
|
nom = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
@@ -52,13 +54,7 @@ class DomaineDeRecherche(models.Model):
|
|||||||
|
|
||||||
class Projet(models.Model):
|
class Projet(models.Model):
|
||||||
"""Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé."""
|
"""Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé."""
|
||||||
TYPE_PROJET = [
|
|
||||||
('laboratoire', 'Laboratoire'),
|
|
||||||
('épidémiologie', 'Épidémiologie'),
|
|
||||||
('sciences sociales', 'Sciences sociales'),
|
|
||||||
('cliniques', 'Cliniques'),
|
|
||||||
('autre', 'Autre'),
|
|
||||||
]
|
|
||||||
id_projet = models.CharField(
|
id_projet = models.CharField(
|
||||||
max_length=100,
|
max_length=100,
|
||||||
blank=True,
|
blank=True,
|
||||||
@@ -83,12 +79,6 @@ class Projet(models.Model):
|
|||||||
description = models.TextField(
|
description = models.TextField(
|
||||||
verbose_name="Description"
|
verbose_name="Description"
|
||||||
)
|
)
|
||||||
type_projet = models.CharField(
|
|
||||||
max_length=100,
|
|
||||||
choices=TYPE_PROJET,
|
|
||||||
default='épidémiologie',
|
|
||||||
verbose_name="Type de projet"
|
|
||||||
)
|
|
||||||
domaine_recherche = models.ManyToManyField(DomaineDeRecherche)
|
domaine_recherche = models.ManyToManyField(DomaineDeRecherche)
|
||||||
budget=models.DecimalField(
|
budget=models.DecimalField(
|
||||||
max_digits=12,
|
max_digits=12,
|
||||||
@@ -111,13 +101,13 @@ class Projet(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def statut(self):
|
def statut(self):
|
||||||
if self.date_fin < date.today():
|
if self.date_fin < timezone.now().date():
|
||||||
return "Terminé"
|
return "Terminé"
|
||||||
return "En cours"
|
return "En cours"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def avancement(self):
|
def avancement(self):
|
||||||
aujourd_hui = date.today()
|
aujourd_hui = timezone.now().date()
|
||||||
if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin):
|
if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin):
|
||||||
duree_projet = (self.date_fin - self.date_debut).days
|
duree_projet = (self.date_fin - self.date_debut).days
|
||||||
temps_ecoule = (aujourd_hui - self.date_debut).days
|
temps_ecoule = (aujourd_hui - self.date_debut).days
|
||||||
@@ -155,7 +145,7 @@ class FinancementProjet(models.Model):
|
|||||||
|
|
||||||
class DocumentProjet(models.Model):
|
class DocumentProjet(models.Model):
|
||||||
"""Modèle représentant un document associé à un projet, avec des métadonnées et un fichier attaché."""
|
"""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'),
|
('protocole', 'Protocole d’étude'),
|
||||||
('ethique', "Approbation du comité d'éthique"),
|
('ethique', "Approbation du comité d'éthique"),
|
||||||
('autorisation', 'Autorisation (DNLP)'),
|
('autorisation', 'Autorisation (DNLP)'),
|
||||||
@@ -165,7 +155,7 @@ class DocumentProjet(models.Model):
|
|||||||
('convention', 'Convention'),
|
('convention', 'Convention'),
|
||||||
('rapport_final', 'Rapport final'),
|
('rapport_final', 'Rapport final'),
|
||||||
('autre', 'Autre'),
|
('autre', 'Autre'),
|
||||||
]
|
)
|
||||||
|
|
||||||
projet = models.ForeignKey(
|
projet = models.ForeignKey(
|
||||||
Projet,
|
Projet,
|
||||||
@@ -306,4 +296,4 @@ class LivrablesLivres(models.Model):
|
|||||||
null = True
|
null = True
|
||||||
)
|
)
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.nom
|
return self.nom
|
||||||
|
|||||||
@@ -23,29 +23,25 @@ btnEnregistrerBailleur.addEventListener('click', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
|
|
||||||
table = new Tabulator("#table-bailleurs", {
|
table = new Tabulator("#table-bailleurs", {
|
||||||
ajaxURL: "/gestion-projet/bailleurs/",
|
ajaxURL: "/gestion-projet/bailleurs/",
|
||||||
layout: "fitColumns",
|
layout: "fitColumns",
|
||||||
pagination: "local",
|
pagination: "local",
|
||||||
paginationSize: 5,
|
paginationSize: 5,
|
||||||
|
|
||||||
columns: [
|
columns: [
|
||||||
{title: "#", formatter: "rownum", width: 60},
|
{title: "#", formatter: "rownum", width: 60},
|
||||||
{title: "Organisme", field: "nom_organisme"},
|
{title: "Organisme", field: "nom_organisme"},
|
||||||
{title: "Contact", field: "contact"},
|
{title: "Contact", field: "contact"},
|
||||||
{title: "Email", field: "email"},
|
{title: "Email", field: "email"},
|
||||||
{title: "Pays", field: "pays"},
|
{title: "Pays", field: "pays"},
|
||||||
],
|
],
|
||||||
rowDblClick: function(e, row) {
|
rowDblClick: function(e, row) {
|
||||||
const data = row.getData();
|
const data = row.getData();
|
||||||
|
|
||||||
if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) {
|
if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) {
|
||||||
supprimerBailleur(data.id);
|
supprimerBailleur(data.id);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const $ = (element) => document.getElementById(element)
|
const $ = (element) => document.getElementById(element)
|
||||||
const url_liste_projet = $("tableau-liste-projet").dataset.url;
|
const url_liste_projet = $("tableau-liste-projet").dataset.url;
|
||||||
|
|
||||||
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
|
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
|
||||||
fitColumns: true,
|
fitColumns: true,
|
||||||
responsiveLayout : true,
|
responsiveLayout : true,
|
||||||
@@ -39,7 +40,6 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
|
|||||||
$("detail_date_debut").value = data.date_debut;
|
$("detail_date_debut").value = data.date_debut;
|
||||||
$("detail_date_fin").value = data.date_fin;
|
$("detail_date_fin").value = data.date_fin;
|
||||||
$("detail_numero_convention").value = data.numero_convention;
|
$("detail_numero_convention").value = data.numero_convention;
|
||||||
$("detail_type_projet").value = data.type_projet;
|
|
||||||
Array.from($("detail_domaine_recherche").options).forEach(option => {
|
Array.from($("detail_domaine_recherche").options).forEach(option => {
|
||||||
if (data.domaine_recherche.includes(option.value)) {
|
if (data.domaine_recherche.includes(option.value)) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
@@ -61,4 +61,4 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
|
|||||||
// $('detail-projet-form').addEventListener('submit', (e) => {
|
// $('detail-projet-form').addEventListener('submit', (e) => {
|
||||||
// e.preventDefault();
|
// e.preventDefault();
|
||||||
// new FormData($("detail-projet-form"));
|
// new FormData($("detail-projet-form"));
|
||||||
// })
|
// })
|
||||||
|
|||||||
@@ -15,11 +15,6 @@ const tableau_liste_activite = new Tabulator("#tableau-liste-activite", {
|
|||||||
})
|
})
|
||||||
tableau_liste_activite.on("rowClick", function (row, rowData) {
|
tableau_liste_activite.on("rowClick", function (row, rowData) {
|
||||||
const data = rowData.getData();
|
const data = rowData.getData();
|
||||||
console.log('=== ACTIVITÉ SÉLECTIONNÉE ===');
|
|
||||||
console.log('ID:', data.id);
|
|
||||||
console.log('Budget prévu:', data.budget_prevu);
|
|
||||||
console.log('Budget dépensé:', data.budget_depense);
|
|
||||||
|
|
||||||
$("idDetailActivite").value = data.id;
|
$("idDetailActivite").value = data.id;
|
||||||
$("titreDetailActivite").value = data.titre;
|
$("titreDetailActivite").value = data.titre;
|
||||||
$("descriptionDetailActivite").value = data.description;
|
$("descriptionDetailActivite").value = data.description;
|
||||||
@@ -27,79 +22,14 @@ tableau_liste_activite.on("rowClick", function (row, rowData) {
|
|||||||
$("date_finDetailActivite").value = data.date_fin;
|
$("date_finDetailActivite").value = data.date_fin;
|
||||||
$("statutDetailActivite").value = data.statut;
|
$("statutDetailActivite").value = data.statut;
|
||||||
$("budget_prevuDetailActivite").value = data.budget_prevu;
|
$("budget_prevuDetailActivite").value = data.budget_prevu;
|
||||||
$("budget_depenseDetailActivite").value = data.budget_depense || 0;
|
|
||||||
|
|
||||||
// Vérifier si l'activité est annulée
|
|
||||||
const isAnnulee = data.statut && data.statut.toLowerCase().includes('annul');
|
|
||||||
console.log('Activité annulée?', isAnnulee);
|
|
||||||
|
|
||||||
// Masquer/Afficher les boutons d'action selon le statut
|
|
||||||
const btnAnnuler = $("btnAnnulerActivite");
|
|
||||||
const btnMiseAJour = $("btnMiseAJourDepense");
|
|
||||||
const btnAjouterLivrable = $("btnOuvrirAjouterLivrable");
|
|
||||||
|
|
||||||
if (isAnnulee) {
|
|
||||||
// Activité annulée : masquer les boutons
|
|
||||||
if (btnAnnuler) btnAnnuler.style.display = "none";
|
|
||||||
if (btnMiseAJour) btnMiseAJour.style.display = "none";
|
|
||||||
if (btnAjouterLivrable) btnAjouterLivrable.style.display = "none";
|
|
||||||
console.log('✓ Boutons masqués - activité annulée');
|
|
||||||
} else {
|
|
||||||
// Activité active : afficher les boutons
|
|
||||||
if (btnAnnuler) btnAnnuler.style.display = "block";
|
|
||||||
if (btnMiseAJour) btnMiseAJour.style.display = "block";
|
|
||||||
if (btnAjouterLivrable) btnAjouterLivrable.style.display = "block";
|
|
||||||
console.log('✓ Boutons affichés - activité active');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vérifier si les budgets sont différents
|
|
||||||
const budgetPrevu = parseFloat(data.budget_prevu) || 0;
|
|
||||||
const budgetDepense = parseFloat(data.budget_depense) || 0;
|
|
||||||
const isDifferent = budgetDepense !== budgetPrevu;
|
|
||||||
|
|
||||||
console.log('Budget prévu:', budgetPrevu);
|
|
||||||
console.log('Budget dépensé:', budgetDepense);
|
|
||||||
console.log('Sont-ils différents?', isDifferent);
|
|
||||||
|
|
||||||
// Masquer/Afficher le conteneur du motif
|
|
||||||
const motifContainer = $("motif_container");
|
|
||||||
if (motifContainer) {
|
|
||||||
if (isDifferent) {
|
|
||||||
motifContainer.style.display = "block";
|
|
||||||
$("motif_changement_budgetDetailActivite").value = data.motif_changement_budget || "";
|
|
||||||
console.log('✓ Motif AFFICHÉ - budgets différents');
|
|
||||||
} else {
|
|
||||||
motifContainer.style.display = "none";
|
|
||||||
$("motif_changement_budgetDetailActivite").value = "";
|
|
||||||
console.log('✓ Motif MASQUÉ - budgets identiques');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error('❌ Élément motif_container introuvable!');
|
|
||||||
}
|
|
||||||
|
|
||||||
$("besoin_ressources_materiellesDetailActivite").value = data.besoin_ressource_materielle;
|
$("besoin_ressources_materiellesDetailActivite").value = data.besoin_ressource_materielle;
|
||||||
const modal = new bootstrap.Modal($("modalDetailActivite"));
|
const modal = new bootstrap.Modal($("modalDetailActivite"));
|
||||||
modal.show();
|
modal.show();
|
||||||
|
|
||||||
const url_livrables = `/gestion-projet/activite/liste-des-livrables/${data.id}/`;
|
fetch(`liste-des-livrables/${data.id}/`)
|
||||||
console.log('URL des livrables:', url_livrables);
|
.then(response => response.json())
|
||||||
|
|
||||||
fetch(url_livrables)
|
|
||||||
.then(response => {
|
|
||||||
console.log('Réponse status:', response.status);
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(livrables => {
|
.then(livrables => {
|
||||||
console.log('Livrables reçus:', livrables);
|
|
||||||
console.log('Nombre de livrables:', livrables.length);
|
|
||||||
tableau_liste_livrable.setData(livrables);
|
tableau_liste_livrable.setData(livrables);
|
||||||
$("nombreLivrables").textContent = livrables.length;
|
|
||||||
console.log('Tableau mis à jour avec', livrables.length, 'livrables');
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error(' Erreur lors du chargement des livrables:', error);
|
|
||||||
tableau_liste_livrable.setData([]);
|
|
||||||
$("nombreLivrables").textContent = 0;
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -110,7 +40,6 @@ const tableau_liste_livrable = new Tabulator("#listeLivrables", {
|
|||||||
],
|
],
|
||||||
placeholder: "Aucun livrable trouvé",
|
placeholder: "Aucun livrable trouvé",
|
||||||
})
|
})
|
||||||
console.log('✓ Tableau des livrables initialisé');
|
|
||||||
|
|
||||||
$("btnMiseAJourDepense").addEventListener("click", function() {
|
$("btnMiseAJourDepense").addEventListener("click", function() {
|
||||||
const modal = new bootstrap.Modal($("modalDepenseActivite"));
|
const modal = new bootstrap.Modal($("modalDepenseActivite"));
|
||||||
@@ -127,5 +56,4 @@ $("btnAnnulerActivite").addEventListener("click", function(event) {
|
|||||||
new bootstrap.Modal($("modalAnnulerActivite")).show();
|
new bootstrap.Modal($("modalAnnulerActivite")).show();
|
||||||
$("id_activite_annulation").value = $("idDetailActivite").value;
|
$("id_activite_annulation").value = $("idDetailActivite").value;
|
||||||
bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
|
bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -12,12 +12,12 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="row">
|
<div class="row d-flex flex-column flex-lg-row">
|
||||||
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
|
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
|
||||||
<span class="fs-5">Projets en cours</span>
|
<span class="fs-5">Projets en cours</span>
|
||||||
<h3>{{projet_en_cours}}</h3>
|
<h3>{{projet_en_cours}}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-2">
|
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4 my-1 my-lg-0 mx-0 mx-lg-2">
|
||||||
<span class="fs-5">Budget Total (GNF)</span>
|
<span class="fs-5">Budget Total (GNF)</span>
|
||||||
<h3>{{budget_total}}</h3>
|
<h3>{{budget_total}}</h3>
|
||||||
</div>
|
</div>
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col d-flex justify-content-between align-items-center mb-3">
|
<div class="col d-flex flex-lg-row flex-column justify-content-between align-items-center mb-3">
|
||||||
<h5 class="mb-4" >La liste des projets </h5>
|
<h5 class="mb-4" >La liste des projets </h5>
|
||||||
{% if user|has_group:"ressource_humaine" %}
|
{% if user|has_group:"ressource_humaine" %}
|
||||||
<div>
|
<div class="d-flex flex-lg-column flex-row justify-content-between align-items-center">
|
||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalProjet">
|
<button class="btn btn-primary me-2 me-lg-0" data-bs-toggle="modal" data-bs-target="#modalProjet">
|
||||||
<i class="bi bi-plus-circle"></i> Ajouter un projet
|
<i class="bi bi-plus-circle"></i> Ajouter un projet
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-info" data-bs-toggle="modal" data-bs-target="#modalBailleur">
|
<button class="btn btn-info" data-bs-toggle="modal" data-bs-target="#modalBailleur">
|
||||||
|
|||||||
@@ -18,4 +18,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,111 +1,58 @@
|
|||||||
<div class="modal fade" id="modalDetailActivite" tabindex="-1"
|
<div class="modal fade" id="modalDetailActivite" tabindex="-1">
|
||||||
aria-labelledby="modalDetailActiviteLabel" aria-hidden="true">
|
<div class="modal-dialog">
|
||||||
<div class="modal-dialog modal-lg modal-dialog-centered">
|
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header bg-orange-dark text-black">
|
<div class="modal-header bg-orange-dark">
|
||||||
<h5 class="modal-title" id="modalDetailActiviteLabel">
|
<h5 class="modal-title">Détails de l'activité</h5>
|
||||||
<i class="bi bi-clipboard-check me-2"></i>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
Détails de l'activité
|
|
||||||
</h5>
|
|
||||||
<button type="button"class="btn-close btn-close-white" data-bs-dismiss="modal"
|
|
||||||
aria-label="Fermer">
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" data-url-livrables="{% url 'gestion_projet:liste-livrables-activite' activite_id=0 %}">
|
<div class="modal-body">
|
||||||
<input type="hidden" id="idDetailActivite">
|
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12">
|
<div class="">
|
||||||
<label for="titreDetailActivite" class="form-label fw-bold">
|
<input type="hidden" id="idDetailActivite">
|
||||||
Titre :
|
<div class="form-group mb-2">
|
||||||
</label>
|
<label>Titre :</label>
|
||||||
<input type="text"class="form-control" id="titreDetailActivite"readonly>
|
<input type="text" class="form-control" id="titreDetailActivite">
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="descriptionDetailActivite" class="form-label fw-bold">
|
|
||||||
Description :
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control"id="descriptionDetailActivite" rows="3" readonly></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="date_debutDetailActivite"class="form-label fw-bold">
|
|
||||||
Date de début :
|
|
||||||
</label>
|
|
||||||
<input type="date"class="form-control"id="date_debutDetailActivite"readonly>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="date_finDetailActivite"class="form-label fw-bold">
|
|
||||||
Date de fin :
|
|
||||||
</label>
|
|
||||||
<input type="date"class="form-control"id="date_finDetailActivite"readonly>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="statutDetailActivite" class="form-label fw-bold">
|
|
||||||
Statut :
|
|
||||||
</label>
|
|
||||||
<input type="text"class="form-control"id="statutDetailActivite"readonly>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="budget_prevuDetailActivite"class="form-label fw-bold">
|
|
||||||
Budget prévu :
|
|
||||||
</label>
|
|
||||||
<input type="number"class="form-control" id="budget_prevuDetailActivite"readonly>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label for="budget_depenseDetailActivite"class="form-label fw-bold">
|
|
||||||
Montant dépensé (GNF) :
|
|
||||||
</label>
|
|
||||||
<input type="number"class="form-control" id="budget_depenseDetailActivite"readonly>
|
|
||||||
</div>
|
|
||||||
<div class="col-12" id="motif_container">
|
|
||||||
<label for="motif_changement_budgetDetailActivite"class="form-label fw-bold">
|
|
||||||
Motif de différence :
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control"id="motif_changement_budgetDetailActivite"rows="2"readonly></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<label for="besoin_ressources_materiellesDetailActivite"class="form-label fw-bold">
|
|
||||||
Besoin de ressources matérielles :
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control"id="besoin_ressources_materiellesDetailActivite"rows="3"readonly></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 mt-4">
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
||||||
<h5 class="mb-0 fw-bold">
|
|
||||||
<i class="bi bi-box-seam me-2"></i>
|
|
||||||
Liste des livrables
|
|
||||||
</h5>
|
|
||||||
<span class="badge bg-primary" id="nombreLivrables">
|
|
||||||
0
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Description :</label>
|
||||||
|
<textarea class="form-control" id="descriptionDetailActivite"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date de début :</label>
|
||||||
|
<input type="date" class="form-control" id="date_debutDetailActivite">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date de fin :</label>
|
||||||
|
<input type="date" class="form-control" id="date_finDetailActivite">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Statut:</label>
|
||||||
|
<input type="text" class="form-control" id="statutDetailActivite">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Budget prévu :</label>
|
||||||
|
<input type="number" class="form-control" id="budget_prevuDetailActivite">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Besoin de ressources matérielles:</label>
|
||||||
|
<textarea class="form-control" id="besoin_ressources_materiellesDetailActivite"></textarea>
|
||||||
|
</div>
|
||||||
|
<p><strong>Liste des livrables :</strong></p>
|
||||||
<div id="listeLivrables"></div>
|
<div id="listeLivrables"></div>
|
||||||
</div>
|
<button class="btn btn-danger d-block mx-auto" id="btnAnnulerActivite">
|
||||||
|
|
||||||
<div class="col-12 mt-3 text-center">
|
|
||||||
<button type="button"class="btn btn-danger"id="btnAnnulerActivite">
|
|
||||||
<i class="bi bi-x-circle me-2"></i>
|
|
||||||
Annuler cette activité
|
Annuler cette activité
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button"class="btn btn-success"id="btnMiseAJourDepense">
|
<button class="btn btn-success" id="btnMiseAJourDepense">
|
||||||
<i class="bi bi-cash-stack me-2"></i>
|
<i class="bi bi-plus-circle"></i> Mise à jour du budget
|
||||||
Mise à jour du budget
|
|
||||||
</button>
|
</button>
|
||||||
<button type="button"class="btn btn-primary"id="btnOuvrirAjouterLivrable"data-bs-toggle="modal"
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjouterLivrable">
|
||||||
data-bs-target="#modalAjouterLivrable">
|
<i class="bi bi-plus-circle"></i> Ajouter un livrable
|
||||||
<i class="bi bi-plus-circle me-2"></i>
|
|
||||||
Ajouter un livrable
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-secondary"data-bs-dismiss="modal">
|
|
||||||
Fermer
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@@ -31,23 +31,15 @@
|
|||||||
<label>Date de fin :</label>
|
<label>Date de fin :</label>
|
||||||
<input type="date" class="form-control" id="detail_date_fin" name='date_fin'>
|
<input type="date" class="form-control" id="detail_date_fin" name='date_fin'>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-2">
|
|
||||||
<label> Type de projet </label>
|
|
||||||
<select class="form-select" id="detail_type_projet" name='type_projet'>
|
|
||||||
<option value="laboratoire">Laboratoire</option>
|
|
||||||
<option value="épidémiologie">Épidémiologie</option>
|
|
||||||
<option value="sciences sociales">Sciences sociales</option>
|
|
||||||
<option value="cliniques">Cliniques</option>
|
|
||||||
<option value="autre">Autre</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label> Domaine de recherche </label>
|
<label> Domaine de recherche </label>
|
||||||
<select class="form-select" multiple id="detail_domaine_recherche" name="domaine_recherche">
|
<select class="form-select" multiple id="detail_domaine_recherche" name="domaine_recherche">
|
||||||
<option value="sciences_sociales">Sciences sociales</option>
|
<option value="sciences_sociales">Sciences sociales</option>
|
||||||
<option value="naturelles">Naturelles</option>
|
|
||||||
<option value="humaines">Humaines</option>
|
<option value="humaines">Humaines</option>
|
||||||
<option value="veterinaires">Vétérinaires</option>
|
<option value="epidemiologie">Epidémiologie</option>
|
||||||
|
<option value="sciences_biomedicales">Sciences biomédicales</option>
|
||||||
|
<option value="service_pedagogique">Service Pédagogique</option>
|
||||||
|
<option value="recherche_clinique">Recherche clinique</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
@@ -112,4 +104,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -90,12 +90,6 @@ urlpatterns = [
|
|||||||
views.liste_activites_projet,
|
views.liste_activites_projet,
|
||||||
name='liste-activites-projet'
|
name='liste-activites-projet'
|
||||||
),
|
),
|
||||||
|
|
||||||
# path(
|
|
||||||
# 'projet/ajout-de-document/',
|
|
||||||
# views.ajouter_document_projet,
|
|
||||||
# name='ajouter-document'
|
|
||||||
# ),
|
|
||||||
path(
|
path(
|
||||||
'projet/liste-des-documents/',
|
'projet/liste-des-documents/',
|
||||||
views.liste_documents_projet,
|
views.liste_documents_projet,
|
||||||
|
|||||||
@@ -1,30 +1,34 @@
|
|||||||
from datetime import date
|
import datetime
|
||||||
from decimal import Decimal, InvalidOperation
|
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 import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
from django.forms.models import model_to_dict
|
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.forms import AffectationForm
|
||||||
from gestion_employe.models import Affectation, Employe
|
from gestion_employe.models import Affectation, Employe
|
||||||
from gestion_projet.forms import ProjetForm
|
from gestion_projet.forms import ProjetForm
|
||||||
from gestion_projet.models import Projet
|
from gestion_projet.models import Projet
|
||||||
from .models import (
|
|
||||||
DocumentProjet,
|
|
||||||
Bailleur,
|
|
||||||
FinancementProjet,
|
|
||||||
ActiviteProjet,
|
|
||||||
LivrablesLivres,
|
|
||||||
)
|
|
||||||
from .forms import (
|
from .forms import (
|
||||||
ActiviteProjetForm,
|
ActiviteProjetForm,
|
||||||
|
BailleurForm,
|
||||||
DocumentProjetForm,
|
DocumentProjetForm,
|
||||||
FinancementProjetFrom,
|
FinancementProjetFrom,
|
||||||
BailleurForm,
|
LivrablesLivresForm,
|
||||||
LivrablesLivresForm
|
|
||||||
)
|
)
|
||||||
|
from .models import (
|
||||||
|
ActiviteProjet,
|
||||||
|
Bailleur,
|
||||||
|
DocumentProjet,
|
||||||
|
FinancementProjet,
|
||||||
|
LivrablesLivres,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def liste_projet(request):
|
def liste_projet(request):
|
||||||
""" Vue pour retourner la liste de tous les projet """
|
""" Vue pour retourner la liste de tous les projet """
|
||||||
@@ -40,7 +44,6 @@ def liste_projet(request):
|
|||||||
"date_fin": p.date_fin,
|
"date_fin": p.date_fin,
|
||||||
"numero_convention": p.numero_convention,
|
"numero_convention": p.numero_convention,
|
||||||
"description": p.description,
|
"description": p.description,
|
||||||
"type_projet": p.type_projet,
|
|
||||||
"budget": p.budget,
|
"budget": p.budget,
|
||||||
"budget_RH": p.budget_RH,
|
"budget_RH": p.budget_RH,
|
||||||
"created_at": p.created_at,
|
"created_at": p.created_at,
|
||||||
@@ -88,7 +91,7 @@ def index(request):
|
|||||||
'nombre_personnel': nombre_personnel,
|
'nombre_personnel': nombre_personnel,
|
||||||
'budget_total': budget_total,
|
'budget_total': budget_total,
|
||||||
'formulaire_creation_projet': ProjetForm(),
|
'formulaire_creation_projet': ProjetForm(),
|
||||||
'projet_en_cours': Projet.objects.filter(date_fin__gte=date.today()).count(),
|
'projet_en_cours': Projet.objects.filter(date_fin__gte=datetime.datetime.now(tz=datetime.timezone.utc).date()).count(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'gestion_projet/index.html', context)
|
return render(request, 'gestion_projet/index.html', context)
|
||||||
@@ -137,10 +140,9 @@ def mises_a_jour_projet(request):
|
|||||||
@login_required
|
@login_required
|
||||||
def creation_bailleur(request):
|
def creation_bailleur(request):
|
||||||
form = BailleurForm(request.POST)
|
form = BailleurForm(request.POST)
|
||||||
if request.method == 'POST':
|
if request.method == 'POST' and form.is_valid():
|
||||||
if form.is_valid():
|
form.save()
|
||||||
form.save()
|
return JsonResponse({'success': True})
|
||||||
return JsonResponse({'success': True})
|
|
||||||
return JsonResponse({'success': False})
|
return JsonResponse({'success': False})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -381,32 +383,15 @@ def liste_activites_projet(request):
|
|||||||
@login_required
|
@login_required
|
||||||
def liste_livrables_activite(request, activite_id):
|
def liste_livrables_activite(request, activite_id):
|
||||||
"""Vue pour retourner la liste des livrables attendus d'une activité spécifique"""
|
"""Vue pour retourner la liste des livrables attendus d'une activité spécifique"""
|
||||||
try:
|
livrables = LivrablesLivres.objects.filter(activite__id=activite_id)
|
||||||
livrables = LivrablesLivres.objects.filter(activite__id=activite_id)
|
data = []
|
||||||
data = []
|
for livrable in livrables:
|
||||||
|
print(livrable.fichier.url)
|
||||||
for livrable in livrables:
|
data.append({
|
||||||
lien = ""
|
"titre": livrable.nom,
|
||||||
if livrable.fichier:
|
"lien": livrable.fichier.url if livrable.fichier else "",
|
||||||
try:
|
})
|
||||||
lien = livrable.fichier.url
|
return JsonResponse(data, safe=False)
|
||||||
except Exception as e:
|
|
||||||
print(f"[DEBUG] Erreur accès URL fichier {livrable.nom}: {e}")
|
|
||||||
lien = ""
|
|
||||||
|
|
||||||
data.append({
|
|
||||||
"titre": livrable.nom,
|
|
||||||
"lien": lien,
|
|
||||||
})
|
|
||||||
|
|
||||||
print(f"[DEBUG] ✓ Livrables activité {activite_id}: {len(data)} trouvés")
|
|
||||||
return JsonResponse(data, safe=False)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[DEBUG] Erreur liste_livrables_activite: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
return JsonResponse({"error": f"Erreur serveur: {str(e)}"}, status=500)
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def mises_a_jour_depense_activite(request):
|
def mises_a_jour_depense_activite(request):
|
||||||
@@ -516,4 +501,4 @@ def annuler_activite_projet(request):
|
|||||||
activite.motif_annulation = request.POST.get("motif_annulation", "").strip()
|
activite.motif_annulation = request.POST.get("motif_annulation", "").strip()
|
||||||
activite.save()
|
activite.save()
|
||||||
messages.success(request, f"L'activité '{activite.titre}' a été annulée avec succès.")
|
messages.success(request, f"L'activité '{activite.titre}' a été annulée avec succès.")
|
||||||
return redirect('gestion_projet:activites-projet')
|
return redirect('gestion_projet:activites-projet')
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from .models import Reservation
|
from .models import Reservation
|
||||||
|
|
||||||
|
|
||||||
class ReservationForm(forms.ModelForm):
|
class ReservationForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Reservation
|
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 = {
|
widgets = {
|
||||||
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||||
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2.13 on 2026-06-16 11:06
|
# Generated by Django 5.2.13 on 2026-09-03 14:33
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from gestion_employe.models import Employe
|
from gestion_employe.models import Employe
|
||||||
|
|
||||||
|
|
||||||
class Reservation(models.Model):
|
class Reservation(models.Model):
|
||||||
"""Modèle de création des réservations"""
|
"""Modèle de création des réservations"""
|
||||||
TYPE_CHOICES = [
|
TYPE_CHOICES = (
|
||||||
('formation', 'Salle de formation'),
|
('formation', 'Salle de formation'),
|
||||||
('reunion', 'Salle de réunion'),
|
('reunion', 'Salle de réunion'),
|
||||||
('lien_zoom', 'Lien Zoom'),
|
('lien_zoom', 'Lien Zoom'),
|
||||||
]
|
)
|
||||||
STATUT = [
|
STATUT = (
|
||||||
('en_attente', 'En attente'),
|
('en_attente', 'En attente'),
|
||||||
('validee', 'Validée'),
|
('validee', 'Validée'),
|
||||||
('refusee', 'Refusée'),
|
('refusee', 'Refusée'),
|
||||||
('annulee', 'Annulée'),
|
('annulee', 'Annulée'),
|
||||||
]
|
)
|
||||||
|
|
||||||
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
|
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
|
||||||
salle = models.CharField(max_length=100, choices=TYPE_CHOICES)
|
salle = models.CharField(max_length=100, choices=TYPE_CHOICES)
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
|
|||||||
const modal = new bootstrap.Modal($("modalDetailReservation"));
|
const modal = new bootstrap.Modal($("modalDetailReservation"));
|
||||||
modal.show();
|
modal.show();
|
||||||
fetch (`/gestion-salle/revervation/details/${event.guid}`)
|
fetch (`/gestion-salle/revervation/details/${event.guid}`)
|
||||||
.then(response => response.json())
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Erreur HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
currentReservationId = data.id_reservation;
|
currentReservationId = data.id_reservation;
|
||||||
$("id_reservation_detail").value = data.id_reservation;
|
$("id_reservation_detail").value = data.id_reservation;
|
||||||
@@ -27,13 +32,20 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
|
|||||||
$("motif_reservation").value=data.motif_reservation;
|
$("motif_reservation").value=data.motif_reservation;
|
||||||
$("besoin_zoom").checked=data.besoin_zoom;
|
$("besoin_zoom").checked=data.besoin_zoom;
|
||||||
$("besoin_ordinateur").checked=data.besoin_ordinateur;
|
$("besoin_ordinateur").checked=data.besoin_ordinateur;
|
||||||
$("lien_zoom").value=data.lien_zoom;
|
$("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){
|
if (data.besoin_zoom === false){
|
||||||
$("lien_zoom_container").className = "d-none";
|
$("lien_zoom_container").className = "d-none";
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
.catch(error => console.error("Impossible de charger les détails de la réservation :", error));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,12 +87,11 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
$("bouton-annuler").addEventListener("click", (e) => {
|
$("bouton-annuler").addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
||||||
const url_annuler = $("formulaire-details").dataset.urlannuler;
|
const url_annuler = $("formulaire-details").dataset.urlannuler;
|
||||||
|
|
||||||
console.log("URL d'annulation :");
|
|
||||||
console.log($("formulaire-details"))
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
fetch(
|
fetch(
|
||||||
@@ -129,6 +140,8 @@ if($("ajoutZoom")){
|
|||||||
|
|
||||||
if($("refuserReservation")){
|
if($("refuserReservation")){
|
||||||
$("refuserReservation").addEventListener("click", (e) => {
|
$("refuserReservation").addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
||||||
const url = e.currentTarget.dataset.lienrefus;
|
const url = e.currentTarget.dataset.lienrefus;
|
||||||
const idRes = $("id_reservation_detail").value;
|
const idRes = $("id_reservation_detail").value;
|
||||||
@@ -155,7 +168,7 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
|
|||||||
columns: [
|
columns: [
|
||||||
{title: "Employé", field: "employe"},
|
{title: "Employé", field: "employe"},
|
||||||
{title: "Salle", field: "salle"},
|
{title: "Salle", field: "salle"},
|
||||||
{title: "Date de l'evenement", field: "date_debut"},
|
{title: "Date de l'evenement", field: "date_evenement"},
|
||||||
{title: "Heure de début", field: "heure_debut"},
|
{title: "Heure de début", field: "heure_debut"},
|
||||||
{title: "Heure de fin", field: "heure_fin"},
|
{title: "Heure de fin", field: "heure_fin"},
|
||||||
{title: "Motif de reservation", field: "motif_reservation"},
|
{title: "Motif de reservation", field: "motif_reservation"},
|
||||||
@@ -167,11 +180,14 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
|
|||||||
tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
||||||
const data = rowData.getData();
|
const data = rowData.getData();
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
if(data.besoin_zoom === false){
|
if(data.besoin_zoom === false){
|
||||||
$("lien_zoom_container").className = 'd-none';
|
$("lien_zoom_container").className = 'd-none';
|
||||||
}
|
}
|
||||||
|
|
||||||
const id_user = $("current-user-id").dataset.userid;
|
const id_user = $("current-user-id").dataset.userId;
|
||||||
|
|
||||||
$("id_reservation_detail").value = data.id;
|
$("id_reservation_detail").value = data.id;
|
||||||
$("id_reservation_refus").value = data.id;
|
$("id_reservation_refus").value = data.id;
|
||||||
$("id_reservation_zoom").value = data.id;
|
$("id_reservation_zoom").value = data.id;
|
||||||
@@ -179,8 +195,7 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
|||||||
$("employe").value=data.employe;
|
$("employe").value=data.employe;
|
||||||
$("salle").value=data.salle;
|
$("salle").value=data.salle;
|
||||||
$("statut-reservation").innerHTML=data.statut;
|
$("statut-reservation").innerHTML=data.statut;
|
||||||
$("date_debut").value = data.date_debut;
|
$("date_evenement").value = data.date_evenement;
|
||||||
$("date_fin").value = data.date_fin;
|
|
||||||
$("heure_debut").value=data.heure_debut;
|
$("heure_debut").value=data.heure_debut;
|
||||||
$("heure_fin").value=data.heure_fin;
|
$("heure_fin").value=data.heure_fin;
|
||||||
$("motif_reservation").value=data.motif_reservation;
|
$("motif_reservation").value=data.motif_reservation;
|
||||||
@@ -188,6 +203,14 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
|||||||
$("besoin_ordinateur").checked=data.besoin_ordi;
|
$("besoin_ordinateur").checked=data.besoin_ordi;
|
||||||
$("lien_zoom").value=data.lien_zoom;
|
$("lien_zoom").value=data.lien_zoom;
|
||||||
|
|
||||||
|
const refuserReservation = document.querySelector("#refuserReservation");
|
||||||
|
const bouton_valider = document.querySelector("#bouton-valider");
|
||||||
|
|
||||||
|
if((refuserReservation | bouton_valider) && data.statut === "En attente"){
|
||||||
|
refuserReservation.style.display = "inline-block";
|
||||||
|
bouton_valider.style.display = "inline-block";
|
||||||
|
}
|
||||||
|
|
||||||
if (id_user != data.employe_id){
|
if (id_user != data.employe_id){
|
||||||
$("bouton-annuler").className = "d-none";
|
$("bouton-annuler").className = "d-none";
|
||||||
}
|
}
|
||||||
@@ -195,4 +218,4 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
|||||||
const modal = new bootstrap.Modal($("modalDetailReservation"));
|
const modal = new bootstrap.Modal($("modalDetailReservation"));
|
||||||
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
|
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
|
||||||
modal.show();
|
modal.show();
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,21 +9,21 @@
|
|||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
|
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
|
||||||
{{ message }}
|
{{ message }}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col py-2 px-4">
|
<div class="col py-2 px-4">
|
||||||
<h1 class="text-center">Reservation de salle</h1>
|
<h1 class="text-center">Réservation de salle</h1>
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row d-flex justify-content-around">
|
<div class="row d-flex flex-column flex-lg-row justify-content-around">
|
||||||
<div class="form-group col-5 me-2">
|
<div class="form-group col col-lg-5 me-2">
|
||||||
<label>Selectionner le jour concerné :</label>
|
<label>Selectionner le jour concerné :</label>
|
||||||
<input type="date" class="form-control" id="semaineDate" />
|
<input type="date" class="form-control" id="semaineDate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-5 me-2">
|
<div class="form-group col col-lg-5 me-2">
|
||||||
<label>Selectionner une salle :</label>
|
<label>Selectionner une salle :</label>
|
||||||
<select class = "form-select" id="liste-salle">
|
<select class = "form-select" id="liste-salle">
|
||||||
<option value='Salle de formation'>Salle de formation</option>
|
<option value='Salle de formation'>Salle de formation</option>
|
||||||
@@ -33,9 +33,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col d-flex justify-content-center">
|
<div class="col d-flex flex-column flex-lg-row justify-content-center">
|
||||||
<button class="btn btn-lg btn-primary me-2" data-bs-toggle="modal" data-bs-target="#modalReservation">Nouvelle reservation</button>
|
<button class="btn btn-lg btn-primary me-0 me-lg-2" data-bs-toggle="modal" data-bs-target="#modalReservation">Nouvelle réservation</button>
|
||||||
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#modalReservationAttente">
|
<button type="button" class="btn mt-2 mt-lg-0 btn-warning" data-bs-toggle="modal" data-bs-target="#modalReservationAttente">
|
||||||
Validation en attente <span class="badge badge-light">{{ nb_reservation_attente }}</span>
|
Validation en attente <span class="badge badge-light">{{ nb_reservation_attente }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -55,5 +55,5 @@
|
|||||||
{% block 'js' %}
|
{% block 'js' %}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/lemonadejs/dist/lemonade.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@calendarjs/ce/dist/index.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@calendarjs/ce/dist/index.min.js"></script>
|
||||||
<script src="{% static 'gestion_salle/js/index.js' %}"></script>
|
<script src="{% static 'gestion_salle/js/index.js' %}?v=3"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 class="modal-title fs-5" id="modalReservationLabel">
|
<h1 class="modal-title fs-5" id="modalReservationLabel">
|
||||||
Nouvelle reservation
|
Nouvelle réservation
|
||||||
</h1>
|
</h1>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,14 +5,14 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 class="modal-title fs-5" id="modalDetailReservationLabel">
|
<h1 class="modal-title fs-5" id="modalDetailReservationLabel">
|
||||||
Détails de la reservation (<span id='statut-reservation'></span>)
|
Détails de la réservation (<span id='statut-reservation'></span>)
|
||||||
</h1>
|
</h1>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form method="POST" data-urlannuler="{% url 'gestion_salle:annuler-reservation' %}" data-urlvalider="{% url 'gestion_salle:valider-reservation' %}" id="formulaire-details">
|
<form method="POST" data-urlannuler="{% url 'gestion_salle:annuler-reservation' %}" data-urlvalider="{% url 'gestion_salle:valider-reservation' %}" id="formulaire-details">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type='hidden' class="form-control" id="id_reservation_detail" name='id_reservation'>
|
<input type='' class="form-control" id="id_reservation_detail" name='id_reservation' hidden>
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>Employé :</label>
|
<label>Employé :</label>
|
||||||
<input class="form-control" id="employe" readonly>
|
<input class="form-control" id="employe" readonly>
|
||||||
@@ -22,12 +22,8 @@
|
|||||||
<input class="form-control" id="salle" readonly>
|
<input class="form-control" id="salle" readonly>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>Date de debut :</label>
|
<label>Date de l'évènement :</label>
|
||||||
<input type='date' class="form-control" id="date_debut" readonly >
|
<input type='date' class="form-control" id="date_evenement" readonly >
|
||||||
</div>
|
|
||||||
<div class="form-group mb-2">
|
|
||||||
<label>Date de fin :</label>
|
|
||||||
<input type='date' class="form-control" id="date_fin" readonly >
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>Heure de début :</label>
|
<label>Heure de début :</label>
|
||||||
@@ -38,7 +34,7 @@
|
|||||||
<input type='time' class="form-control" id="heure_fin" readonly >
|
<input type='time' class="form-control" id="heure_fin" readonly >
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
<label>Motif de la reservation :</label>
|
<label>Motif de la réservation :</label>
|
||||||
<textarea class="form-control" id="motif_reservation" readonly ></textarea>
|
<textarea class="form-control" id="motif_reservation" readonly ></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check mb-2">
|
<div class="form-check mb-2">
|
||||||
@@ -50,22 +46,27 @@
|
|||||||
<input type="url" class="form-control" id="lien_zoom" readonly >
|
<input type="url" class="form-control" id="lien_zoom" readonly >
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check mb-2">
|
<div class="form-check mb-2">
|
||||||
<label label="form-check-label">Besoin d'un ordinateur</label>
|
<label class="form-check-label">Besoin d'un ordinateur {{ reservation.statut }}</label>
|
||||||
<input type="checkbox" class="form-check-input" id="besoin_ordinateur" readonly >
|
<input type="checkbox" class="form-check-input" id="besoin_ordinateur" readonly >
|
||||||
</div>
|
</div>
|
||||||
<div class='d-flex justify-content-around mt-2'>
|
<hr />
|
||||||
{% if appartient_au_departement_informatique %}
|
<div class='d-flex justify-content-center mt-2'>
|
||||||
<button class="btn btn-primary" id="ajoutZoom">Ajout du lien zoom</button>
|
{% if est_equipe_informatique %}
|
||||||
|
<button class="btn btn-primary me-1" id="ajoutZoom">Ajout du lien zoom</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user|has_group:'direction' %}
|
{% if est_assistante_direction %}
|
||||||
<button class="btn btn-danger" id="refuserReservation" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
|
<button class="btn btn-danger me-1" 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-success me-1" id="bouton-valider">Valider</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span id="current-user-id" data-userid="{{ request.user.id }}"></span>
|
{% if est_equipe_informatique %}
|
||||||
|
<button class="btn btn-danger me-1" id="refuserReservationInfo" data-lienrefus="{% url 'gestion_salle:refuser-reservation' %}">Refuser</button>
|
||||||
|
<button class="btn btn-success me-1" id="bouton-valider-info">Valider</button>
|
||||||
|
{% endif %}
|
||||||
|
<span id="current-user-id" data-user-id="{{ user.id }}"></span>
|
||||||
<button class="btn btn-danger" id="bouton-annuler">Annuler</button>
|
<button class="btn btn-danger" id="bouton-annuler">Annuler</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 class="modal-title fs-5" id="modalReservationAttenteLabel">
|
<h1 class="modal-title fs-5" id="modalReservationAttenteLabel">
|
||||||
Liste des reservation en attente de validation
|
Liste des réservation en attente de validation
|
||||||
</h1>
|
</h1>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
app_name = "gestion_salle"
|
app_name = "gestion_salle"
|
||||||
|
|||||||
@@ -1,25 +1,32 @@
|
|||||||
from datetime import timedelta
|
|
||||||
import json
|
import json
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.shortcuts import redirect, render
|
from django.db.models import Q
|
||||||
from django.http import JsonResponse, HttpRequest
|
|
||||||
from django.forms import model_to_dict
|
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 fonction_utilitaire import fonctions_utilitaire
|
||||||
from gestion_employe.models import Employe
|
from gestion_employe.models import Employe
|
||||||
from gestion_salle.forms import ReservationForm
|
from gestion_salle.forms import ReservationForm
|
||||||
|
|
||||||
from .models import Reservation
|
from .models import Reservation
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def index(request: HttpRequest):
|
def index(request: HttpRequest):
|
||||||
|
"""Vue d'affichage de la page d'accueil de la gestion des salles"""
|
||||||
try:
|
try:
|
||||||
employe = Employe.objects.get(user=request.user)
|
employe = Employe.objects.get(user=request.user)
|
||||||
except Employe.DoesNotExist:
|
except Employe.DoesNotExist:
|
||||||
messages.error(request, "Profil employé introuvable.")
|
messages.error(request, "Profil employé introuvable.")
|
||||||
return redirect('gestion_conges:conge')
|
return redirect('gestion_conges:conge')
|
||||||
|
|
||||||
|
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe)
|
||||||
|
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = ReservationForm(request.POST)
|
form = ReservationForm(request.POST)
|
||||||
|
|
||||||
@@ -34,11 +41,11 @@ def index(request: HttpRequest):
|
|||||||
besoin_ordi = form.cleaned_data['besoin_ordi']
|
besoin_ordi = form.cleaned_data['besoin_ordi']
|
||||||
|
|
||||||
if date_fin < date_debut:
|
if date_fin < date_debut:
|
||||||
messages.error(request, "Date fin invalide.")
|
messages.error(request, "La date de fin est antérieure à la date de début.")
|
||||||
return redirect('gestion_salle:reservation-salle')
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
if heure_fin <= heure_debut:
|
if heure_fin <= heure_debut:
|
||||||
messages.error(request, "Heure invalide.")
|
messages.error(request, "L'heure de fin est antérieure à l'heure de début.")
|
||||||
return redirect('gestion_salle:reservation-salle')
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
if not request.user.first_name.strip() or not request.user.last_name.strip():
|
if not request.user.first_name.strip() or not request.user.last_name.strip():
|
||||||
@@ -51,8 +58,9 @@ def index(request: HttpRequest):
|
|||||||
created = []
|
created = []
|
||||||
current_date = date_debut
|
current_date = date_debut
|
||||||
|
|
||||||
while current_date <= date_fin:
|
print(f"Création de réservations pour {employe}")
|
||||||
|
|
||||||
|
while current_date <= date_fin:
|
||||||
reservation = Reservation.objects.create(
|
reservation = Reservation.objects.create(
|
||||||
employe=employe,
|
employe=employe,
|
||||||
date_debut=current_date,
|
date_debut=current_date,
|
||||||
@@ -71,20 +79,35 @@ def index(request: HttpRequest):
|
|||||||
|
|
||||||
messages.success(request, "Réservation(s) créée(s) avec succès.")
|
messages.success(request, "Réservation(s) créée(s) avec succès.")
|
||||||
|
|
||||||
if fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
|
# Nouvelle logique d'envoi de mails à la création :
|
||||||
|
# 1. Une demande de salle (formation/réunion) est envoyée à l'assistante de direction pour validation.
|
||||||
|
# - Si elle nécessite un lien Zoom ou un ordinateur, l'équipe informatique sera notifiée
|
||||||
|
# seulement après la validation par l'assistante.
|
||||||
|
# 2. Une demande de lien Zoom directement est envoyée à l'équipe informatique.
|
||||||
|
if salle != "lien_zoom" and fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION:
|
||||||
fonctions_utilitaire.envoyer_mail(
|
fonctions_utilitaire.envoyer_mail(
|
||||||
sujet = "Reservation de salle",
|
sujet="Demande de réservation de salle",
|
||||||
message = f"""
|
message=f"""Bonjour,
|
||||||
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}".
|
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}".
|
||||||
|
{"Cette demande nécessite un lien Zoom et/ou un ordinateur et sera traitée par l'équipe informatique après votre validation." if besoin_zoom or besoin_ordi else ""}
|
||||||
Veuillez vous connecter à la plateforme pour plus de détails.""",
|
Veuillez vous connecter à la plateforme pour plus de détails.""",
|
||||||
destinataires = fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION
|
destinataires=fonctions_utilitaire.EMAIL_ASSISTANTE_DE_DIRECTION
|
||||||
|
)
|
||||||
|
|
||||||
|
if salle == "lien_zoom" and fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE:
|
||||||
|
fonctions_utilitaire.envoyer_mail(
|
||||||
|
sujet="Demande de lien Zoom",
|
||||||
|
message=f"""Bonjour,
|
||||||
|
Une nouvelle demande de lien Zoom a été effectuée par {employe.user.first_name} {employe.user.last_name} du {date_debut.strftime('%d/%m/%Y')} au {date_fin.strftime('%d/%m/%Y')} de {heure_debut.strftime('%H:%M')} à {heure_fin.strftime('%H:%M')} pour le motif {motif}.
|
||||||
|
|
||||||
|
Veuillez vous connecter à la plateforme pour traiter cette demande.""",
|
||||||
|
destinataires=fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE
|
||||||
)
|
)
|
||||||
return redirect('gestion_salle:reservation-salle')
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
formulaire_reservation = ReservationForm()
|
|
||||||
departement = employe.departement
|
|
||||||
|
|
||||||
appartient_direction = request.user.groups.filter(name='direction').exists()
|
formulaire_reservation = ReservationForm()
|
||||||
|
# departement = employe.departement
|
||||||
|
|
||||||
liste_demande_reservation = Reservation.objects.filter(
|
liste_demande_reservation = Reservation.objects.filter(
|
||||||
employe=employe,
|
employe=employe,
|
||||||
@@ -94,13 +117,14 @@ Veuillez vous connecter à la plateforme pour plus de détails.""",
|
|||||||
context = {
|
context = {
|
||||||
'formulaire_reservation': formulaire_reservation,
|
'formulaire_reservation': formulaire_reservation,
|
||||||
'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(),
|
'nb_reservation_attente': Reservation.objects.filter(statut='en_attente').count(),
|
||||||
'appartient_au_departement_informatique': departement and departement.nom == "Systeme informatique",
|
'appartient_au_departement_informatique': est_equipe_info,
|
||||||
'appartient_direction': appartient_direction,
|
'est_assistante_direction': est_assistante,
|
||||||
|
'est_equipe_informatique': est_equipe_info,
|
||||||
'liste_demande_reservation': list(liste_demande_reservation),
|
'liste_demande_reservation': list(liste_demande_reservation),
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, "gestion_salle/index.html", context)
|
return render(request, "gestion_salle/index.html", context)
|
||||||
|
|
||||||
def liste_reservation(request:HttpRequest):
|
def liste_reservation(request:HttpRequest):
|
||||||
"""Vue d'affichage des creneaux disponibles"""
|
"""Vue d'affichage des creneaux disponibles"""
|
||||||
reservations = Reservation.objects.filter(statut = "validee")
|
reservations = Reservation.objects.filter(statut = "validee")
|
||||||
@@ -115,8 +139,6 @@ def liste_reservation(request:HttpRequest):
|
|||||||
else:
|
else:
|
||||||
color = "#dc3545"
|
color = "#dc3545"
|
||||||
|
|
||||||
print(dict(Reservation.TYPE_CHOICES).get(reservation.salle))
|
|
||||||
|
|
||||||
liste_reservation.append({
|
liste_reservation.append({
|
||||||
"guid": reservation.pk,
|
"guid": reservation.pk,
|
||||||
"title": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
|
"title": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
|
||||||
@@ -129,7 +151,35 @@ def liste_reservation(request:HttpRequest):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def liste_reservation_attente(request):
|
def liste_reservation_attente(request):
|
||||||
|
"""Liste des réservations en attente, filtrée selon le profil :
|
||||||
|
- Assistante de direction : voit les salles à valider (pas les liens choisis).
|
||||||
|
- Équipe informatique : voit les liens choisis et les salles demandant zoom/ordi non traités.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(user=request.user)
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
return JsonResponse({"success": False, "message": "Profil employé introuvable"})
|
||||||
|
|
||||||
|
est_informatique = fonctions_utilitaire.est_equipe_informatique(employe)
|
||||||
|
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
|
||||||
|
|
||||||
reservations = Reservation.objects.filter(statut="en_attente")
|
reservations = Reservation.objects.filter(statut="en_attente")
|
||||||
|
|
||||||
|
if est_assistante:
|
||||||
|
# L'assistante voit les demandes de salle (formation / réunion)
|
||||||
|
# Elle ne reçoit pas les demandes de simple lien (lien_zoom) qui vont à l'équipe info.
|
||||||
|
reservations = reservations.exclude(salle="lien_zoom")
|
||||||
|
elif est_informatique:
|
||||||
|
# L'équipe informatique voit :
|
||||||
|
# - les demandes de lien zoom directs
|
||||||
|
# - les demandes de salle avec besoin_zoom ou besoin_ordi (déjà validés par l'assistance)
|
||||||
|
# pour lesquelles le lien_zoom n'a pas encore était fourni (lien_zoom vide)
|
||||||
|
reservations = reservations.filter(
|
||||||
|
Q(salle="lien_zoom") | Q(besoin_zoom=True) | Q(besoin_ordi=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Test :", [reservation.employe for reservation in reservations])
|
||||||
|
|
||||||
liste_reservation = [
|
liste_reservation = [
|
||||||
{
|
{
|
||||||
**model_to_dict(reservation),
|
**model_to_dict(reservation),
|
||||||
@@ -137,21 +187,20 @@ def liste_reservation_attente(request):
|
|||||||
"employe_id": reservation.employe.user.id,
|
"employe_id": reservation.employe.user.id,
|
||||||
"salle": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
|
"salle": dict(Reservation.TYPE_CHOICES).get(reservation.salle),
|
||||||
'statut': dict(Reservation.STATUT).get(reservation.statut),
|
'statut': dict(Reservation.STATUT).get(reservation.statut),
|
||||||
|
"date_evenement": reservation.date_debut.strftime('%Y-%m-%d'),
|
||||||
} for reservation in reservations
|
} for reservation in reservations
|
||||||
]
|
]
|
||||||
|
|
||||||
return JsonResponse(liste_reservation, safe=False)
|
return JsonResponse(liste_reservation, safe=False)
|
||||||
|
|
||||||
def detail_reservation(request:HttpRequest, reservation_id:int):
|
def detail_reservation(request:HttpRequest, reservation_id:int):
|
||||||
|
|
||||||
reservation = Reservation.objects.get(id=reservation_id)
|
reservation = Reservation.objects.get(id=reservation_id)
|
||||||
employe = reservation.employe.user
|
employe = reservation.employe.user
|
||||||
|
|
||||||
reservation_json = {
|
reservation_json = {
|
||||||
|
|
||||||
'id_reservation': reservation_id,
|
'id_reservation': reservation_id,
|
||||||
'employe': f"{employe.first_name} {employe.last_name}",
|
'employe': f"{employe.first_name} {employe.last_name}",
|
||||||
'salle': reservation.salle,
|
'salle': dict(Reservation.TYPE_CHOICES).get(reservation.salle),
|
||||||
'statut': dict(Reservation.STATUT).get(reservation.statut),
|
'statut': dict(Reservation.STATUT).get(reservation.statut),
|
||||||
'date_evenement': reservation.date_debut.strftime('%Y-%m-%d'),
|
'date_evenement': reservation.date_debut.strftime('%Y-%m-%d'),
|
||||||
'heure_debut': reservation.heure_debut.strftime('%H:%M'),
|
'heure_debut': reservation.heure_debut.strftime('%H:%M'),
|
||||||
@@ -166,49 +215,99 @@ def detail_reservation(request:HttpRequest, reservation_id:int):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def ajouter_lien_zoom(request:HttpRequest):
|
def ajouter_lien_zoom(request:HttpRequest):
|
||||||
"""Vue de gestion de l'ajout du lien zoom"""
|
"""Traitement de la réservation par l'équipe informatique :
|
||||||
if request.method == 'POST':
|
- Ajoute lien zoom si demandé.
|
||||||
reservation_id= request.POST['id_reservation']
|
- Valide une réservation qui exigeait un traitement par l'équipe informatique.
|
||||||
try:
|
- Envoie un mail à toutes les équipes et au demandeur après validation.
|
||||||
reservation = Reservation.objects.get(id=reservation_id)
|
"""
|
||||||
except reservation.DoesNotExist:
|
reservation = None
|
||||||
messages.error(request, "La resevertion selectionné n'existe pas.")
|
|
||||||
return redirect("salle")
|
|
||||||
|
|
||||||
if not (reservation.besoin_zoom or reservation.salle == "lien_zoom"):
|
|
||||||
messages.error(request, "Cette réservation ne nécessite pas de lien Zoom.")
|
|
||||||
return redirect('gestion_salle:reservation-salle')
|
|
||||||
elif reservation.statut in ["annulee", "refusee"]:
|
|
||||||
messages.error(request, "Le lien Zoom ne peut être ajouté pour les réservations annulée ou refusée.")
|
|
||||||
return redirect('gestion_salle:reservation-salle')
|
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == 'POST':
|
||||||
lien = request.POST.get("lien_zoom")
|
reservation_id = request.POST.get('id_reservation')
|
||||||
reservation.lien_zoom = lien
|
try:
|
||||||
reservation.statut = "validee"
|
reservation = Reservation.objects.get(id=reservation_id)
|
||||||
|
except (Reservation.DoesNotExist, ValueError):
|
||||||
|
messages.error(request, "La réservation sélectionnée n'existe pas.")
|
||||||
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(user=request.user)
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
messages.error(request, "Profil employé introuvable.")
|
||||||
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
|
# Vérification : seule l'équipe informatique peut traiter ces demandes
|
||||||
|
if not fonctions_utilitaire.est_equipe_informatique(employe):
|
||||||
|
messages.error(request, "Vous n'êtes pas autorisé à traiter cette demande.")
|
||||||
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
|
if reservation.salle == "lien_zoom" or reservation.besoin_zoom:
|
||||||
|
lien = request.POST.get("lien_zoom")
|
||||||
|
if not lien:
|
||||||
|
messages.error(request, "Veuillez fournir un lien Zoom.")
|
||||||
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
reservation.lien_zoom = lien
|
||||||
|
|
||||||
|
reservation.statut = "validee"
|
||||||
reservation.save()
|
reservation.save()
|
||||||
messages.success(request, "Le lien Zoom a été ajouté et la réservation a été validée. ")
|
messages.success(request, "La demande a été traitée et validée. ")
|
||||||
|
|
||||||
|
# Mail au demandeur
|
||||||
|
fonctions_utilitaire.envoyer_mail(
|
||||||
|
sujet="Réservation traitée",
|
||||||
|
message=f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre demande de {dict(Reservation.TYPE_CHOICES).get(reservation.salle)}{' avec lien Zoom' if reservation.lien_zoom else ''} a été traitée. Veuillez vous connecter à la plateforme pour plus de détails.""",
|
||||||
|
destinataires=[reservation.employe.user.email]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Mail aux emails de l'équipe informatique (hors utilisateur courant)
|
||||||
|
autres_membres = Employe.objects.filter(departement__nom="Systeme informatique").exclude(id=employe.id).values_list('user__email', flat=True)
|
||||||
|
if autres_membres:
|
||||||
|
fonctions_utilitaire.envoyer_mail(
|
||||||
|
sujet="Réservation validée par l'équipe informatique",
|
||||||
|
message=f"""Bonjour,
|
||||||
|
La réservation {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} pour {reservation.employe.user.first_name} {reservation.employe.user.last_name} a été traitée et validée par {employe.user.first_name} {employe.user.last_name}.\n\nVeuillez vous connecter à la plateforme pour plus de détails.""",
|
||||||
|
destinataires=list(autres_membres)
|
||||||
|
)
|
||||||
|
|
||||||
return redirect('gestion_salle:reservation-salle')
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def annuler_reservation(request:HttpRequest):
|
def annuler_reservation(request:HttpRequest):
|
||||||
"""Vue de gestion de l'annulation de la reservation"""
|
"""Vue de gestion de l'annulation de la reservation, réservée à l'employé demandeur ou aux validateurs."""
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
reservation_id= request.POST['id_reservation']
|
reservation_id= request.POST['id_reservation']
|
||||||
try:
|
try:
|
||||||
reservation = Reservation.objects.get(id=reservation_id)
|
reservation = Reservation.objects.get(id=reservation_id)
|
||||||
except Reservation.DoesNotExist:
|
except Reservation.DoesNotExist:
|
||||||
messages.error(request, "La resevertion selectionné n'existe pas.")
|
messages.error(request, "La reservation selectionné n'existe pas.")
|
||||||
return redirect("salle")
|
return redirect("gestion_salle:reservation-salle")
|
||||||
|
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(user=request.user)
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
messages.error(request, "Profil employé introuvable.")
|
||||||
|
return redirect("gestion_salle:reservation-salle")
|
||||||
|
|
||||||
|
# Seul l'employé demandeur ou un validateur (assistante / équipe info) peut annuler
|
||||||
|
est_demandeur = reservation.employe == employe
|
||||||
|
est_validateur = fonctions_utilitaire.est_assistante_direction(employe) or fonctions_utilitaire.est_equipe_informatique(employe)
|
||||||
|
|
||||||
|
if not est_demandeur and not est_validateur:
|
||||||
|
messages.error(request, "Vous n'êtes pas autorisé à annuler cette réservation.")
|
||||||
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
reservation.statut = 'annulee'
|
reservation.statut = 'annulee'
|
||||||
reservation.save()
|
reservation.save()
|
||||||
messages.success(request, "Votre réservation a été annulée.")
|
messages.success(request, "La réservation a été annulée.")
|
||||||
return redirect('gestion_salle:reservation-salle')
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def valider_reservation(request: HttpRequest):
|
def valider_reservation(request: HttpRequest):
|
||||||
"""Validation d'une réservation"""
|
"""Validation d'une réservation selon le profil :
|
||||||
|
- Assistante de direction : valide les demandes de salles (sauf lien_zoom) ;
|
||||||
|
- Équipe informatique : valide les demandes de lien_zoom et les
|
||||||
|
demandes de salle avec besoin zoom/ordi restées en attente après la première correction.
|
||||||
|
"""
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
reservation_id = request.POST.get('id_reservation')
|
reservation_id = request.POST.get('id_reservation')
|
||||||
|
|
||||||
@@ -216,39 +315,122 @@ def valider_reservation(request: HttpRequest):
|
|||||||
reservation = Reservation.objects.get(id=reservation_id)
|
reservation = Reservation.objects.get(id=reservation_id)
|
||||||
except Reservation.DoesNotExist:
|
except Reservation.DoesNotExist:
|
||||||
messages.error(request, "La réservation sélectionnée n'existe pas.")
|
messages.error(request, "La réservation sélectionnée n'existe pas.")
|
||||||
return redirect("salle")
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
reservation.statut = 'validee'
|
try:
|
||||||
reservation.save()
|
employe = Employe.objects.get(user=request.user)
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
messages.error(request, "Profil employé introuvable.")
|
||||||
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
fonctions_utilitaire.envoyer_mail(
|
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
|
||||||
sujet = "Reservation de salle",
|
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe)
|
||||||
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.")
|
if est_assistante:
|
||||||
|
# L'assistante ne doit pas traiter les simple lien_zoom
|
||||||
|
if reservation.salle == "lien_zoom":
|
||||||
|
messages.error(request, "Les demandes de lien Zoom sont traitées par l'équipe informatique.")
|
||||||
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
|
if reservation.besoin_zoom or reservation.besoin_ordi:
|
||||||
|
# La demande reste en attente, l'équipe informatique doit être notifiée
|
||||||
|
# L'assistante a donné sa validation "salle", on garde le statut "en_attente"
|
||||||
|
# pour que l'équipe informatique puisse traiter, et on envoie le mail à l'équipe.
|
||||||
|
if fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE:
|
||||||
|
fonctions_utilitaire.envoyer_mail(
|
||||||
|
sujet="Réservation de salle à traiter par l'équipe informatique",
|
||||||
|
message=f"""Bonjour,
|
||||||
|
L'assistante de direction a validé la réservation de la salle {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} pour {reservation.employe.user.first_name} {reservation.employe.user.last_name} le {reservation.date_debut.strftime('%d/%m/%Y')} (de {reservation.heure_debut.strftime('%H:%M')} à {reservation.heure_fin.strftime('%H:%M')}).
|
||||||
|
Cette demande nécessite {"un lien zoom" if reservation.besoin_zoom else ""}{" - un ordinateur" if reservation.besoin_ordi else ""}.
|
||||||
|
|
||||||
|
Veuillez vous connecter à la plateforme pour traiter cette demande.""",
|
||||||
|
destinataires=fonctions_utilitaire.EMAIL_EQUIPE_INFORMATIQUE
|
||||||
|
)
|
||||||
|
|
||||||
|
messages.success(request, "Réservation validée, une demande a été envoyée à l'équipe informatique.")
|
||||||
|
reservation.statut = "en_attente" # reste en attente pour l'équipe info
|
||||||
|
reservation.save()
|
||||||
|
else:
|
||||||
|
# Validation directe sans traitement informatique
|
||||||
|
reservation.statut = "validee"
|
||||||
|
reservation.save()
|
||||||
|
fonctions_utilitaire.envoyer_mail(
|
||||||
|
sujet="Réservation de salle validée",
|
||||||
|
message=f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre réservation de la {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]
|
||||||
|
)
|
||||||
|
|
||||||
|
elif est_equipe_info:
|
||||||
|
# L'équipe informatique traite le reste : lien_zoom et besoins info
|
||||||
|
if reservation.salle == "lien_zoom" or reservation.besoin_zoom or reservation.besoin_ordi:
|
||||||
|
if reservation.besoin_zoom and not reservation.lien_zoom:
|
||||||
|
# La demande exige un lien zoom qui n'a pas été encore rempli,
|
||||||
|
# il est préférable de passer par ajouter_lien_zoom. On laisse traiter.
|
||||||
|
messages.success(request, "Veuillez ajouter le lien Zoom via le champs dédié.")
|
||||||
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
|
reservation.statut = "validee"
|
||||||
|
reservation.save()
|
||||||
|
|
||||||
|
# Mail au demandeur + autres membres de l'équipe
|
||||||
|
fonctions_utilitaire.envoyer_mail(
|
||||||
|
sujet="Réservation validée",
|
||||||
|
message=f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre demande de {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} a été traitée et validée. Veuillez vous connecter pour plus de détails.""",
|
||||||
|
destinataires=[reservation.employe.user.email]
|
||||||
|
)
|
||||||
|
|
||||||
|
autres = Employe.objects.filter(departement__nom="Systeme informatique").exclude(id=employe.id).values_list('user__email', flat=True)
|
||||||
|
if autres:
|
||||||
|
fonctions_utilitaire.envoyer_mail(
|
||||||
|
sujet="Réservation traitée par un collègue",
|
||||||
|
message=f"""Bonjour {request.user.first_name} {request.user.last_name}, la demande de {dict(Reservation.TYPE_CHOICES).get(reservation.salle)} du {reservation.date_debut.strftime('%d/%m/%Y')} pour {reservation.employe.user.first_name} {reservation.employe.user.last_name} a été traitée et validée par {employe.user.first_name} {employe.user.last_name}. Veuillez vous connecter pour plus de détails.""",
|
||||||
|
destinataires=list(autres)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
messages.error(request, "Cette réservation ne relève pas de l'équipe informatique.")
|
||||||
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
else:
|
||||||
|
messages.error(request, "Vous n'êtes pas autorisé à effectuer cette validation.")
|
||||||
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
|
messages.success(request, f"La réservation de {request.user.first_name} {request.user.last_name} a été validée avec succès.")
|
||||||
return redirect('gestion_salle:reservation-salle')
|
return redirect('gestion_salle:reservation-salle')
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def refuser_reservation(request: HttpRequest):
|
def refuser_reservation(request:HttpRequest):
|
||||||
"""Refuser une réservation"""
|
"""Vue de gestion de refus de la reservation, autorisée à l'assistant de direction
|
||||||
if request.method == 'POST':
|
et l'équipe informatique selon le type de demande."""
|
||||||
reservation_id = json.loads(request.body).get('id_reservation')
|
data = json.loads(request.body)
|
||||||
|
reservation_id = data.get("id_reservation")
|
||||||
|
try:
|
||||||
|
reservation = Reservation.objects.get(id=reservation_id)
|
||||||
|
except Reservation.DoesNotExist:
|
||||||
|
return JsonResponse({"message": "La reservation selectionné n'existe pas."})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
reservation = Reservation.objects.get(id=reservation_id)
|
employe = Employe.objects.get(user=request.user)
|
||||||
except Reservation.DoesNotExist:
|
except Employe.DoesNotExist:
|
||||||
messages.error(request, "La réservation n'existe pas.")
|
return JsonResponse({"message": "Profil employé introuvable."})
|
||||||
return JsonResponse({"message": "Une erreur s'est produite lors de l'annulation de la reservation."})
|
|
||||||
|
|
||||||
reservation.statut = 'refusee'
|
est_assistante = fonctions_utilitaire.est_assistante_direction(employe)
|
||||||
reservation.save()
|
est_equipe_info = fonctions_utilitaire.est_equipe_informatique(employe)
|
||||||
|
|
||||||
fonctions_utilitaire.envoyer_mail(
|
peut_refuser = False
|
||||||
sujet = "Reservation de salle",
|
if est_assistante and reservation.salle != "lien_zoom":
|
||||||
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.""",
|
peut_refuser = True
|
||||||
destinataires = [reservation.employe.user.email]
|
elif est_equipe_info and (reservation.salle == "lien_zoom" or reservation.besoin_zoom or reservation.besoin_ordi):
|
||||||
)
|
peut_refuser = True and (not reservation.besoin_zoom or reservation.lien_zoom)
|
||||||
|
|
||||||
return JsonResponse({"message": "Réservation refusée avec succès."})
|
if not peut_refuser:
|
||||||
|
return JsonResponse({"message": "Vous n'êtes pas autorisé à refuser cette réservation."})
|
||||||
|
|
||||||
|
reservation.statut = "refusee"
|
||||||
|
reservation.save()
|
||||||
|
|
||||||
|
fonctions_utilitaire.envoyer_mail(
|
||||||
|
sujet = "Réservation refusée",
|
||||||
|
message = f"""Bonjour {reservation.employe.user.first_name} {reservation.employe.user.last_name}, votre demande de réservation du {reservation.date_debut.strftime('%d/%m/%Y')} de {reservation.date_debut.strftime('%d/%m/%Y')} de {reservation.heure_debut.strftime('%H:%M')} à {reservation.heure_fin.strftime('%H:%M')} a été refusée.""",
|
||||||
|
destinataires = [reservation.employe.user.email]
|
||||||
|
)
|
||||||
|
|
||||||
|
return JsonResponse({"message": "Réservation refusée avec succès."})
|
||||||
6
staticfiles/bootstrap.min.css
vendored
6
staticfiles/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -1,3 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class GestionCongeConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'gestion_conge'
|
|
||||||
@@ -3,7 +3,6 @@ const $ = (element) => document.getElementById(element);
|
|||||||
const url_liste_conge_attente = $("liste-demande-conges").dataset.url
|
const url_liste_conge_attente = $("liste-demande-conges").dataset.url
|
||||||
|
|
||||||
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
|
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
|
||||||
// layout : "fitColumns",
|
|
||||||
columns: [
|
columns: [
|
||||||
{"title": "Nom et Prénom", "field": "prenom_nom"},
|
{"title": "Nom et Prénom", "field": "prenom_nom"},
|
||||||
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
|
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
|
|||||||
{"title": "Projet", "field": "projet"},
|
{"title": "Projet", "field": "projet"},
|
||||||
{"title": "Téléphone", "field": "telephone"},
|
{"title": "Téléphone", "field": "telephone"},
|
||||||
],
|
],
|
||||||
// ajaxURL: url_liste_employe,
|
|
||||||
pagination: true,
|
pagination: true,
|
||||||
paginationSize: 10,
|
paginationSize: 10,
|
||||||
responsiveLayout : "hide"
|
responsiveLayout : "hide"
|
||||||
@@ -19,7 +18,6 @@ fetch(url_liste_employe)
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success){
|
if (data.success){
|
||||||
console.log(data.data)
|
|
||||||
tableau_liste_employe.setData(data.data);
|
tableau_liste_employe.setData(data.data);
|
||||||
}else{
|
}else{
|
||||||
alert(data.message);
|
alert(data.message);
|
||||||
@@ -29,6 +27,7 @@ fetch(url_liste_employe)
|
|||||||
|
|
||||||
tableau_liste_employe.on("rowClick", function (row, rowData) {
|
tableau_liste_employe.on("rowClick", function (row, rowData) {
|
||||||
const data = rowData.getData();
|
const data = rowData.getData();
|
||||||
|
|
||||||
document.getElementById('detail-id').value = data.id;
|
document.getElementById('detail-id').value = data.id;
|
||||||
document.getElementById('detail-matricule').value = data.matricule;
|
document.getElementById('detail-matricule').value = data.matricule;
|
||||||
document.getElementById('detail-employe').value = data.employe;
|
document.getElementById('detail-employe').value = data.employe;
|
||||||
@@ -211,23 +210,31 @@ $("enregistrerDetail").addEventListener("click", function() {
|
|||||||
const fonction = document.getElementById('detail-fonction').value;
|
const fonction = document.getElementById('detail-fonction').value;
|
||||||
const dateEmbauche = document.getElementById('detail-dateEmbauche').value;
|
const dateEmbauche = document.getElementById('detail-dateEmbauche').value;
|
||||||
const matricule = document.getElementById('detail-matricule').value;
|
const matricule = document.getElementById('detail-matricule').value;
|
||||||
|
const departement = document.getElementById('detail-departement').value;
|
||||||
|
|
||||||
const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
|
const url_enregistrer_detail = document.getElementById("modalDetailEmployeBody").dataset.url;
|
||||||
|
|
||||||
|
console.log("Le lien de l'enregistrement :", document.getElementById("modalDetailEmployeBody").dataset)
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
"id": id_,
|
||||||
|
"fonction": fonction,
|
||||||
|
"date_embauche": dateEmbauche,
|
||||||
|
"matricule": matricule,
|
||||||
|
"departement": departement
|
||||||
|
};
|
||||||
|
|
||||||
fetch(url_enregistrer_detail, {
|
fetch(url_enregistrer_detail, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
|
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(payload)
|
||||||
"id": id_,
|
|
||||||
"fonction": fonction,
|
|
||||||
"date_embauche": dateEmbauche,
|
|
||||||
"matricule": matricule
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
console.log('Réponse du serveur:', data);
|
||||||
alert(data.message);
|
alert(data.message);
|
||||||
location.reload();
|
location.reload();
|
||||||
})
|
})
|
||||||
@@ -261,4 +268,70 @@ const listeContratExpirant = new Tabulator("#listeContratExpirant", {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
|
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/* let tabContrats = null;
|
||||||
|
|
||||||
|
document.getElementById("modalContratsEmploye").addEventListener("show.bs.modal", function () {
|
||||||
|
|
||||||
|
const employeId = document.getElementById("detail-id").value;
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}); */
|
||||||
|
|||||||
@@ -13,18 +13,20 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
|
|||||||
inputFormat:"yyyy-MM-dd",
|
inputFormat:"yyyy-MM-dd",
|
||||||
outputFormat:"dd/MM/yy",
|
outputFormat:"dd/MM/yy",
|
||||||
}},
|
}},
|
||||||
|
{"title": "Fichier", "field": "certificat", formatter:"link", formatterParams:{
|
||||||
|
label:"Voir le fichier",
|
||||||
|
target:"_blank",
|
||||||
|
}},
|
||||||
],
|
],
|
||||||
ajaxURL: url_certificat,
|
ajaxURL: url_certificat,
|
||||||
})
|
})
|
||||||
const enregistrerProfil = $("enregistrerProfil");
|
const enregistrerProfil = $("enregistrerProfil");
|
||||||
|
|
||||||
enregistrerProfil.addEventListener("click", (e) => {
|
enregistrerProfil.addEventListener("click", (e) => {
|
||||||
|
|
||||||
const url = $("information-personnelles").dataset.url;
|
const url = $("information-personnelles").dataset.url;
|
||||||
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
|
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
formData.append("nom", $("nom").value);
|
formData.append("nom", $("nom").value);
|
||||||
formData.append("prenom", $("prenom").value);
|
formData.append("prenom", $("prenom").value);
|
||||||
formData.append("email", $("email").value);
|
formData.append("email", $("email").value);
|
||||||
@@ -32,6 +34,10 @@ enregistrerProfil.addEventListener("click", (e) => {
|
|||||||
formData.append("adresse", $("adresse").value);
|
formData.append("adresse", $("adresse").value);
|
||||||
formData.append("sexe", $("sexe").value);
|
formData.append("sexe", $("sexe").value);
|
||||||
formData.append("date_naissance", $("date_naissance").value);
|
formData.append("date_naissance", $("date_naissance").value);
|
||||||
|
const photoInput = $("photo");
|
||||||
|
if (photoInput.files.length > 0) {
|
||||||
|
formData.append("photo", photoInput.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -23,29 +23,25 @@ btnEnregistrerBailleur.addEventListener('click', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
|
|
||||||
table = new Tabulator("#table-bailleurs", {
|
table = new Tabulator("#table-bailleurs", {
|
||||||
ajaxURL: "/gestion-projet/bailleurs/",
|
ajaxURL: "/gestion-projet/bailleurs/",
|
||||||
layout: "fitColumns",
|
layout: "fitColumns",
|
||||||
pagination: "local",
|
pagination: "local",
|
||||||
paginationSize: 5,
|
paginationSize: 5,
|
||||||
|
|
||||||
columns: [
|
columns: [
|
||||||
{title: "#", formatter: "rownum", width: 60},
|
{title: "#", formatter: "rownum", width: 60},
|
||||||
{title: "Organisme", field: "nom_organisme"},
|
{title: "Organisme", field: "nom_organisme"},
|
||||||
{title: "Contact", field: "contact"},
|
{title: "Contact", field: "contact"},
|
||||||
{title: "Email", field: "email"},
|
{title: "Email", field: "email"},
|
||||||
{title: "Pays", field: "pays"},
|
{title: "Pays", field: "pays"},
|
||||||
],
|
],
|
||||||
rowDblClick: function(e, row) {
|
rowDblClick: function(e, row) {
|
||||||
const data = row.getData();
|
const data = row.getData();
|
||||||
|
|
||||||
if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) {
|
if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) {
|
||||||
supprimerBailleur(data.id);
|
supprimerBailleur(data.id);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const $ = (element) => document.getElementById(element)
|
const $ = (element) => document.getElementById(element)
|
||||||
const url_liste_projet = $("tableau-liste-projet").dataset.url;
|
const url_liste_projet = $("tableau-liste-projet").dataset.url;
|
||||||
|
|
||||||
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
|
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
|
||||||
fitColumns: true,
|
fitColumns: true,
|
||||||
responsiveLayout : true,
|
responsiveLayout : true,
|
||||||
@@ -39,7 +40,6 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
|
|||||||
$("detail_date_debut").value = data.date_debut;
|
$("detail_date_debut").value = data.date_debut;
|
||||||
$("detail_date_fin").value = data.date_fin;
|
$("detail_date_fin").value = data.date_fin;
|
||||||
$("detail_numero_convention").value = data.numero_convention;
|
$("detail_numero_convention").value = data.numero_convention;
|
||||||
$("detail_type_projet").value = data.type_projet;
|
|
||||||
Array.from($("detail_domaine_recherche").options).forEach(option => {
|
Array.from($("detail_domaine_recherche").options).forEach(option => {
|
||||||
if (data.domaine_recherche.includes(option.value)) {
|
if (data.domaine_recherche.includes(option.value)) {
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
@@ -61,4 +61,4 @@ tableau_liste_projet.on("rowClick", function (row, rowData) {
|
|||||||
// $('detail-projet-form').addEventListener('submit', (e) => {
|
// $('detail-projet-form').addEventListener('submit', (e) => {
|
||||||
// e.preventDefault();
|
// e.preventDefault();
|
||||||
// new FormData($("detail-projet-form"));
|
// new FormData($("detail-projet-form"));
|
||||||
// })
|
// })
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
|
|||||||
const modal = new bootstrap.Modal($("modalDetailReservation"));
|
const modal = new bootstrap.Modal($("modalDetailReservation"));
|
||||||
modal.show();
|
modal.show();
|
||||||
fetch (`/gestion-salle/revervation/details/${event.guid}`)
|
fetch (`/gestion-salle/revervation/details/${event.guid}`)
|
||||||
.then(response => response.json())
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Erreur HTTP ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
currentReservationId = data.id_reservation;
|
currentReservationId = data.id_reservation;
|
||||||
$("id_reservation_detail").value = data.id_reservation;
|
$("id_reservation_detail").value = data.id_reservation;
|
||||||
@@ -27,13 +32,20 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), {
|
|||||||
$("motif_reservation").value=data.motif_reservation;
|
$("motif_reservation").value=data.motif_reservation;
|
||||||
$("besoin_zoom").checked=data.besoin_zoom;
|
$("besoin_zoom").checked=data.besoin_zoom;
|
||||||
$("besoin_ordinateur").checked=data.besoin_ordinateur;
|
$("besoin_ordinateur").checked=data.besoin_ordinateur;
|
||||||
$("lien_zoom").value=data.lien_zoom;
|
$("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){
|
if (data.besoin_zoom === false){
|
||||||
$("lien_zoom_container").className = "d-none";
|
$("lien_zoom_container").className = "d-none";
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
.catch(error => console.error("Impossible de charger les détails de la réservation :", error));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,12 +87,11 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
$("bouton-annuler").addEventListener("click", (e) => {
|
$("bouton-annuler").addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
||||||
const url_annuler = $("formulaire-details").dataset.urlannuler;
|
const url_annuler = $("formulaire-details").dataset.urlannuler;
|
||||||
|
|
||||||
console.log("URL d'annulation :");
|
|
||||||
console.log($("formulaire-details"))
|
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
fetch(
|
fetch(
|
||||||
@@ -129,6 +140,8 @@ if($("ajoutZoom")){
|
|||||||
|
|
||||||
if($("refuserReservation")){
|
if($("refuserReservation")){
|
||||||
$("refuserReservation").addEventListener("click", (e) => {
|
$("refuserReservation").addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value;
|
||||||
const url = e.currentTarget.dataset.lienrefus;
|
const url = e.currentTarget.dataset.lienrefus;
|
||||||
const idRes = $("id_reservation_detail").value;
|
const idRes = $("id_reservation_detail").value;
|
||||||
@@ -155,7 +168,7 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
|
|||||||
columns: [
|
columns: [
|
||||||
{title: "Employé", field: "employe"},
|
{title: "Employé", field: "employe"},
|
||||||
{title: "Salle", field: "salle"},
|
{title: "Salle", field: "salle"},
|
||||||
{title: "Date de l'evenement", field: "date_debut"},
|
{title: "Date de l'evenement", field: "date_evenement"},
|
||||||
{title: "Heure de début", field: "heure_debut"},
|
{title: "Heure de début", field: "heure_debut"},
|
||||||
{title: "Heure de fin", field: "heure_fin"},
|
{title: "Heure de fin", field: "heure_fin"},
|
||||||
{title: "Motif de reservation", field: "motif_reservation"},
|
{title: "Motif de reservation", field: "motif_reservation"},
|
||||||
@@ -167,11 +180,14 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente"
|
|||||||
tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
||||||
const data = rowData.getData();
|
const data = rowData.getData();
|
||||||
|
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
if(data.besoin_zoom === false){
|
if(data.besoin_zoom === false){
|
||||||
$("lien_zoom_container").className = 'd-none';
|
$("lien_zoom_container").className = 'd-none';
|
||||||
}
|
}
|
||||||
|
|
||||||
const id_user = $("current-user-id").dataset.userid;
|
const id_user = $("current-user-id").dataset.userId;
|
||||||
|
|
||||||
$("id_reservation_detail").value = data.id;
|
$("id_reservation_detail").value = data.id;
|
||||||
$("id_reservation_refus").value = data.id;
|
$("id_reservation_refus").value = data.id;
|
||||||
$("id_reservation_zoom").value = data.id;
|
$("id_reservation_zoom").value = data.id;
|
||||||
@@ -179,8 +195,7 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
|||||||
$("employe").value=data.employe;
|
$("employe").value=data.employe;
|
||||||
$("salle").value=data.salle;
|
$("salle").value=data.salle;
|
||||||
$("statut-reservation").innerHTML=data.statut;
|
$("statut-reservation").innerHTML=data.statut;
|
||||||
$("date_debut").value = data.date_debut;
|
$("date_evenement").value = data.date_evenement;
|
||||||
$("date_fin").value = data.date_fin;
|
|
||||||
$("heure_debut").value=data.heure_debut;
|
$("heure_debut").value=data.heure_debut;
|
||||||
$("heure_fin").value=data.heure_fin;
|
$("heure_fin").value=data.heure_fin;
|
||||||
$("motif_reservation").value=data.motif_reservation;
|
$("motif_reservation").value=data.motif_reservation;
|
||||||
@@ -188,6 +203,14 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
|||||||
$("besoin_ordinateur").checked=data.besoin_ordi;
|
$("besoin_ordinateur").checked=data.besoin_ordi;
|
||||||
$("lien_zoom").value=data.lien_zoom;
|
$("lien_zoom").value=data.lien_zoom;
|
||||||
|
|
||||||
|
const refuserReservation = document.querySelector("#refuserReservation");
|
||||||
|
const bouton_valider = document.querySelector("#bouton-valider");
|
||||||
|
|
||||||
|
if((refuserReservation | bouton_valider) && data.statut === "En attente"){
|
||||||
|
refuserReservation.style.display = "inline-block";
|
||||||
|
bouton_valider.style.display = "inline-block";
|
||||||
|
}
|
||||||
|
|
||||||
if (id_user != data.employe_id){
|
if (id_user != data.employe_id){
|
||||||
$("bouton-annuler").className = "d-none";
|
$("bouton-annuler").className = "d-none";
|
||||||
}
|
}
|
||||||
@@ -195,4 +218,4 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => {
|
|||||||
const modal = new bootstrap.Modal($("modalDetailReservation"));
|
const modal = new bootstrap.Modal($("modalDetailReservation"));
|
||||||
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
|
bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide();
|
||||||
modal.show();
|
modal.show();
|
||||||
})
|
})
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user