feature: Affichage de la liste des bailleur #14
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# db.sqlite3
|
||||||
|
# venv/*
|
||||||
|
# media/*
|
||||||
|
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
db.sqlite3
|
||||||
|
venv/
|
||||||
|
.env
|
||||||
0
SIRH/__init__.py
Normal file
0
SIRH/__init__.py
Normal file
16
SIRH/asgi.py
Normal file
16
SIRH/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for SIRH project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'SIRH.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
159
SIRH/settings.py
Normal file
159
SIRH/settings.py
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
"""
|
||||||
|
Django settings for SI_RH project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.2.4.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure--wdb9t(77rvyac$_q!n5gw86&0r(0&&j171v9h!-_$jahsza*5'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ["https://support.cerfig.org", "support.cerfig.org"]
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'gestion_employe',
|
||||||
|
'gestion_conge',
|
||||||
|
'gestion_projet',
|
||||||
|
'gestion_salle',
|
||||||
|
'simple_sso.sso_server'
|
||||||
|
]
|
||||||
|
|
||||||
|
LOGIN_URL = 'login'
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'SIRH.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [os.path.join(BASE_DIR, "SIRH", "templates", "SIRH")],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'SIRH.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||||
|
|
||||||
|
# DATABASES = {
|
||||||
|
# 'default': {
|
||||||
|
# 'ENGINE': 'django.db.backends.mysql',
|
||||||
|
# 'NAME': 'sirh',
|
||||||
|
# 'USER': 'sirh',
|
||||||
|
# 'PASSWORD': 'sirh-cerfig',
|
||||||
|
# 'HOST': 'localhost',
|
||||||
|
# 'PORT': '3306',
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'fr-fr'
|
||||||
|
|
||||||
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
|
||||||
|
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / 'static',
|
||||||
|
]
|
||||||
|
|
||||||
|
MEDIA_URL = "/media/"
|
||||||
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
|
EMAIL_HOST = 'ssl0.ovh.net'
|
||||||
|
EMAIL_PORT = 465
|
||||||
|
EMAIL_USE_SSL = True
|
||||||
|
EMAIL_USE_TLS = False
|
||||||
|
EMAIL_HOST_USER = 'support.it@cerfig.org'
|
||||||
|
EMAIL_HOST_PASSWORD = 'Cerfig2025'
|
||||||
|
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||||
BIN
SIRH/static/SIRH/PS1.jpg
Normal file
BIN
SIRH/static/SIRH/PS1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
BIN
SIRH/static/SIRH/cerfig.jpeg
Normal file
BIN
SIRH/static/SIRH/cerfig.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
2078
SIRH/static/SIRH/icons.css
Normal file
2078
SIRH/static/SIRH/icons.css
Normal file
File diff suppressed because it is too large
Load Diff
BIN
SIRH/static/SIRH/per1.webp
Normal file
BIN
SIRH/static/SIRH/per1.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
BIN
SIRH/static/SIRH/user.jpg
Normal file
BIN
SIRH/static/SIRH/user.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
157
SIRH/static/templates/SIRH/index.html
Normal file
157
SIRH/static/templates/SIRH/index.html
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PRINCICALA</title>
|
||||||
|
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="{% static 'styles.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'icons.css' %}">
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bg-orange-dark {
|
||||||
|
background: linear-gradient(90deg, #993d00, #b34700); /* oranges plus sombres */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.sidebar .nav-link {
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.sidebar .nav-link:hover {
|
||||||
|
background-color: rgba(255,255,255,0.2);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
max-width: 90px;
|
||||||
|
|
||||||
|
}
|
||||||
|
main {
|
||||||
|
margin-left: 220px;
|
||||||
|
}
|
||||||
|
.stat-card {
|
||||||
|
background: rgb(255, 255, 255);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0px 2px 5px rgba(113, 11, 11, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row flex-nowrap">
|
||||||
|
|
||||||
|
<nav class="col-12 col-md-3 col-lg-2 sidebar p-3">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<img src="{% static 'CERFIF.jpg' %}" alt="Logo" class="logo mb-5">
|
||||||
|
<h4 class="text-white">CERFIG</h4>
|
||||||
|
</div>
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="/" class="nav-link"><i class="bi bi-speedometer2"></i> Tableau de bord</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="#" class="nav-link"><i class=" bi bi-airplane"></i> Demande de congé </a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="#" class="nav-link"><i class="bi bi-people"></i> Gestion des Employés</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="#" class="nav-link"><i class="bi bi-folder"></i> Gestion des Projets</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="#" class="nav-link"><i class="bi bi-airplane"></i> Gestion des Congés</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="#" class="nav-link"><i class="bi bi-graph-up"></i>Rapports et Statistiques</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="#" class="nav-link"><i class="bi bi-gear"></i> Paramètres</a>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#" class="nav-link"><i class="bi bi-box-arrow-right"></i> Déconnexion</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Contenu principal -->
|
||||||
|
<main class="col px-4 py-4">
|
||||||
|
<h1 class="mb-4">Bienvenue sur le tableau de bord</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row g-3 mb-4">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="stat-card">
|
||||||
|
<h5><i class="bi bi-airplane"></i> Congés en attente</h5>
|
||||||
|
|
||||||
|
<p class="display-6 fw-bold"> {{ nombre_conges_attente }}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="stat-card">
|
||||||
|
<h5><i class="bi bi-people"></i> Nombre Employés </h5>
|
||||||
|
<p class="display-6 fw-bold">{{ nombre_employes }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="stat-card">
|
||||||
|
<h5><i class="bi bi-kanban"></i> Projets en cours</h5>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card p-3">
|
||||||
|
<h3>
|
||||||
|
<i class="bi bi-people"></i> La liste des employés en congé</h3>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Employé</th>
|
||||||
|
<th>Type de congé</th>
|
||||||
|
<th>Date de début</th>
|
||||||
|
<th>Date de fin</th>
|
||||||
|
<th>Statut</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
{% for employe in personnes_en_conge %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ employe.nom }} {{ employe.prenom }}</td>
|
||||||
|
<td>{{ employe.type }}</td>
|
||||||
|
<td>{{ employe.date_debut }}</td>
|
||||||
|
<td>{{ employe.date_fin }}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-success">{{ employe.statut }} </span>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" class="text-center">Personne en conge </td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
197
SIRH/static/templates/SIRH/login.html
Normal file
197
SIRH/static/templates/SIRH/login.html
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Connexion SI-RH</title>
|
||||||
|
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="{% static 'styles.css' %}">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
<body class="d-flex justify-content-center align-items-center min-vh-100 ">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bg-orange-dark {
|
||||||
|
background: linear-gradient(90deg, #b35400, #cc6600);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- Formulaire de connexion -->
|
||||||
|
<div class="p-4 shadow-sm rounded bg-white" style="max-width: 400px; margin: auto;">
|
||||||
|
<img src="{% static 'CERFIF.jpg' %}" alt="Logo" class="logo mb-3"
|
||||||
|
style="display: block; margin-left: auto; margin-right: auto; width: 60%; max-width: 500px; height: auto;">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
<!-- Modal message -->
|
||||||
|
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark
|
||||||
|
{% if messages.0.tags == 'error' %}bg-orange-dark text-white
|
||||||
|
{% elif messages.0.tags == 'success' %}bg-orange-dark text-white
|
||||||
|
{% else %}bg-orange-dark text-white{% endif %}">
|
||||||
|
<h5 class="modal-title" id="messageModalLabel">
|
||||||
|
{% if messages.0.tags == 'error' %}Erreur
|
||||||
|
{% elif messages.0.tags == 'success' %}Succès
|
||||||
|
{% else %}Information{% endif %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% for message in messages %}
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
var messageModal = new bootstrap.Modal(document.getElementById('messageModal'));
|
||||||
|
messageModal.show();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
<form method="POST" action="{% url 'login' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="username">Nom d’utilisateur</label>
|
||||||
|
<input type="text" name="username" class="form-control" placeholder="Entrez votre nom d’utilisateur" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 position-relative">
|
||||||
|
<label for="password">Mot de passe</label>
|
||||||
|
<input type="password" name="password" class="form-control" placeholder="Entrez votre mot de passe" required>
|
||||||
|
<i class="bi bi-eye toggle-password" onclick="togglePassword()"
|
||||||
|
style="position:absolute; right:10px; top:38px; cursor:pointer;"></i>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<p class="mt-3 text-center">
|
||||||
|
<a href="#" data-bs-toggle="modal" data-bs-target="#resetPasswordModal">Mot de passe oublié ?</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modale de réinitialisation -->
|
||||||
|
<div class="modal fade" id="resetPasswordModal" tabindex="-1" aria-labelledby="resetPasswordModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content p-4">
|
||||||
|
<h5 class="modal-title mb-3" id="resetPasswordModalLabel">Réinitialisation du mot de passe</h5>
|
||||||
|
<form id="resetPasswordForm">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email">Entrez votre email</label>
|
||||||
|
<input type="email" name="email" class="form-control" placeholder="Email" required>
|
||||||
|
</div>
|
||||||
|
<div id="resetMessage" class="mb-3"></div>
|
||||||
|
<button type="submit" class="btn btn-warning w-100">Envoyer le lien</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if show_politique_modal %}
|
||||||
|
<!-- Modal Politique d'utilisation -->
|
||||||
|
<div class="modal fade" id="politiqueModal" tabindex="-1" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark text-white">
|
||||||
|
<h5 class="modal-title">Politique d'utilisation de SI-RH</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Veuillez lire et accepter notre politique d'utilisation avant d'accéder à votre compte.</p>
|
||||||
|
<h6>Confidentialité des données</h6>
|
||||||
|
<p>Vos informations personnelles et professionnelles sont protégées. Toute divulgation non autorisée est strictement interdite.</p>
|
||||||
|
<h6>Utilisation autorisée</h6>
|
||||||
|
<p>L’application est réservée à un usage professionnel. Toute utilisation à des fins personnelles ou non autorisées est prohibée.</p>
|
||||||
|
<h6>Sécurité des comptes</h6>
|
||||||
|
<p>Ne partagez jamais vos identifiants. Changez votre mot de passe régulièrement et en cas de suspicion d’intrusion.</p>
|
||||||
|
<h6>Responsabilités de l’utilisateur</h6>
|
||||||
|
<p>Vous êtes responsable des actions effectuées via votre compte. Signalez toute anomalie ou problème à l’équipe RH ou à l’administrateur.</p>
|
||||||
|
<h6>Acceptation et conformité</h6>
|
||||||
|
<p>En cliquant sur <strong>“J’accepte”</strong>, vous confirmez avoir lu et accepté cette politique. Le non-respect peut entraîner une suspension ou une révocation de l’accès.</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<form method="POST" action="{% url 'accepter-politique' %}" style="display:inline;">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-success">J'accepte</button>
|
||||||
|
</form>
|
||||||
|
<form method="POST" action="{% url 'refuser-politique' %}" style="display:inline;">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-danger">Refuser</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
var politiqueModal = new bootstrap.Modal(document.getElementById('politiqueModal'));
|
||||||
|
politiqueModal.show();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
var politiqueModal = new bootstrap.Modal(document.getElementById('politiqueModal'));
|
||||||
|
politiqueModal.show();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
var modal = new bootstrap.Modal(document.getElementById('politiqueModal'));
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Scripts -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
|
||||||
|
<script>
|
||||||
|
// Afficher / masquer le mot de passe
|
||||||
|
function togglePassword() {
|
||||||
|
const input = document.querySelector('input[name="password"]');
|
||||||
|
input.type = input.type === 'password' ? 'text' : 'password';
|
||||||
|
}
|
||||||
|
|
||||||
|
// AJAX pour réinitialisation
|
||||||
|
$('#resetPasswordForm').on('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const email = $('input[name="email"]').val();
|
||||||
|
const csrfToken = $('[name=csrfmiddlewaretoken]').val();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "{% url 'password_reset' %}",
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
'email': email,
|
||||||
|
'csrfmiddlewaretoken': csrfToken
|
||||||
|
},
|
||||||
|
success: function(response) {
|
||||||
|
$('#resetMessage').html('<div class="alert alert-success">Email envoyé ! Vérifiez votre boîte de réception.</div>');
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
$('#resetMessage').html('<div class="alert alert-danger">Erreur lors de l’envoi. Vérifiez l’email.</div>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
150
SIRH/static/templates/SIRH/menu_principal.html
Normal file
150
SIRH/static/templates/SIRH/menu_principal.html
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
{% load static %}
|
||||||
|
{% load roles %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>PRINCICALA</title>
|
||||||
|
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="{% static 'styles.css' %}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- ===================== -->
|
||||||
|
<!-- SIDEBAR RESPONSIVE -->
|
||||||
|
<!-- ===================== -->
|
||||||
|
<nav class="sidebar bg-orange-dark text-white d-none d-md-block col-md-3 col-lg-2 p-3"
|
||||||
|
style="background: linear-gradient(90deg, #993d00, #b34700); position: fixed; height: 100vh; overflow-y: auto;">
|
||||||
|
|
||||||
|
<!-- Profil utilisateur -->
|
||||||
|
<div class="text-center mb-4 d-flex flex-column align-items-center" style="margin-top:20px;">
|
||||||
|
<h6 class="fw-bold text-white mb-2">{{ user.first_name }} {{ user.last_name }}</h6>
|
||||||
|
<a href="{% url 'mon_profil' %}">
|
||||||
|
{% if user.photo %}
|
||||||
|
<img src="{{ user.photo.url }}" alt="Profil"
|
||||||
|
class="rounded-circle img-profil mx-auto"
|
||||||
|
style="width: 80px; height: 80px; object-fit: cover;">
|
||||||
|
{% else %}
|
||||||
|
<div class="rounded-circle bg-secondary d-flex justify-content-center align-items-center"
|
||||||
|
style="width: 80px; height: 80px;">
|
||||||
|
<i class="bi bi-person-circle text-white" style="font-size: 2rem;"></i>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Liens du menu -->
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
{% if user|has_role:"Employe" or user|has_role:"Administrateur" %}
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'mon_profil' %}" class="nav-link text-white"><i class="bi bi-person-circle"></i> Mon profil</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'activites-projet' %}" class="nav-link text-white"><i class="bi bi-list-task"></i> Suivi des Activités</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'employe-conge' %}" class="nav-link text-white"><i class="bi bi-airplane"></i> Mes Congés</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'mes_formations' %}" class="nav-link text-white"><i class="bi bi-journal-text"></i> Mes certificats</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'salle' %}" class="nav-link text-white"><i class="bi bi-calendar-check"></i> Réserver une salle</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user|has_role:"RH" or user|has_role:"Directeur" or user|has_role:"Assistante" %}
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'employe-index' %}" class="nav-link text-white"><i class="bi bi-speedometer2"></i> Tableau de bord</a>
|
||||||
|
</li>
|
||||||
|
{% if user|has_role:"Assistante" or user|has_role:"RH" %}
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'activites-projet' %}" class="nav-link text-white"><i class="bi bi-list-task"></i> Suivi des Activités</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% if user|has_role:"Directeur" %}
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'directeur' %}" class="nav-link text-white"><i class="bi bi-graph-up"></i> Suivi des Projets</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'projet-index' %}" class="nav-link text-white"><i class="bi bi-folder"></i> Gestion des Projets</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'conge' %}" class="nav-link text-white"><i class="bi bi-airplane"></i> Congés</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'mes_formations' %}" class="nav-link text-white"><i class="bi bi-journal-text"></i> Mes certificats</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'salle' %}" class="nav-link text-white"><i class="bi bi-calendar-check"></i> Réserver une salle</a>
|
||||||
|
</li>
|
||||||
|
{% if user|has_role:"RH" or user|has_role:"Directeur" %}
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'rapport-rh' %}" class="nav-link text-white"><i class="bi bi-graph-up"></i> Rapports et Statistiques</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'mon_profil' %}" class="nav-link text-white"><i class="bi bi-person-circle"></i> Mon profil</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user|has_role:"Administrateur" %}
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'gestion-utilisateurs' %}" class="nav-link text-white"><i class="bi bi-person-gear"></i> Gestion des Utilisateurs</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'parametres-rh' %}" class="nav-link text-white"><i class="bi bi-gear"></i> Paramètres</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'deconnexion' %}" class="nav-link text-white"><i class="bi bi-box-arrow-right"></i> Déconnexion</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- ===================== -->
|
||||||
|
<!-- MENU MOBILE (offcanvas) -->
|
||||||
|
<!-- ===================== -->
|
||||||
|
<!-- Bouton burger (visible uniquement sur téléphone) -->
|
||||||
|
<nav class="navbar navbar-dark bg-orange-dark d-md-none p-2" style="background: linear-gradient(90deg, #993d00, #b34700);">
|
||||||
|
<button class="btn btn-light" type="button" data-bs-toggle="offcanvas" data-bs-target="#menuMobile">
|
||||||
|
<i class="bi bi-list"></i> Menu
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Menu Offcanvas mobile -->
|
||||||
|
<div class="offcanvas offcanvas-start" tabindex="-1" id="menuMobile" style="background: linear-gradient(90deg, #993d00, #b34700); color: white;">
|
||||||
|
<div class="offcanvas-header">
|
||||||
|
<h5 class="offcanvas-title">{{ user.first_name }} {{ user.last_name }}</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas"></button>
|
||||||
|
</div>
|
||||||
|
<div class="offcanvas-body">
|
||||||
|
<!-- Tu peux copier ici le même <ul> du menu principal -->
|
||||||
|
<ul class="nav flex-column">
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'mon_profil' %}" class="nav-link text-white"><i class="bi bi-person-circle"></i> Mon profil</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'activites-projet' %}" class="nav-link text-white"><i class="bi bi-list-task"></i> Activités</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item mb-2">
|
||||||
|
<a href="{% url 'deconnexion' %}" class="nav-link text-white"><i class="bi bi-box-arrow-right"></i> Déconnexion</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
10
SIRH/static/templates/SIRH/message.html
Normal file
10
SIRH/static/templates/SIRH/message.html
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="alert alert-{{ message.tags }} d-flex align-items-center" role="alert">
|
||||||
|
{% if message.tags == 'success' %}
|
||||||
|
<i class="bi bi-check-circle-fill me-2"></i>
|
||||||
|
{% elif message.tags == 'warning' %}
|
||||||
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||||
|
{% elif message.tags == 'error' %}
|
||||||
|
<i class="bi bi-x-circle-fill me-2"></i>
|
||||||
|
{% endif %}
|
||||||
|
{{ message }}
|
||||||
|
</div>
|
||||||
384
SIRH/static/templates/SIRH/monprofil.html
Normal file
384
SIRH/static/templates/SIRH/monprofil.html
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Profil utilisateur</title>
|
||||||
|
|
||||||
|
<!-- CSS -->
|
||||||
|
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="{% static 'styles.css' %}">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bg-orange-dark {
|
||||||
|
background: linear-gradient(90deg, #993d00, #b34700); /* oranges plus sombres */
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.card-body p { margin-bottom: 0.5rem; }
|
||||||
|
|
||||||
|
.main {
|
||||||
|
margin-left: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row flex-nowrap">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
{% include 'menu_principal.html' %}
|
||||||
|
|
||||||
|
<!-- Main -->
|
||||||
|
<main class="col px-2 py-4" style="margin-left: 280px;">
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
<!-- Modal Message Django -->
|
||||||
|
<div class="modal fade" id="messageModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark text-white">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
{% if messages.0.tags == 'error' %}
|
||||||
|
Erreur
|
||||||
|
{% elif messages.0.tags == 'success' %}
|
||||||
|
Succès
|
||||||
|
{% else %}
|
||||||
|
Information
|
||||||
|
{% endif %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% for message in messages %}
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Icône Notification -->
|
||||||
|
<div class="notification-bell position-fixed top-0 end-0 m-3" data-bs-toggle="modal" data-bs-target="#modalNotifications" style="z-index:1050; cursor:pointer;">
|
||||||
|
<i class="bi bi-bell-fill fs-4"></i>
|
||||||
|
<span class="badge bg-danger">{{ notifications_contrats|length }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Notifications Contrats -->
|
||||||
|
<div class="modal fade" id="modalNotifications" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark text-white">
|
||||||
|
<h5 class="modal-title"><i class="bi bi-bell me-2"></i>Contrats proches de la fin</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% if notifications_contrats %}
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for notif in notifications_contrats %}
|
||||||
|
<h5>Votre Contrat <strong>{{ notif.numero }}</strong> signé le <strong>{{ notif.date }}</strong>
|
||||||
|
se termine dans
|
||||||
|
<span class="badge bg-danger rounded-pill">{{ notif.jours_restants }} jours</span>
|
||||||
|
</h5>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted">Aucun contrat proche de la fin.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Profil -->
|
||||||
|
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-12 col-md-10 col-lg-8">
|
||||||
|
<div class="card border-0 shadow-sm">
|
||||||
|
|
||||||
|
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||||
|
<h4 class="mb-0 fw-bold">
|
||||||
|
<i class="bi bi-person-circle me-2"></i> Mon Profil
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body p-3">
|
||||||
|
<!-- Infos personnelles -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p><strong>Nom :</strong> {{ employe.last_name|default:"Non renseigné" }}</p>
|
||||||
|
<p><strong>Prénom :</strong> {{ employe.first_name|default:"Non renseigné" }}</p>
|
||||||
|
<p><strong>Matricule :</strong> {{ employe.matricule|default:"Non renseigné" }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p><strong>Département :</strong> {{ employe.departement|default:"Non renseigné" }}</p>
|
||||||
|
<p><strong>Email :</strong> {{ employe.email|default:"Non renseigné" }}</p>
|
||||||
|
<p><strong>Téléphone :</strong> {{ employe.telephone|default:"Non renseigné" }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contrats -->
|
||||||
|
<h5 class="mt-3">Contrats</h5>
|
||||||
|
{% for contrat in contrats %}
|
||||||
|
<div class="mb-3 p-3 border rounded">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p><strong>Numéro :</strong> {{ contrat.numero_contrat }}</p>
|
||||||
|
<p><strong>Type :</strong> {{ contrat.type_contrat }}</p>
|
||||||
|
<p><strong>Date début :</strong> {{ contrat.date_debut|date:"d/m/Y" }}</p>
|
||||||
|
<p><strong>Date fin :</strong> {{ contrat.date_fin|date:"d/m/Y"|default:"Non précisée" }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<p><strong>Salaire mensuel :</strong>
|
||||||
|
{% if contrat.salaire_mensuel %}
|
||||||
|
{{ contrat.salaire_mensuel }} GNF
|
||||||
|
{% else %}
|
||||||
|
Non précisé
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<p><strong>Statut :</strong>
|
||||||
|
<span class="badge
|
||||||
|
{% if contrat.statut_auto == 'Actif' %}bg-success
|
||||||
|
{% elif contrat.statut_auto == 'Terminé' %}bg-danger
|
||||||
|
{% elif contrat.statut_auto == 'Suspendu' %}bg-warning text-dark
|
||||||
|
{% else %}bg-secondary{% endif %}">
|
||||||
|
{{ contrat.statut_auto }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p><strong>Solde congé :</strong> {{ contrat.solde_conge|default:"Non précisé" }} jours</p>
|
||||||
|
<p><strong>Fichier :</strong>
|
||||||
|
{% if contrat.fichier_contrat %}
|
||||||
|
<a href="{{ contrat.fichier_contrat.url }}" target="_blank">
|
||||||
|
<i class="bi bi-file-earmark-pdf me-1"></i> Télécharger
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted">Aucun fichier</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p class="text-danger">Aucun contrat trouvé.</p>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- Projets -->
|
||||||
|
<h5 class="mt-3">Projets</h5>
|
||||||
|
<ul class="list-group mb-3">
|
||||||
|
{% for a in affectations %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
{{ a.projet.nom_projet }} - {{ a.temps_affectation }}%
|
||||||
|
{% if a.projet.statut %}
|
||||||
|
- <span class="badge bg-info text-dark">{{ a.projet.statut }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<button type="button" class="btn bg-orange-dark btn-sm text-white"
|
||||||
|
data-bs-toggle="modal" data-bs-target="#modalFicheProjet{{ a.projet.id }}">
|
||||||
|
<i class="bi bi-info-circle"></i> Détails
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="list-group-item text-muted text-center">Aucun projet assigné.</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer bg-white d-flex justify-content-end">
|
||||||
|
<button type="button" class="btn btn-outline-secondary me-2" onclick="window.history.back()">Fermer</button>
|
||||||
|
<a href="{% url 'profil' %}" class="btn bg-orange-dark text-white">
|
||||||
|
<i class="bi bi-pencil-square me-1"></i> Renseigner les autres informations
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for a in affectations %}
|
||||||
|
<!-- Modal Détails Projet -->
|
||||||
|
<div class="modal fade" id="modalFicheProjet{{ a.projet.id }}" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark text-white">
|
||||||
|
<h5 class="modal-title">
|
||||||
|
<i class="bi bi-info-circle me-2"></i>Détails du projet {{ a.projet.nom_projet }}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<p><strong>Nom :</strong> {{ a.projet.nom_projet }}</p>
|
||||||
|
<p><strong>Numéro convention :</strong> {{ a.projet.numero_convention }}</p>
|
||||||
|
<p><strong>Dates :</strong> {{ a.projet.date_debut|date:"d/m/Y" }} → {{ a.projet.date_fin|date:"d/m/Y" }}</p>
|
||||||
|
<p><strong>Type :</strong> {{ a.projet.get_type_projet_display }}</p>
|
||||||
|
<p><strong>Domaine :</strong> {{ a.projet.get_domaine_recherche_display }}</p>
|
||||||
|
<p><strong>Budget :</strong> {{ a.projet.budget }} GNF</p>
|
||||||
|
<p><strong>Budget RH :</strong> {{ a.projet.budget_RH }} GNF</p>
|
||||||
|
<p><strong>Statut :</strong> <span class="badge bg-info">{{ a.projet.statut }}</span></p>
|
||||||
|
<p><strong>Description :</strong> {{ a.projet.description }}</p>
|
||||||
|
|
||||||
|
<h6 class="mt-4">Employés affectés :</h6>
|
||||||
|
<ul class="list-group mb-3">
|
||||||
|
{% for aff in a.projet.affectations.all %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ aff.employe }} <span class="badge bg-orange-dark">{{ aff.temps_affectation }}%</span>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="list-group-item text-muted">Aucun employé affecté</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h6>Bailleurs :</h6>
|
||||||
|
<ul class="list-group mb-3">
|
||||||
|
{% for f in a.projet.financements.all %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ f.bailleur.nom }} <span class="badge bg-success">{{ f.pourcentage }}%</span>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="list-group-item text-muted">Aucun bailleur enregistré</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h6 class="mt-4">Documents du projet :</h6>
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for doc in a.projet.documents.all %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-start">
|
||||||
|
<div>
|
||||||
|
<strong>{{ doc.get_nom_document_display }}</strong><br>
|
||||||
|
pour le projet {{ a.projet.nom_projet }}<br>
|
||||||
|
{% if doc.description %}
|
||||||
|
<small class="text-muted">{{ doc.description }}</small><br>
|
||||||
|
{% endif %}
|
||||||
|
<small class="text-muted">
|
||||||
|
{% if doc.numero %}
|
||||||
|
{{ doc.numero }}
|
||||||
|
{% endif %}
|
||||||
|
</small>
|
||||||
|
<br> <small></small>
|
||||||
|
document ajouter le {{ doc.date_ajout|date:"d/m/Y" }}</small>
|
||||||
|
{% if doc.date_validite %}
|
||||||
|
<br><small class="text-muted"> et Valide jusqu'au {{ doc.date_validite|date:"d/m/Y" }}</small>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="list-group-item text-muted">Aucun document disponible</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer bg-light">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="bi bi-x-circle me-1"></i> Fermer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- JS -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
// Ferme les modales ouvertes avant d'en ouvrir une autre
|
||||||
|
const modals = document.querySelectorAll('.modal');
|
||||||
|
modals.forEach(modal => {
|
||||||
|
modal.addEventListener('show.bs.modal', function () {
|
||||||
|
document.querySelectorAll('.modal.show').forEach(openModal => {
|
||||||
|
if (openModal !== modal) {
|
||||||
|
const instance = bootstrap.Modal.getInstance(openModal);
|
||||||
|
if (instance) instance.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Répare le bug d'écran figé après fermeture
|
||||||
|
document.addEventListener('hidden.bs.modal', function () {
|
||||||
|
if (!document.querySelector('.modal.show')) {
|
||||||
|
document.body.classList.remove('modal-open');
|
||||||
|
document.body.style.removeProperty('padding-right');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const forms = document.querySelectorAll('form[action*="ajouter-document"]');
|
||||||
|
forms.forEach(form => {
|
||||||
|
form.addEventListener('submit', function (e) {
|
||||||
|
const btn = form.querySelector('button[type="submit"]');
|
||||||
|
const modalEl = form.closest('.modal');
|
||||||
|
if (btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.innerHTML = '<i class="bi bi-hourglass-split"></i> Enregistrement...';
|
||||||
|
}
|
||||||
|
if (modalEl) {
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(modalEl);
|
||||||
|
if (modalInstance) modalInstance.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const messageModalEl = document.getElementById('messageModal');
|
||||||
|
if (messageModalEl) {
|
||||||
|
const messageModal = new bootstrap.Modal(messageModalEl);
|
||||||
|
messageModal.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
223
SIRH/static/templates/SIRH/parametre.html
Normal file
223
SIRH/static/templates/SIRH/parametre.html
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Paramètres RH</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="{% static 'styles.css' %}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.bg-orange-dark {
|
||||||
|
background: linear-gradient(90deg, #993d00, #b34700);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row flex-nowrap">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
{% include 'menu_principal.html' %}
|
||||||
|
|
||||||
|
<!-- Main -->
|
||||||
|
<main class="col px-2 py-4" style="margin-left: 280px;">
|
||||||
|
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
<!-- Modal message -->
|
||||||
|
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark
|
||||||
|
{% if messages.0.tags == 'error' %}bg-orange-dark text-white
|
||||||
|
{% elif messages.0.tags == 'success' %}bg-orange-dark text-white
|
||||||
|
{% else %}bg-orange-dark text-white{% endif %}">
|
||||||
|
<h5 class="modal-title" id="messageModalLabel">
|
||||||
|
{% if messages.0.tags == 'error' %}Erreur
|
||||||
|
{% elif messages.0.tags == 'success' %}Succès
|
||||||
|
{% else %}Information{% endif %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% for message in messages %}
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
var messageModal = new bootstrap.Modal(document.getElementById('messageModal'));
|
||||||
|
messageModal.show();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<h2 class="mb-4"><i class="bi bi-gear-fill"></i> Paramètres Administration</h2>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<!-- Colonne Départements -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h5><i class="bi bi-building"></i> Départements</h5>
|
||||||
|
<button class="btn bg-orange-dark btn-sm" data-bs-toggle="modal" data-bs-target="#modalAjouterDepartement">
|
||||||
|
<i class="bi bi-plus-circle"></i> Ajouter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Ajout -->
|
||||||
|
<div class="modal fade" id="modalAjouterDepartement" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="ajouter_departement" value="1">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark text-white">
|
||||||
|
<h5 class="modal-title">Ajouter un département</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{{ form_departement.as_p }}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn bg-orange-dark">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Liste des départements -->
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for d in departements %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ d.nom }}
|
||||||
|
<div>
|
||||||
|
<!-- Bouton détails -->
|
||||||
|
<button class="btn btn-sm btn-outline-info" data-bs-toggle="modal" data-bs-target="#modalDetailDepartement{{ d.id }}">
|
||||||
|
<i class="bi bi-eye"></i> Détails
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Bouton modification -->
|
||||||
|
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#modalModifierDepartement{{ d.id }}">
|
||||||
|
<i class="bi bi-pencil-square"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
<form method="POST" style="display:inline;">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="supprimer_departement" value="1">
|
||||||
|
<input type="hidden" name="departement_id" value="{{ d.id }}">
|
||||||
|
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('Voulez-vous vraiment supprimer ce département ?');">
|
||||||
|
<i class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</li><!-- Modal détails -->
|
||||||
|
<div class="modal fade" id="modalDetailDepartement{{ d.id }}" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark text-white">
|
||||||
|
<h5 class="modal-title">Détails du département</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p><strong>Nom :</strong> {{ d.nom }}</p>
|
||||||
|
<p><strong>Chef :</strong> {% if d.chef %}{{ d.chef.first_name }} {{ d.chef.last_name }}{% else %}Non défini{% endif %}</p>
|
||||||
|
|
||||||
|
<!-- Liste des employés du département -->
|
||||||
|
{% for emp in d.employe_set.all %}
|
||||||
|
<p>- {{ emp.username }}</p>
|
||||||
|
{% empty %}
|
||||||
|
<p>Aucun employé associé</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal fade" id="modalModifierDepartement{{ d.id }}" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="modifier_departement" value="1">
|
||||||
|
<input type="hidden" name="departement_id" value="{{ d.id }}">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark text-white">
|
||||||
|
<h5 class="modal-title">Modifier le département</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Nom du département -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="nom_{{ d.id }}" class="form-label">Nom du département</label>
|
||||||
|
<input type="text" class="form-control" id="nom_{{ d.id }}" name="nom" value="{{ d.nom }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sélection du chef -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="chef_{{ d.id }}" class="form-label">Chef du département</label>
|
||||||
|
<select class="form-select" id="chef_{{ d.id }}" name="chef">
|
||||||
|
<option value="">-- Aucun chef --</option>
|
||||||
|
{% for emp in employes %}
|
||||||
|
<option value="{{ emp.id }}" {% if d.chef and emp.id == d.chef.id %}selected{% endif %}>
|
||||||
|
{{ emp.first_name }} {{ emp.last_name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn bg-orange-dark">Modifier</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Colonne Groupes -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<h5><i class="bi bi-person-badge-fill"></i> Groupes et rôles</h5>
|
||||||
|
<button class="btn bg-orange-dark btn-sm">Ajouter</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for g in groupes %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ g.name }}
|
||||||
|
<a href="{% url 'admin:auth_group_change' g.id %}" class="btn btn-sm btn-outline-secondary">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
205
SIRH/static/templates/SIRH/rapport.html
Normal file
205
SIRH/static/templates/SIRH/rapport.html
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Tableau de Bord RH</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h1 { font-weight: 700; }
|
||||||
|
.card { border-radius: 15px; }
|
||||||
|
.chart-container { position: relative; height: 350px; }
|
||||||
|
table { min-width: 100%; }
|
||||||
|
|
||||||
|
/* Couleurs oranges */
|
||||||
|
.bg-orange-dark { background: linear-gradient(90deg, #b35400, #cc6600); color: white; }
|
||||||
|
.bg-orange { background: #ff9f1c; color: white; }
|
||||||
|
.bg-orange-light { background: #ffc107; color: #212529; }
|
||||||
|
|
||||||
|
/* Badges */
|
||||||
|
.badge-warning { background-color: #ffc107; color: #212529; }
|
||||||
|
.badge-success { background-color: #28a745; color: #fff; }
|
||||||
|
.badge-primary { background-color: #007bff; color: #fff; }
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body * { visibility: hidden; }
|
||||||
|
.printable-table, .printable-table * { visibility: visible; }
|
||||||
|
.printable-table { position: absolute; top: 0; left: 0; width: 100%; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<div class="row flex-nowrap">
|
||||||
|
{% include 'menu_principal.html' %}
|
||||||
|
|
||||||
|
<main class="col px-2 py-4" style="margin-left: 280px;">
|
||||||
|
<h1 class="mb-5 text-center"><i class="bi bi-bar-chart-line-fill me-2"></i>Tableau de Bord RH</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Filtres -->
|
||||||
|
<form method="GET" class="row g-3 mb-5">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<select name="mois" class="form-select">
|
||||||
|
<option value="">Tous les mois</option>
|
||||||
|
{% for num, nom in mois_liste %}
|
||||||
|
<option value="{{ num }}" {% if mois_selectionne == num %}selected{% endif %}>{{ nom }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<select name="departement" class="form-select">
|
||||||
|
<option value="">Tous les départements</option>
|
||||||
|
{% for d in departements %}
|
||||||
|
<option value="{{ d.id }}" {% if departement_selectionne == d.id %}selected{% endif %}>{{ d.nom }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<button type="submit" class="btn btn-outline-primary w-100"><i class="bi bi-search"></i> Filtrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Cartes statistiques -->
|
||||||
|
<div class="row g-4 mb-5">
|
||||||
|
<div class="col-md-3"><div class="card text-center"><div class="card-body"><h6 class="fw-bold"><i class="bi bi-people me-2"></i>Total Employés</h6><h3 class="fw-bold">{{ total_employes }}</h3></div></div></div>
|
||||||
|
<div class="col-md-3"><div class="card text-center"><div class="card-body"><h6 class="fw-bold"><i class="bi bi-gender-male me-2"></i>Hommes</h6><h3 class="fw-bold">{{ hommes }}</h3></div></div></div>
|
||||||
|
<div class="col-md-3"><div class="card text-center"><div class="card-body"><h6 class="fw-bold"><i class="bi bi-gender-female me-2"></i>Femmes</h6><h3 class="fw-bold">{{ femmes }}</h3></div></div></div>
|
||||||
|
<div class="col-md-3"><div class="card text-center"><div class="card-body"><h6 class="fw-bold"><i class="bi bi-clock-history me-2"></i>Contrats expirants(2mois)</h6><h3 class="fw-bold">{{ expirants }}</h3></div></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4 mb-5">
|
||||||
|
<div class="col-md-3"><div class="card text-center"><div class="card-header fw-bold"><i class="bi bi-airplane me-2"></i>Congés en attente</div><div class="card-body"><h5 class="display-6 fw-bold">{{ conges_attente }}</h5></div></div></div>
|
||||||
|
<div class="col-md-3"><div class="card text-center"><div class="card-body"><h6 class="fw-bold"><i class="bi bi-kanban me-2"></i>Projets</h6><h6>(actifs / total)</h6><h3 class="fw-bold">{{ nb_projets_actifs }} / {{ nb_projets }}</h3></div></div></div>
|
||||||
|
<div class="col-md-3"><div class="card text-center"><div class="card-header fw-bold"><i class="bi bi-clock-history me-2"></i>Âge moyen</div><div class="card-body"><h5 class="display-6 fw-bold">{{ age_moyen }} ans</h5></div></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Graphiques -->
|
||||||
|
<div class="row g-4 mb-5">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="card shadow"><div class="card-header bg-orange-dark text-white"><i class="bi bi-pie-chart-fill me-2"></i> Répartition par Département</div>
|
||||||
|
<div class="card-body chart-container"><canvas id="chartDepartement"></canvas></div></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="card shadow"><div class="card-header bg-orange-dark text-white"><i class="bi bi-pie-chart-fill me-2"></i> Répartition par genre</div>
|
||||||
|
<div class="card-body chart-container"><canvas id="chartSexe"></canvas></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4 mb-6">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="card shadow"><div class="card-header bg-orange-dark text-white"><i class="bi bi-bar-chart-fill me-2"></i> Projets (En cours / Terminés)</div>
|
||||||
|
<div class="card-body chart-container"><canvas id="chartProjets"></canvas></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="card shadow"><div class="card-header bg-orange-dark text-white"><i class="bi bi-bar-chart-fill me-2"></i> Projets par domaine de recherche</div>
|
||||||
|
<div class="card-body chart-container"><canvas id="chartDomaine"></canvas></div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const couleursOrange = ['#b35400', '#ff9f1c', '#ffc107', '#ffb84d', '#ffcc80'];
|
||||||
|
const departementLabels = {{ departement_labels|safe }};
|
||||||
|
const departementCounts = {{ departement_counts|safe }};
|
||||||
|
const sexeLabels = {{ sexe_labels|safe }};
|
||||||
|
const sexeCounts = {{ sexe_counts|safe }};
|
||||||
|
const projetLabels = {{ projet_labels|safe }};
|
||||||
|
const projetCounts = {{ projet_counts|safe }};
|
||||||
|
const ticketsLabels = ["Traités", "Non traités"];
|
||||||
|
const ticketsCounts = [{{ tickets_traite }}, {{ tickets_non_traite }}];
|
||||||
|
const domaineLabels = {{ domaine_labels|safe }};
|
||||||
|
const domaineCounts = {{ domaine_counts|safe }};
|
||||||
|
|
||||||
|
new Chart(document.getElementById("chartDepartement"), {
|
||||||
|
type: 'pie',
|
||||||
|
plugins: [ChartDataLabels],
|
||||||
|
data: {
|
||||||
|
labels: departementLabels,
|
||||||
|
datasets: [{ data: departementCounts, backgroundColor: couleursOrange }]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
legend: { position: 'bottom' },
|
||||||
|
datalabels: { color: '#fff', font: { weight: 'bold', size: 14 }, formatter: v => v }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(document.getElementById("chartSexe"), {
|
||||||
|
type: 'doughnut',
|
||||||
|
plugins: [ChartDataLabels],
|
||||||
|
data: {
|
||||||
|
labels: sexeLabels,
|
||||||
|
datasets: [{ data: sexeCounts, backgroundColor: couleursOrange }]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
plugins: {
|
||||||
|
legend: { position: 'bottom' },
|
||||||
|
datalabels: { color: '#fff', font: { weight: 'bold', size: 14 }, formatter: v => v }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(document.getElementById("chartProjets"), {
|
||||||
|
type: 'bar',
|
||||||
|
plugins: [ChartDataLabels],
|
||||||
|
data: {
|
||||||
|
labels: projetLabels,
|
||||||
|
datasets: [{ label:'Nombre de projets', data: projetCounts, backgroundColor: couleursOrange }]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
datalabels: { anchor:'end', align:'top', color:'#000', font:{ weight:'bold', size:16 }, formatter:v => v }
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: { beginAtZero:true, ticks:{ stepSize:1, precision:0 }, title:{ display:true, text:'Nombre de projets' } },
|
||||||
|
x: { title:{ display:true, text:'' } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(document.getElementById("chartDomaine"), {
|
||||||
|
type: 'bar',
|
||||||
|
plugins: [ChartDataLabels],
|
||||||
|
data: {
|
||||||
|
labels: domaineLabels,
|
||||||
|
datasets: [{ label:'Nombre de projets', data: domaineCounts, backgroundColor: couleursOrange }]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
datalabels: { anchor:'end', align:'top', color:'#000', font:{ weight:'bold', size:14 }, formatter:v => v }
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: { beginAtZero:true, title:{ display:true, text:'Nombre de projets' }, ticks:{ precision:0, stepSize:1 } },
|
||||||
|
x: {
|
||||||
|
ticks:{ autoSkip:false, maxRotation:100, minRotation:30, callback: function(value){ return this.getLabelForValue(value); } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btnVoirRapport').addEventListener('click', function() {
|
||||||
|
const projetId = document.getElementById('projetId').value;
|
||||||
|
if(!projetId) return alert("Veuillez sélectionner un projet.");
|
||||||
|
fetch(`/rapports/projet/${projetId}/`)
|
||||||
|
.then(resp => { if(!resp.ok) throw new Error("Projet non trouvé"); return resp.text(); })
|
||||||
|
.then(html => { document.getElementById('contenuRapportProjet').innerHTML = html; })
|
||||||
|
.catch(err => { document.getElementById('contenuRapportProjet').innerHTML = "<p class='text-danger'>Erreur lors du chargement.</p>"; console.error(err); });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
0
SIRH/static/templates/SIRH/registration
Normal file
0
SIRH/static/templates/SIRH/registration
Normal file
33
SIRH/static/templates/SIRH/rest.html
Normal file
33
SIRH/static/templates/SIRH/rest.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Mot de passe oublié</title>
|
||||||
|
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'styles.css' %}">
|
||||||
|
</head>
|
||||||
|
<body class="container mt-5">
|
||||||
|
<div class="p-4 shadow-sm rounded bg-white" style="max-width: 400px; margin:auto;">
|
||||||
|
<h2 class="mb-4 text-center" style="color:rgba(255, 157, 0, 0.895);">🔑 Réinitialiser le mot de passe</h2>
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-info">{{ message }}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="email">Adresse e-mail</label>
|
||||||
|
<input type="email" name="email" class="form-control" placeholder="Entrez votre email" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Envoyer le lien</button>
|
||||||
|
</form>
|
||||||
|
<p class="mt-3 text-center">
|
||||||
|
<a href="{% url 'login' %}">Retour à la connexion</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
620
SIRH/static/templates/SIRH/tiket.html
Normal file
620
SIRH/static/templates/SIRH/tiket.html
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
{% load static %}
|
||||||
|
{% load roles %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Mes demandes</title>
|
||||||
|
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="{% static 'styles.css' %}">
|
||||||
|
<style>
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.2); }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-orange-dark {
|
||||||
|
background: linear-gradient(90deg, #b35400, #cc6600);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row flex-nowrap">
|
||||||
|
<nav class="col-12 col-md-3 col-lg-2 sidebar p-3">
|
||||||
|
{% include 'menu_principal.html' %}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="col px-4 py-4">
|
||||||
|
|
||||||
|
<!-- Boutons Notifications -->
|
||||||
|
{% if notifications %}
|
||||||
|
<div class="d-flex justify-content-end mb-3">
|
||||||
|
<button type="button" class="btn position-relative" data-bs-toggle="modal" data-bs-target="#modalNotifications">
|
||||||
|
<i class="bi bi-bell-fill"></i> Notifications
|
||||||
|
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
||||||
|
{{ notifications|length }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Bouton Notifications -->
|
||||||
|
{% if notifications_employe %}
|
||||||
|
<div class="d-flex justify-content-end mb-3">
|
||||||
|
<button type="button" class="btn position-relative" data-bs-toggle="modal" data-bs-target="#modalNotifications">
|
||||||
|
<i class="bi bi-bell-fill"></i> Notifications
|
||||||
|
<span id="badgeNotifications" class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
||||||
|
{{ notifications_employe|length }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Modal Notifications -->
|
||||||
|
<div class="modal fade" id="modalNotifications" tabindex="-1" aria-labelledby="modalNotificationsLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="modalNotificationsLabel">Mes Notifications</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% if notifications_employe %}
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for notif in notifications_employe %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ notif.message|default:"Nouvelle notification" }}
|
||||||
|
<small class="text-muted">{{ notif.date_created|date:"d/m/Y H:i" }}</small>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted">Aucune notification pour le moment.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
var modal = document.getElementById('modalNotifications');
|
||||||
|
|
||||||
|
modal.addEventListener('show.bs.modal', function () {
|
||||||
|
fetch("{% url 'notifications-lues' %}", {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': '{{ csrf_token }}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if(data.status === "ok"){
|
||||||
|
document.getElementById('badgeNotifications').style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<h1 class="mb-4">La liste des demandes de congés</h1>
|
||||||
|
<!-- Cartes Statistiques -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card text-white ">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title"><i class="bi bi-list-check me-2"></i> Total Demandes</h6>
|
||||||
|
<p class="card-text fs-4">{{ total }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card text-white ">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title"><i class="bi bi-check-circle me-2"></i> Validés</h6>
|
||||||
|
<p class="card-text fs-4">{{ valides }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card text-white ">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title"><i class="bi bi-x-circle me-2"></i> Refusés</h6>
|
||||||
|
<p class="card-text fs-4">{{ refuses }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="card text-dark ">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="card-title"><i class="bi bi-hourglass-split me-2"></i> En attente</h6>
|
||||||
|
<p class="card-text fs-4">{{ attentes }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
<!-- Modal message -->
|
||||||
|
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark
|
||||||
|
{% if messages.0.tags == 'error' %}bg-danger text-white
|
||||||
|
{% elif messages.0.tags == 'success' %}bg-success text-white
|
||||||
|
{% else %}bg-info text-white{% endif %}">
|
||||||
|
<h5 class="modal-title" id="messageModalLabel">
|
||||||
|
{% if messages.0.tags == 'error' %}Erreur
|
||||||
|
{% elif messages.0.tags == 'success' %}Succès
|
||||||
|
{% else %}Information{% endif %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% for message in messages %}
|
||||||
|
<p>{{ message }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
var messageModal = new bootstrap.Modal(document.getElementById('messageModal'));
|
||||||
|
messageModal.show();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Bouton Nouvelle Demande -->
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h2><i class="bi bi-people"></i> Liste</h2>
|
||||||
|
<button type="button" class="btn btn-primary mb-3" data-bs-toggle="modal" data-bs-target="#modalDemandeConge">
|
||||||
|
<i class="bi bi-calendar-plus"></i> Nouvelle demande de congé
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Nouvelle Demande -->
|
||||||
|
<div class="modal fade" id="modalDemandeConge" tabindex="-1" aria-labelledby="modalDemandeCongeLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title" id="modalDemandeCongeLabel">
|
||||||
|
<i class="bi bi-calendar-plus me-2"></i> Nouvelle demande de congé
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<fieldset class="border p-3 rounded">
|
||||||
|
<legend class="fw-bold">Informations du congé
|
||||||
|
Il vous reste {{ employe.solde_conge }} jours de congé Annuel
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
{{ form.type.label_tag }} {{ form.type }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
{{ form.date_debut.label_tag }} {{ form.date_debut }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
{{ form.date_fin.label_tag }} {{ form.date_fin }}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
{{ form.nombre_jours.label_tag }} {{ form.nombre_jours }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
<i class="bi bi-send"></i> Envoyer
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="bi bi-x-circle"></i> Annuler
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tableau des demandes -->
|
||||||
|
<table class="table table-striped mt-2">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Employe</th>
|
||||||
|
<th>Date demande</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Date début</th>
|
||||||
|
<th>Date fin</th>
|
||||||
|
<th>Nombre jours</th>
|
||||||
|
<th>Jours restant</th>
|
||||||
|
<th>Statut</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for conge in conges %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ conge.employe.first_name }} {{ conge.employe.last_name }}</td>
|
||||||
|
|
||||||
|
<td>{{ conge.date_demande|date:"d/m/Y" }}</td>
|
||||||
|
<td>{{ conge.type }}</td>
|
||||||
|
<td>{{ conge.date_debut|date:"d/m/Y"}}</td>
|
||||||
|
<td>{{ conge.date_fin|date:"d/m/Y" }}</td>
|
||||||
|
<td>{{ conge.nombre_jours }}</td>
|
||||||
|
|
||||||
|
<td>{{ conge.employe.solde_conge }} </td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
{% if conge.statut == "Refusé" %}
|
||||||
|
<span class="badge bg-danger">Refusé</span>
|
||||||
|
{% elif conge.statut == "Validé directeur" %}
|
||||||
|
<span class="badge bg-success">Valide </span>
|
||||||
|
{% elif conge.statut == "Validé chef" %}
|
||||||
|
<span class="badge bg-warning">Approuvé </span>
|
||||||
|
{% elif conge.statut == "Refusé par chef" %}
|
||||||
|
<span class="badge bg-danger">Refusé </span>
|
||||||
|
{% elif conge.statut == "En attente" %}
|
||||||
|
<span class="badge bg-warning">En attente </span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-sm btn-info"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#detailsCongeModal{{ conge.id }}">
|
||||||
|
<i class="bi bi-eye"></i> Voir
|
||||||
|
</button>
|
||||||
|
{% if conge.statut == "En attente" and conge.employe == request.user %}
|
||||||
|
<!-- Bouton modifier -->
|
||||||
|
<button class="btn btn-sm btn-primary"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#modifierCongeModal{{ conge.id }}">
|
||||||
|
Modifier
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Bouton supprimer -->
|
||||||
|
<button class="btn btn-sm btn-danger"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#supprimerCongeModal{{ conge.id }}">
|
||||||
|
Supprimer
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% for conge in conges %}
|
||||||
|
<div class="modal fade" id="modifierCongeModal{{ conge.id }}" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="conge_id" value="{{ conge.id }}">
|
||||||
|
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title">Modifier la demande de congé</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Type de congé</label>
|
||||||
|
<select name="type" class="form-select">
|
||||||
|
<option value="Conge Annuel" {% if conge.type == "Conge Annuel" %}selected{% endif %}>Conge Annuel</option>
|
||||||
|
<option value="Maladie" {% if conge.type == "Maladie" %}selected{% endif %}>Maladie</option>
|
||||||
|
<option value="Vacances" {% if conge.type == "Vacances" %}selected{% endif %}>Vacances</option>
|
||||||
|
<option value="Maternité" {% if conge.type == "Maternité" %}selected{% endif %}>Maternité</option>
|
||||||
|
|
||||||
|
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Nombre de jours</label>
|
||||||
|
<input type="number" name="nombre_jours" class="form-control" value="{{ conge.nombre_jours }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Date début</label>
|
||||||
|
<input type="date" name="date_debut" class="form-control" value="{{ conge.date_debut|date:'Y-m-d' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Date fin</label>
|
||||||
|
<input type="date" name="date_fin" class="form-control" value="{{ conge.date_fin|date:'Y-m-d' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for conge in conges %}
|
||||||
|
<div class="modal fade" id="detailsCongeModal{{ conge.id }}" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title">Détails de la demande</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
<li class="list-group-item"><strong>Employé :</strong> {{ conge.employe.first_name }} {{ conge.employe.last_name }}</li>
|
||||||
|
<li class="list-group-item"><strong>Type de congé :</strong> {{ conge.type }}</li>
|
||||||
|
<li class="list-group-item"><strong>Date début :</strong> {{ conge.date_debut|date:"d/m/Y" }}</li>
|
||||||
|
<li class="list-group-item"><strong>Date fin :</strong> {{ conge.date_fin|date:"d/m/Y" }}</li>
|
||||||
|
<li class="list-group-item"><strong>Nombre de jours :</strong> {{ conge.nombre_jours }}</li>
|
||||||
|
<li class="list-group-item"><strong>Solde restant :</strong> {{ conge.employe.solde_conge }}</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<strong>Statut :</strong>
|
||||||
|
{% if conge.statut == "En attente" %}
|
||||||
|
<span class="badge bg-warning text-dark">{{ conge.statut }}</span>
|
||||||
|
{% elif conge.statut == "Validé directeur" %}
|
||||||
|
<span class="badge bg-success">Validé</span>
|
||||||
|
{% elif conge.statut == "Refusé" %}
|
||||||
|
<span class="badge bg-danger">Refusé</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">{{ conge.statut }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item"><strong>Motif :</strong> {{ conge.motif_refus|default:"—" }}</li>
|
||||||
|
<li class="list-group-item"><strong>Date de la demande :</strong> {{ conge.date_demande|date:"d/m/Y H:i" }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
{% for conge in conges %}
|
||||||
|
<!-- Modal supprimer -->
|
||||||
|
<div class="modal fade" id="supprimerCongeModal{{ conge.id }}" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="delete_id" value="{{ conge.id }}">
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title">Supprimer la demande</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Voulez-vous vraiment supprimer la demande de congé du
|
||||||
|
<strong>{{ conge.date_debut|date:"d/m/Y" }}</strong> au
|
||||||
|
<strong>{{ conge.date_fin|date:"d/m/Y" }}</strong> ?</p>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button type="submit" class="btn btn-danger">Supprimer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="10" class="text-center text-muted">Aucune demande trouvée.</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<nav>
|
||||||
|
<ul class="pagination">
|
||||||
|
{% for page_num in conges.paginator.page_range %}
|
||||||
|
<li class="page-item {% if conges.number == page_num %}active{% endif %}">
|
||||||
|
<a class="page-link" href="?page={{ page_num }}&search={{ search }}">{{ page_num }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<!-- Modal Notifications Chef -->
|
||||||
|
<div class="modal fade" id="modalNotifications" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title"><i class="bi bi-bell-fill me-2"></i> Demandes de congé en attente</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{% if notifications %}
|
||||||
|
<table class="table table-bordered table-hover">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Employé</th>
|
||||||
|
<th>Période</th>
|
||||||
|
<th>Jours</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for conge in notifications %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ conge.employe.first_name }} {{ conge.employe.last_name }}</td>
|
||||||
|
<td>{{ conge.date_debut|date:"d/m/Y" }} - {{ conge.date_fin|date:"d/m/Y" }}</td>
|
||||||
|
<td>{{ conge.nombre_jours }}</td>
|
||||||
|
<td>{{ conge.type }}</td>
|
||||||
|
<td class="d-flex gap-1">
|
||||||
|
|
||||||
|
<!-- Valider -->
|
||||||
|
<form method="POST" action="{% url 'valider-par-chef' conge.id %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-success btn-sm" title="Valider">
|
||||||
|
<i class="bi bi-check-circle"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- Refuser -->
|
||||||
|
<button class="btn btn-danger btn-sm btn-refuser"
|
||||||
|
data-id="{{ conge.id }}"
|
||||||
|
data-nom="{{ conge.employe.first_name }} {{ conge.employe.last_name }}"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
data-bs-target="#modalRefuserGlobal">
|
||||||
|
<i class="bi bi-x-circle"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info">Aucune demande en attente.</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Modal Refuser -->
|
||||||
|
<div class="modal fade" id="modalRefuserGlobal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST" id="formRefuser">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title">Refuser la demande</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p id="texteConge"></p>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Motif du refus</label>
|
||||||
|
{{ form.motif_refus }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-danger">Refuser</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('modalRefuserGlobal'));
|
||||||
|
const texteConge = document.getElementById('texteConge');
|
||||||
|
const formRefuser = document.getElementById('formRefuser');
|
||||||
|
|
||||||
|
document.querySelectorAll('.btn-refuser').forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const congeId = this.getAttribute('data-id');
|
||||||
|
const nomEmploye = this.getAttribute('data-nom');
|
||||||
|
|
||||||
|
texteConge.innerHTML = `Refuser le congé de <strong>${nomEmploye}</strong> ?`;
|
||||||
|
formRefuser.action = `/refuser-par-chef/${congeId}/`; // URL dynamique vers Django
|
||||||
|
formRefuser.reset();
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
12
SIRH/static/templates/menu_principale.js
Normal file
12
SIRH/static/templates/menu_principale.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
navigator.serviceWorker.register('{% static "sw.js" %}').then(function(reg) {
|
||||||
|
console.log('Service worker registered.', reg);
|
||||||
|
}).catch(function(err) {
|
||||||
|
console.warn('Service worker registration failed:', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
31
SIRH/templates/SIRH/BASE.html
Normal file
31
SIRH/templates/SIRH/BASE.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{% load static %}
|
||||||
|
{% load tags_personnaliser %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
||||||
|
<link href="https://unpkg.com/tabulator-tables@6.4.0/dist/css/tabulator_bootstrap5.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
{% block 'css' %}{% endblock %}
|
||||||
|
<title> {% block 'titre_page'%}{% endblock%}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row flex-nowrap">
|
||||||
|
{% include 'parts/menu_principal.html' %}
|
||||||
|
<div class="col-9 p-4">
|
||||||
|
{% block 'contenu' %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% block 'modal' %} {% endblock %}
|
||||||
|
</div>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/luxon/3.7.2/luxon.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://unpkg.com/tabulator-tables/dist/js/tabulator.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.8/js/bootstrap.min.js"></script>
|
||||||
|
{% block 'js' %} {% endblock%}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
43
SIRH/templates/SIRH/login.html
Normal file
43
SIRH/templates/SIRH/login.html
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{% load static %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
|
||||||
|
<title>Login - SIRH</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid vh-100">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 vh-100 d-flex flex-column justify-content-center align-items-center">
|
||||||
|
<img src="{% static 'img/cerfig.jpg' %}" class="w-50">
|
||||||
|
<h5 class="text-center">Bienvenue sur les systèmes de gestion du CERFIG</h5>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 vh-100 d-flex justify-content-center align-items-center">
|
||||||
|
<form method="POST" action="{% url 'login' %}" class="w-100 shadow rounded px-3 py-5">
|
||||||
|
<h2 class="text-center">Connexion</h2>
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %}">{{message}}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="mail">Votre adresse email :</label>
|
||||||
|
<input type="text" name="mail" class="form-control" placeholder="Entrez votre e-mail" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="mot_de_passe">Mot de passe</label>
|
||||||
|
<input type="password" name="mot_de_passe" class="form-control" placeholder="Entrez votre mot de passe" required>
|
||||||
|
<i class="bi bi-eye toggle-password" onclick="togglePassword()"
|
||||||
|
style="position:absolute; right:10px; top:38px; cursor:pointer;"></i>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Se connecter</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
56
SIRH/templates/SIRH/parts/menu_principal.html
Normal file
56
SIRH/templates/SIRH/parts/menu_principal.html
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{% load static %}
|
||||||
|
{% load tags_personnaliser %}
|
||||||
|
|
||||||
|
<div class="col-3 bg-danger d-flex flex-column vh-100 pt-5 sticky-top">
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
{% if user.employe.photo %}
|
||||||
|
<img src="{{ user.employe.photo.url }}"
|
||||||
|
class="rounded-circle"
|
||||||
|
width="80"
|
||||||
|
height="80"
|
||||||
|
style="object-fit:cover;">
|
||||||
|
{% else %}
|
||||||
|
<i class="bi bi-person-circle text-white" style="font-size:60px;"></i>
|
||||||
|
{% endif %}
|
||||||
|
<div class="text-white mt-2">
|
||||||
|
{{ user.username }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="{% url 'gestion_employe:mon-profil' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
|
||||||
|
<i class="bi bi-person-circle"></i> Mon profil
|
||||||
|
</a>
|
||||||
|
{% if user|has_group:"ressource_humaine" or user|has_group:"direction" %}
|
||||||
|
<a href="{% url 'gestion_employe:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
|
||||||
|
<i class="bi bi-speedometer2"></i> Tableau de bord
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if user|has_group:"ressource_humaine" or user|has_group:"direction" %}
|
||||||
|
<a href="{% url 'gestion_projet:index' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
|
||||||
|
<i class="bi bi-folder"></i> Gestion des Projets
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'gestion_conges:conge' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
|
||||||
|
<i class="bi bi-airplane"></i> Gestion des congés
|
||||||
|
</a>
|
||||||
|
{% if user|is_chef_projet %}
|
||||||
|
<a href="{% url 'gestion_projet:activites-projet' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
|
||||||
|
<i class="bi bi-list-task"></i> Suivi des Activités
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'gestion_salle:reservation-salle' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
|
||||||
|
<i class="bi bi-calendar-check"></i> Réservation
|
||||||
|
</a>
|
||||||
|
{% comment %} <a href="{% url 'rapport-rh' %}" class="text-white fw-bold text-decoration-none mb-2" style="font-size:1.2em">
|
||||||
|
<i class="bi bi-graph-up"></i> Rapports et Statistiques
|
||||||
|
</a> {% endcomment %}
|
||||||
|
{% comment %} <a href="" class="text-white fw-bold text-decoration-none mb-2" style="font-size:1.2em">
|
||||||
|
<i class="bi bi-person-gear"></i> Gestion des Utilisateurs
|
||||||
|
</a> {% endcomment %}
|
||||||
|
{% comment %} <a href="{% url 'gestion_employe:departement' %}" class="text-white fw-bold text-decoration-none mb-2" style="font-size:1.2em">
|
||||||
|
<i class="bi bi-gear"></i> Paramètres
|
||||||
|
</a> {% endcomment %}
|
||||||
|
<a href="{% url 'deconnexion' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size:1.4em">
|
||||||
|
<i class="bi bi-box-arrow-right"></i> Déconnexion
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
67
SIRH/urls.py
Normal file
67
SIRH/urls.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
"""
|
||||||
|
URL configuration for SIRH project.
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/5.2/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import include, path
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.conf import settings
|
||||||
|
# from simple_sso.sso_server.server import Server
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
# server_sso = Server()
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
'',
|
||||||
|
views.login_view,
|
||||||
|
name='index'
|
||||||
|
),
|
||||||
|
|
||||||
|
path('login/',
|
||||||
|
views.login_view,
|
||||||
|
name='login'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'deconnexion/',
|
||||||
|
views.deconnexion_view,
|
||||||
|
name='deconnexion'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'employé/',
|
||||||
|
include("gestion_employe.urls")
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'gestion-conge/',
|
||||||
|
include("gestion_conge.urls")
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'gestion-projet/',
|
||||||
|
include("gestion_projet.urls")
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'gestion-salle/',
|
||||||
|
include("gestion_salle.urls")
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'admin/',
|
||||||
|
admin.site.urls
|
||||||
|
),
|
||||||
|
# path(
|
||||||
|
# 'sso',
|
||||||
|
# include(server_sso.get_urls())
|
||||||
|
# )
|
||||||
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
37
SIRH/views.py
Normal file
37
SIRH/views.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
|
def login_view(request):
|
||||||
|
"""
|
||||||
|
Gère la connexion des utilisateurs avec redirection selon le rôle et
|
||||||
|
vérification de l'acceptation de la politique d'utilisation.
|
||||||
|
"""
|
||||||
|
if request.method == 'POST':
|
||||||
|
email = request.POST.get('mail')
|
||||||
|
password = request.POST.get('mot_de_passe')
|
||||||
|
|
||||||
|
if not (email and password):
|
||||||
|
messages.error(request, "Veuillez remplir tous les champs.")
|
||||||
|
return render(request, 'login.html')
|
||||||
|
|
||||||
|
user = authenticate(request, username=email, password=password)
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
messages.error(request, "Nom d’utilisateur ou mot de passe incorrect.")
|
||||||
|
return render(request, 'login.html')
|
||||||
|
|
||||||
|
if not user.is_active:
|
||||||
|
messages.error(request, "Compte inactif. Contactez l'administrateur.")
|
||||||
|
return render(request, 'login.html')
|
||||||
|
|
||||||
|
login(request, user)
|
||||||
|
|
||||||
|
return redirect("gestion_conges:conge")
|
||||||
|
|
||||||
|
return render(request, 'login.html')
|
||||||
|
|
||||||
|
def deconnexion_view(request):
|
||||||
|
"""Gère la déconnexion de l'utilisateur."""
|
||||||
|
logout(request)
|
||||||
|
return redirect('login')
|
||||||
16
SIRH/wsgi.py
Normal file
16
SIRH/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for SIRH project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'SIRH.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
34
fonction_utilitaire/fonctions_utilitaire.py
Normal file
34
fonction_utilitaire/fonctions_utilitaire.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from django.utils import timezone
|
||||||
|
from gestion_employe.models import Contrat
|
||||||
|
from gestion_conge.models import Conge
|
||||||
|
|
||||||
|
|
||||||
|
QUOTA_CONGE_ANNUEL = 30
|
||||||
|
NOMBRE_PAGINATION = 8
|
||||||
|
DEBUT_RAPPEL = 60
|
||||||
|
DUREE_FIN_CONTRAT = 90
|
||||||
|
|
||||||
|
def solde_conge(employe):
|
||||||
|
"""Fonction de calcul du solde de congé restant l'employé"""
|
||||||
|
contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first()
|
||||||
|
|
||||||
|
if contrat is None or not contrat.date_debut:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "Votre contrat de travail n'a pas été correctement renseigner. Veuillez contacter les ressources humaines."
|
||||||
|
}
|
||||||
|
|
||||||
|
conges = Conge.objects.filter(employe=employe, validation_direction=True, date_fin__year = timezone.now().date().year)
|
||||||
|
jours_conges_valider = sum([conge.nombre_jours for conge in conges])
|
||||||
|
|
||||||
|
if jours_conges_valider >= QUOTA_CONGE_ANNUEL:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "Vous avez atteint le nombre maximal de jours de congés. Veuillez contacter l'administration."
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"quota_annuel": QUOTA_CONGE_ANNUEL - jours_conges_valider,
|
||||||
|
"nombre_jours_valide": jours_conges_valider
|
||||||
|
}
|
||||||
0
gestion_conge/__init__.py
Normal file
0
gestion_conge/__init__.py
Normal file
3
gestion_conge/admin.py
Normal file
3
gestion_conge/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
gestion_conge/apps.py
Normal file
6
gestion_conge/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class GestionCongeConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'gestion_conge'
|
||||||
17
gestion_conge/forms.py
Normal file
17
gestion_conge/forms.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from django import forms
|
||||||
|
from .models import Conge
|
||||||
|
|
||||||
|
class CongeForm(forms.ModelForm):
|
||||||
|
"""Formulaire de demande de congé."""
|
||||||
|
class Meta:
|
||||||
|
model = Conge
|
||||||
|
fields =['type', 'date_debut', 'date_fin']
|
||||||
|
widgets = {
|
||||||
|
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||||
|
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||||
|
'type': forms.Select(attrs={'class': 'form-select'}),
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'nombre_jours':'Nombre de jours',
|
||||||
|
}
|
||||||
30
gestion_conge/migrations/0001_initial.py
Normal file
30
gestion_conge/migrations/0001_initial.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 5.2.13 on 2026-04-17 12:03
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gestion_employe', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Conge',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('date_debut', models.DateField(verbose_name='Date de Début')),
|
||||||
|
('date_fin', models.DateField(verbose_name='Date de Fin')),
|
||||||
|
('type', models.CharField(choices=[('conge_annuel', 'Conge Annuel')], max_length=100, verbose_name='Type de Congé')),
|
||||||
|
('date_demande', models.DateField(auto_now_add=True, verbose_name='Date de Demande')),
|
||||||
|
('validation_hierarchique', models.BooleanField(default=None, null=True)),
|
||||||
|
('validation_direction', models.BooleanField(default=None, null=True)),
|
||||||
|
('motif_refus', models.TextField(blank=True, null=True)),
|
||||||
|
('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='employe', to='gestion_employe.employe')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
0
gestion_conge/migrations/__init__.py
Normal file
0
gestion_conge/migrations/__init__.py
Normal file
36
gestion_conge/models.py
Normal file
36
gestion_conge/models.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import pandas as pd
|
||||||
|
from django.db import models
|
||||||
|
from gestion_employe.models import Employe
|
||||||
|
|
||||||
|
class Conge(models.Model):
|
||||||
|
"""Modèle de création des congés"""
|
||||||
|
TYPE_CHOICES = [
|
||||||
|
# ('maladie', 'Maladie'),
|
||||||
|
('conge_annuel', 'Conge Annuel'),
|
||||||
|
# ('conge_maternite', 'Conge Maternité'),
|
||||||
|
# ('conge_mariage', 'Conge Mariage'),
|
||||||
|
# ('conge_naissance', 'Conge de Naissance'),
|
||||||
|
# ('conge_deces_proche', 'Conge de décès d\'un proche'),
|
||||||
|
# ('conge_mariage_proche', 'Conge de mariage d\'un proche'),
|
||||||
|
# ('autre', 'Autre'),
|
||||||
|
]
|
||||||
|
|
||||||
|
employe = models.ForeignKey(
|
||||||
|
Employe,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="employe"
|
||||||
|
)
|
||||||
|
date_debut = models.DateField(verbose_name='Date de Début')
|
||||||
|
date_fin = models.DateField(verbose_name='Date de Fin')
|
||||||
|
type = models.CharField(max_length=100, choices=TYPE_CHOICES, verbose_name='Type de Congé')
|
||||||
|
date_demande = models.DateField(auto_now_add=True, verbose_name="Date de Demande")
|
||||||
|
validation_hierarchique = models.BooleanField(default=None, null=True)
|
||||||
|
validation_direction = models.BooleanField(default=None, null=True)
|
||||||
|
|
||||||
|
motif_refus = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nombre_jours(self):
|
||||||
|
if self.date_debut and self.date_fin:
|
||||||
|
jours = pd.bdate_range(start=self.date_debut, end=self.date_fin)
|
||||||
|
return len(jours)
|
||||||
75
gestion_conge/static/gestion_conge/js/detail_conges.js
Normal file
75
gestion_conge/static/gestion_conge/js/detail_conges.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
const bouton_enregistrer_detail = document.getElementById("bouton-enregistrer-detail-conge");
|
||||||
|
|
||||||
|
if(bouton_enregistrer_detail){
|
||||||
|
bouton_enregistrer_detail.addEventListener("click", () => {
|
||||||
|
const form = document.getElementById("form-detail-conge");
|
||||||
|
const csrftoken = new FormData(form).get("csrfmiddlewaretoken");
|
||||||
|
const actionUrl = form.action;
|
||||||
|
const id_conge = document.getElementById("id_conge").value;
|
||||||
|
const validation_hierarchique_input = document.querySelector('input[name="validation_hierarchique"]:checked');
|
||||||
|
const validation_hierarchique = validation_hierarchique_input ? validation_hierarchique_input.value : null;
|
||||||
|
const validation_direction_input = document.querySelector('input[name="validation_direction"]:checked');
|
||||||
|
const validation_direction = validation_direction_input ? validation_direction_input.value : null;
|
||||||
|
const motif_refus = document.getElementById("motif_refus").value;
|
||||||
|
|
||||||
|
fetch(actionUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-CSRFToken": csrftoken
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
id_conge,
|
||||||
|
validation_hierarchique,
|
||||||
|
validation_direction,
|
||||||
|
motif_refus
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
alert(data.message);
|
||||||
|
navigation.reload();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if(document.getElementById("validation_hierarchique_refuse")){
|
||||||
|
document.getElementById("validation_hierarchique_refuse").addEventListener('click', function(){
|
||||||
|
if(this.checked){
|
||||||
|
alert("coucou");
|
||||||
|
document.getElementById("motif_refus_container").className="d-block form-group mt-3";
|
||||||
|
}else{
|
||||||
|
document.getElementById("motif_refus_container").className="d-none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if(document.getElementById("validation_hierarchique_refuse")){
|
||||||
|
document.getElementById("validation_hierarchique_refuse").addEventListener('click', function(){
|
||||||
|
if(this.checked){
|
||||||
|
document.getElementById("motif_refus_container").className="d-block form-group mt-3";
|
||||||
|
}else{
|
||||||
|
document.getElementById("motif_refus_container").className="d-none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if(document.getElementById("validation_hierarchique_valide")){
|
||||||
|
document.getElementById("validation_hierarchique_valide").addEventListener('click', function(){
|
||||||
|
if(this.checked){
|
||||||
|
document.getElementById("motif_refus_container").className="d-none";
|
||||||
|
}else{
|
||||||
|
document.getElementById("motif_refus_container").className="d-block form-group mt-3";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if(document.getElementById("validation_direction_valide")){
|
||||||
|
document.getElementById("validation_direction_valide").addEventListener('click', function(){
|
||||||
|
if(this.checked){
|
||||||
|
document.getElementById("motif_refus_container").className="d-block form-group mt-3";
|
||||||
|
}else{
|
||||||
|
document.getElementById("motif_refus_container").className="d-none";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
71
gestion_conge/static/gestion_conge/js/index.js
Normal file
71
gestion_conge/static/gestion_conge/js/index.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
const $ = (element) => document.getElementById(element);
|
||||||
|
|
||||||
|
const url_liste_conge_attente = $("liste-demande-conges").dataset.url
|
||||||
|
|
||||||
|
const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", {
|
||||||
|
layout : "fitColumns",
|
||||||
|
columns: [
|
||||||
|
{"title": "Nom et Prénom", "field": "prenom_nom"},
|
||||||
|
{"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{
|
||||||
|
inputFormat:"yyyy-MM-dd",
|
||||||
|
outputFormat:"dd/MM/yy",
|
||||||
|
}},
|
||||||
|
{"title": "Date de fin", "field": "date_fin", formatter:"datetime", formatterParams:{
|
||||||
|
inputFormat:"yyyy-MM-dd",
|
||||||
|
outputFormat:"dd/MM/yy",
|
||||||
|
}},
|
||||||
|
{"title": "Type de congé", "field": "type"},
|
||||||
|
{"title": "Date de la demande", "field": "date_demande"},
|
||||||
|
{"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{
|
||||||
|
allowEmpty : true ,
|
||||||
|
}},
|
||||||
|
{"title": "Validation par supérieur hiérarchique", "field": "validation_direction", formatter:"tickCross", formatterParams :{
|
||||||
|
allowEmpty : true ,
|
||||||
|
}},
|
||||||
|
],
|
||||||
|
pagination: true,
|
||||||
|
paginationSize: 5
|
||||||
|
})
|
||||||
|
|
||||||
|
const bouton_demande_conges = $("bouton-demande-conge");
|
||||||
|
|
||||||
|
bouton_demande_conges.addEventListener("click", (e) => {
|
||||||
|
var modalDemandeConge = new bootstrap.Modal(document.getElementById('modalDemandeConge'));
|
||||||
|
modalDemandeConge.show();
|
||||||
|
})
|
||||||
|
|
||||||
|
tableau_liste_demande_conge.on("rowClick", function(row, rowData) {
|
||||||
|
const data = rowData.getData();
|
||||||
|
$("id_conge").value = data.id;
|
||||||
|
$("employe").value = data.prenom_nom;
|
||||||
|
$("type_conge").value = data.type;
|
||||||
|
$("date_debut").value = data.date_debut;
|
||||||
|
$("date_fin").value = data.date_fin;
|
||||||
|
$("date_demande").value = data.date_demande;
|
||||||
|
$("nombre_jours").value = data.nombre_jours;
|
||||||
|
$("solde_restant").value = data.solde_conge;
|
||||||
|
$("motif_refus").value = data.motif_refus;
|
||||||
|
|
||||||
|
if($("validation_hierarchique_valide") & $("validation_hierarchique_refuse")){
|
||||||
|
$("validation_hierarchique_valide").checked = data.validation_hierarchique === true;
|
||||||
|
$("validation_hierarchique_refuse").checked = data.validation_hierarchique === false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if($("validation_direction_valide") & $("validation_direction_refuse")){
|
||||||
|
$("validation_direction_valide").checked = data.validation_direction === true;
|
||||||
|
$("validation_direction_refuse").checked = data.validation_direction === false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('detailsCongeModal'));
|
||||||
|
modal.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
fetch(url_liste_conge_attente)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if(data.success === true){
|
||||||
|
tableau_liste_demande_conge.setData(data.data)
|
||||||
|
}else{
|
||||||
|
alert(data.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
51
gestion_conge/templates/gestion_conge/index.html
Normal file
51
gestion_conge/templates/gestion_conge/index.html
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block 'titre_page' %} Gestion des congés {% endblock %}
|
||||||
|
{% block 'contenu' %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %}">{{message}}</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<div class="row d-flex justify-content-center mb-4">
|
||||||
|
<div class="col text-white bg-danger d-flex flex-column justify-content-center align-items-center border rounded p-4">
|
||||||
|
<div class="card-header fw-bold">
|
||||||
|
<i class="bi bi-x-circle me-2"></i> Congés refusés
|
||||||
|
</div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h5 class="fw-bold">{{ nombre_conges_refuse }}</h5>
|
||||||
|
</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="card-header fw-bold">
|
||||||
|
<i class="bi bi-hourglass-split me-2"></i> Congés en attente
|
||||||
|
</div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h5 class="fw-bold">{{ nombre_conges_en_attente }}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
|
||||||
|
<div class="card-header fw-bold">
|
||||||
|
<i class="bi bi-check-circle me-2"></i> Congés Validé
|
||||||
|
</div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<h5 class="fw-bold">{{ nombre_conges_valide }}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between my-4">
|
||||||
|
<h3><i class="bi bi-list-ul"></i> Liste des demandes de congé</h3>
|
||||||
|
<button class='btn btn-primary' id="bouton-demande-conge">Demande de congé</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<div id="liste-demande-conges" data-url="{% url 'gestion_conges:liste-des-conges' %}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'modal' %}
|
||||||
|
{% include 'gestion_conge/parts/modalDemandeConge.html' %}
|
||||||
|
{% include 'gestion_conge/parts/modalDetailConge.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'js' %}
|
||||||
|
<script type="text/javascript" src="{% static 'gestion_conge/js/index.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'gestion_conge/js/detail_conges.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="modal fade" id="modalDemandeConge" tabindex="-1" aria-labelledby="modalDemandeCongeLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title" id="modalDemandeCongeLabel">
|
||||||
|
<i class="bi bi-calendar-plus me-2"></i> Nouvelle demande de congé
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form method="post" action="{% url 'gestion_conges:demande-conge' %}" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ formulaire_demande_conge.as_p }}
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
<i class="bi bi-send"></i> Soumettre
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="bi bi-x-circle"></i> Annuler
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
<div class="modal fade" id="detailsCongeModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title">Détails de la demande de congés</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form method="post" id="form-detail-conge" action="{% url 'gestion_conges:validation-des-conges' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" id="id_conge" value="">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label for="motif_refus">Employé :</label>
|
||||||
|
<input type="text" class="form-control" id="employe" value="" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label for="type_conge">Type de congé :</label>
|
||||||
|
<select class="form-select" id="type_conge" readonly>
|
||||||
|
{% comment %} <option value="maladie">Maladie</option> {% endcomment %}
|
||||||
|
<option value="Conge Annuel">Conge Annuel</option>
|
||||||
|
{% comment %} <option value="conge_maternite">Conge Maternité</option> {% endcomment %}
|
||||||
|
{% comment %} <option value="conge_mariage">Conge Mariage</option>
|
||||||
|
<option value="conge_naissance">Conge Naissance</option>
|
||||||
|
<option value="conge_deces_proche">Conge de décès d'un proche</option>
|
||||||
|
<option value="conge_mariage_proche">Conge de mariage d'un proche</option>
|
||||||
|
<option value="autre">Autre</option> {% endcomment %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label for="date_debut">Date début :</label>
|
||||||
|
<input type="date" class="form-control" id="date_debut" value="" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label for="date_fin">Date fin :</label>
|
||||||
|
<input type="date" class="form-control" id="date_fin" value="" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label for="nombre_jours">Nombre de jours :</label>
|
||||||
|
<input type="number" class="form-control" id="nombre_jours" value="" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label for="solde_restant">Solde restant :</label>
|
||||||
|
<input type="number" class="form-control" id="solde_restant" value="" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label for="date_demande">Date de la demande :</label>
|
||||||
|
<input type="text" class="form-control" id="date_demande" value="" readonly>
|
||||||
|
</div>
|
||||||
|
{% if employe_est_il_chef or est_chef_projet %}
|
||||||
|
<hr>
|
||||||
|
<h5 class="text-center">Validation par le supérieur hiérarchique</h5>
|
||||||
|
<div class="d-flex align-items-center justify-content-center mb-2">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="validation_hierarchique" id="validation_hierarchique_valide" value="valide">
|
||||||
|
<label class="form-check-label" for="validation_hierarchique_valide">Valide</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="validation_hierarchique" id="validation_hierarchique_refuse" value="refuse" {% if conge.validation_hierarchique == False %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="validation_hierarchique_refuse">Refusé</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if membre_de_la_direction %}
|
||||||
|
<hr>
|
||||||
|
<h5 class="text-center">Validation par le directeur</h5>
|
||||||
|
<div class="d-flex align-items-center justify-content-center mb-2">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="validation_direction" id="validation_direction_valide" value="valide" {% if conge.validation_direction == True %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="validation_direction_valide">Valide</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="validation_direction" id="validation_direction_refuse" value="refuse" {% if conge.validation_direction == False %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="validation_direction_refuse">Refusé</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<hr>
|
||||||
|
<div class="d-none form-group mt-3" id="motif_refus_container">
|
||||||
|
<label for="motif_refus">Motif de refus (si applicable) :</label>
|
||||||
|
<textarea class="form-control" id="motif_refus" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
{% if employe_est_il_chef or membre_de_la_direction or est_chef_projet %}
|
||||||
|
<button type="button" class="btn btn-success" id="bouton-enregistrer-detail-conge">Enregistrer</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{% for conge in conges %}
|
||||||
|
<div class="modal fade" id="modifierCongeModal{{ conge.id }}" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="conge_id" value="{{ conge.id }}">
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title">Modifier la demande de congé</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Type de congé</label>
|
||||||
|
<select name="type" class="form-select">
|
||||||
|
<option value="Conge Annuel" {% if conge.type == "Conge Annuel" %}selected{% endif %}>Conge Annuel</option>
|
||||||
|
<option value="Maladie" {% if conge.type == "Maladie" %}selected{% endif %}>Maladie</option>
|
||||||
|
<option value="Conge maternité" {% if conge.type == "Conge maternité" %}selected{% endif %}>Conge Maternité</option>
|
||||||
|
<option value="Conge de Mariage" {% if conge.type == "Conge de Mariage" %}selected{% endif %}>Conge Mariage</option>
|
||||||
|
<option value="conge de naissance" {% if conge.type == "conge de naissance" %}selected{% endif %}>Conge de Naissance</option>
|
||||||
|
<option value="conge de deces dun proche" {% if conge.type == "conge de deces dun proche" %}selected{% endif %}>Conge de décès d'un proche</option>
|
||||||
|
<option value="conge de mariage dun proche" {% if conge.type == "conge de mariage dun proche" %}selected{% endif %}>Conge de mariage d'un proche</option>
|
||||||
|
<option value="conge pour evenements familiaux" {% if conge.type == "conge pour evenements familiaux" %}selected{% endif %}>Conge pour événements familiaux</option>
|
||||||
|
<option value="Autre" {% if conge.type == "Autre" %}selected{% endif %}>Autre</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Nombre de jours</label>
|
||||||
|
<input type="number" name="nombre_jours" class="form-control" value="{{ conge.nombre_jours }}">
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Date début</label>
|
||||||
|
<input type="date" name="date_debut" class="form-control" value="{{ conge.date_debut|date:'Y-m-d' }}">
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label>Date fin</label>
|
||||||
|
<input type="date" name="date_fin" class="form-control" value="{{ conge.date_fin|date:'Y-m-d' }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button type="submit" class="btn bg-orange-dark">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
24
gestion_conge/templates/gestion_conge/parts/modalRefus.html
Normal file
24
gestion_conge/templates/gestion_conge/parts/modalRefus.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<div class="modal fade" id="modalRefuserGlobal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<form method="POST" id="formRefuser">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title">Refuser la demande</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p id="texteConge"></p>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Motif du refus</label>
|
||||||
|
{{ form.motif_refus }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-danger">Refuser</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
3
gestion_conge/tests.py
Normal file
3
gestion_conge/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
27
gestion_conge/urls.py
Normal file
27
gestion_conge/urls.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = "gestion_conges"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
'',
|
||||||
|
views.index,
|
||||||
|
name='conge'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'demande_conge/',
|
||||||
|
views.demander_conge,
|
||||||
|
name='demande-conge'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'liste-des-conges/',
|
||||||
|
views.liste_demande_conges,
|
||||||
|
name='liste-des-conges'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'validation-des-conges/',
|
||||||
|
views.validation_de_conge,
|
||||||
|
name='validation-des-conges'
|
||||||
|
),
|
||||||
|
]
|
||||||
225
gestion_conge/views.py
Normal file
225
gestion_conge/views.py
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
import json
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from gestion_conge.forms import CongeForm
|
||||||
|
from gestion_employe.models import Affectation, Employe
|
||||||
|
from django.forms.models import model_to_dict
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.db.models import Q
|
||||||
|
from fonction_utilitaire import fonctions_utilitaire
|
||||||
|
from .models import Conge
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def index(request):
|
||||||
|
"""Vue de gestion de l'index"""
|
||||||
|
|
||||||
|
employe = Employe.objects.get(user__username = request.user)
|
||||||
|
membre_direction = 'direction' in employe.user.groups.values_list('name', flat=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
affectation = Affectation.objects.get(employe = employe, date_fin_daffectation__gte = timezone.now().date())
|
||||||
|
except Affectation.DoesNotExist:
|
||||||
|
affectation = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
projet = Affectation.objects.get(employe=employe, date_fin_daffectation__gte = timezone.now().date())
|
||||||
|
except Affectation.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if employe.chef:
|
||||||
|
nombre_conges_valide = Conge.objects.filter(validation_hierarchique = True, employe__departement = employe.departement).count()
|
||||||
|
nombre_conges_refuse = Conge.objects.filter(validation_hierarchique = False, employe__departement = employe.departement).count()
|
||||||
|
conges_en_attente = Conge.objects.filter(validation_hierarchique = None, employe__departement = employe.departement).order_by('-date_demande')
|
||||||
|
|
||||||
|
elif membre_direction:
|
||||||
|
nombre_conges_valide = Conge.objects.filter(validation_direction = True).count()
|
||||||
|
nombre_conges_refuse = Conge.objects.filter(validation_direction = False).count()
|
||||||
|
conges_en_attente = Conge.objects.filter(validation_hierarchique = True, validation_direction = None).order_by('-date_demande')
|
||||||
|
|
||||||
|
elif affectation and affectation.role == "chef_projet":
|
||||||
|
employes_du_projet = Affectation.objects.filter(
|
||||||
|
projet = projet.projet,
|
||||||
|
date_fin_daffectation__gte = timezone.now().date()
|
||||||
|
).values('employe')
|
||||||
|
|
||||||
|
nombre_conges_valide = Conge.objects.filter(
|
||||||
|
employe__in = employes_du_projet,
|
||||||
|
validation_hierarchique = True
|
||||||
|
).count()
|
||||||
|
|
||||||
|
nombre_conges_refuse = Conge.objects.filter(
|
||||||
|
Q(employe__in = employes_du_projet) &
|
||||||
|
(Q(validation_hierarchique = False) | Q(validation_direction = False))
|
||||||
|
).count()
|
||||||
|
|
||||||
|
conges_en_attente = Conge.objects.filter(
|
||||||
|
Q(employe__in = employes_du_projet) &
|
||||||
|
(
|
||||||
|
Q(validation_hierarchique__isnull = True) | Q(validation_direction__isnull = True)
|
||||||
|
)
|
||||||
|
).exclude(
|
||||||
|
Q(validation_hierarchique = True) | Q(validation_hierarchique = False) |
|
||||||
|
Q(validation_direction = True) | Q(validation_direction = False)
|
||||||
|
).order_by('-date_demande')
|
||||||
|
|
||||||
|
else:
|
||||||
|
nombre_conges_valide = Conge.objects.filter(
|
||||||
|
employe=employe,
|
||||||
|
validation_direction = True
|
||||||
|
).count()
|
||||||
|
|
||||||
|
nombre_conges_refuse = Conge.objects.filter(Q(employe=employe) & (
|
||||||
|
Q(validation_direction = False) | Q(validation_hierarchique = False)
|
||||||
|
)).count()
|
||||||
|
|
||||||
|
conges_en_attente = Conge.objects.filter(
|
||||||
|
Q(employe = employe) &
|
||||||
|
(
|
||||||
|
Q(validation_direction__isnull = True) | Q(validation_hierarchique__isnull = True)
|
||||||
|
)
|
||||||
|
).exclude(
|
||||||
|
Q(validation_hierarchique = True) | Q(validation_hierarchique = False)
|
||||||
|
).order_by('-date_demande')
|
||||||
|
|
||||||
|
|
||||||
|
return render(request, 'gestion_conge/index.html', {
|
||||||
|
"nombre_conges_valide": nombre_conges_valide,
|
||||||
|
"nombre_conges_refuse": nombre_conges_refuse,
|
||||||
|
"nombre_conges_en_attente": conges_en_attente.count(),
|
||||||
|
"formulaire_demande_conge": CongeForm,
|
||||||
|
"employe_est_il_chef": employe.chef,
|
||||||
|
"membre_de_la_direction": membre_direction,
|
||||||
|
"est_chef_projet": affectation.role == "chef_projet" if affectation else False,
|
||||||
|
})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def demander_conge(request):
|
||||||
|
"""Vue de gestion des demandes de congés"""
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(user__username = request.user)
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
messages.error(request, "Votre demande de congé a échoué car votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'administrateur.")
|
||||||
|
return redirect("gestion_conges:conge")
|
||||||
|
|
||||||
|
retour_quota = fonctions_utilitaire.solde_conge(employe)
|
||||||
|
if retour_quota["success"]:
|
||||||
|
quota_annuel = retour_quota['quota_annuel']
|
||||||
|
else:
|
||||||
|
messages.error(request, retour_quota['message'])
|
||||||
|
return redirect("gestion_conges:conge")
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = CongeForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
conge_obj = form.save(commit=False)
|
||||||
|
conge_obj.employe = employe
|
||||||
|
|
||||||
|
if conge_obj.type == "conge_annuel":
|
||||||
|
if retour_quota["nombre_jours_valide"] + conge_obj.nombre_jours > quota_annuel:
|
||||||
|
messages.error(request, "Quota annuel dépassé (30 jours max).")
|
||||||
|
return redirect("gestion_conges:conge")
|
||||||
|
|
||||||
|
conge_obj.save()
|
||||||
|
messages.success(request, "Votre demande de congé a été enregistrée.")
|
||||||
|
return redirect("gestion_conges:conge")
|
||||||
|
|
||||||
|
return redirect("gestion_conges:conge")
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def liste_demande_conges(request):
|
||||||
|
"""Vue de liste des demandes de congés en attente de validation selon le statut de l'utilisateur actuel"""
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(user__username = request.user)
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
return JsonResponse({
|
||||||
|
"success": False,
|
||||||
|
"message": "Votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'administrateur."
|
||||||
|
})
|
||||||
|
|
||||||
|
try:
|
||||||
|
affectation = Affectation.objects.get(
|
||||||
|
employe=employe,
|
||||||
|
date_fin_daffectation__gte=timezone.now().date()
|
||||||
|
)
|
||||||
|
except Affectation.DoesNotExist:
|
||||||
|
affectation = None
|
||||||
|
|
||||||
|
if employe.chef:
|
||||||
|
print("chef")
|
||||||
|
conges_en_attente = Conge.objects.filter(
|
||||||
|
employe__departement = employe.departement,
|
||||||
|
validation_hierarchique = None
|
||||||
|
).order_by('-date_demande')
|
||||||
|
|
||||||
|
elif affectation and affectation.role == "chef_projet":
|
||||||
|
employes_du_projet = Affectation.objects.filter(
|
||||||
|
projet = affectation.projet,
|
||||||
|
date_fin_daffectation__gte = timezone.now().date()
|
||||||
|
).values('employe')
|
||||||
|
|
||||||
|
conges_en_attente = Conge.objects.filter(
|
||||||
|
employe__in = employes_du_projet,
|
||||||
|
validation_hierarchique = None
|
||||||
|
).order_by('-date_demande')
|
||||||
|
|
||||||
|
elif 'direction' in employe.user.groups.values_list('name', flat=True):
|
||||||
|
conges_en_attente = Conge.objects.filter(
|
||||||
|
validation_hierarchique = True,
|
||||||
|
validation_direction = None
|
||||||
|
).order_by('-date_demande')
|
||||||
|
|
||||||
|
else:
|
||||||
|
conges_en_attente = Conge.objects.filter(
|
||||||
|
employe__user__username = request.user
|
||||||
|
).order_by('-date_demande')
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
"success": True,
|
||||||
|
"data":[
|
||||||
|
{
|
||||||
|
**model_to_dict(conge),
|
||||||
|
"prenom_nom": f"{conge.employe.user.first_name} {conge.employe.user.last_name}",
|
||||||
|
"date_demande": conge.date_demande,
|
||||||
|
"nombre_jours": conge.nombre_jours,
|
||||||
|
"type": dict(conge.TYPE_CHOICES).get(conge.type),
|
||||||
|
"solde_conge": fonctions_utilitaire.solde_conge(conge.employe)["quota_annuel"]
|
||||||
|
}
|
||||||
|
for conge in conges_en_attente]},
|
||||||
|
safe=False
|
||||||
|
)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def validation_de_conge(request):
|
||||||
|
"""
|
||||||
|
Vue de validation de conges par le superieur hierarchique.
|
||||||
|
1- Si l'employe appartient à un département, le congé est validé par le chef de département.
|
||||||
|
2- Si l'employé n'appartient pas à un département, le congé est validé par le chef de projet.
|
||||||
|
"""
|
||||||
|
request_data = json.loads(request.body)
|
||||||
|
conge_id = request_data.get("id_conge", None)
|
||||||
|
try:
|
||||||
|
conge = Conge.objects.get(id=conge_id)
|
||||||
|
except conge.DoesNotExist:
|
||||||
|
return JsonResponse({"message": "Le congé selectionné n'existe pas."})
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
validation_hierarchique = request_data.get("validation_hierarchique", None)
|
||||||
|
validation_direction = request_data.get("validation_direction", None)
|
||||||
|
motif_refus = request_data.get("motif_refus", "")
|
||||||
|
|
||||||
|
if validation_hierarchique is not None:
|
||||||
|
conge.validation_hierarchique = True if validation_hierarchique == "valide" else False
|
||||||
|
if validation_hierarchique == "refuse" and not motif_refus:
|
||||||
|
return JsonResponse({"message": "Veuillez fournir un motif de refus."})
|
||||||
|
conge.motif_refus = motif_refus if validation_hierarchique == "refuse" else ""
|
||||||
|
|
||||||
|
if validation_direction is not None:
|
||||||
|
conge.validation_direction = True if validation_direction == "valide" else False
|
||||||
|
if validation_direction == "refuse" and not motif_refus:
|
||||||
|
return JsonResponse({"message": "Veuillez fournir un motif de refus."})
|
||||||
|
conge.motif_refus = motif_refus if validation_direction == "refuse" else ""
|
||||||
|
|
||||||
|
conge.save()
|
||||||
|
return JsonResponse({"message": "La décision a été enregistrée avec succès."})
|
||||||
0
gestion_employe/__init__.py
Normal file
0
gestion_employe/__init__.py
Normal file
14
gestion_employe/admin.py
Normal file
14
gestion_employe/admin.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import Departement, Employe
|
||||||
|
|
||||||
|
@admin.register(Departement)
|
||||||
|
class DepartementAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("nom", )
|
||||||
|
|
||||||
|
@admin.register(Employe)
|
||||||
|
class EmployeAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"user",
|
||||||
|
"matricule",
|
||||||
|
"fonction",
|
||||||
|
)
|
||||||
84
gestion_employe/forms.py
Normal file
84
gestion_employe/forms.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
from django import forms
|
||||||
|
from .models import Contrat, Departement, Employe,Affectation,Formation
|
||||||
|
|
||||||
|
class EmployeForm(forms.ModelForm):
|
||||||
|
"""Formulaire pour modifier les informations de profil d'un employé"""
|
||||||
|
class Meta:
|
||||||
|
model = Employe
|
||||||
|
fields = (
|
||||||
|
'adresse',
|
||||||
|
'telephone',
|
||||||
|
'CV',
|
||||||
|
'diplome',
|
||||||
|
'rib',
|
||||||
|
'photo',
|
||||||
|
'casier_judiciaire'
|
||||||
|
)
|
||||||
|
|
||||||
|
class AffectationForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Affectation
|
||||||
|
fields = (
|
||||||
|
'projet',
|
||||||
|
'date_affectation',
|
||||||
|
'date_fin_daffectation',
|
||||||
|
'role',
|
||||||
|
'pourcentage_temps_affectation'
|
||||||
|
)
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'projet': forms.Select(attrs={'class': 'form-select'}),
|
||||||
|
'date_affectation': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||||
|
'date_fin_daffectation': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||||
|
'role': forms.Select(attrs={'class': 'form-select'}),
|
||||||
|
'pourcentage_temps_affectation': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||||
|
}
|
||||||
|
|
||||||
|
class ContratForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Contrat
|
||||||
|
fields = [
|
||||||
|
"numero_contrat",
|
||||||
|
"type_contrat",
|
||||||
|
"date_debut",
|
||||||
|
"date_fin",
|
||||||
|
"salaire_mensuel",
|
||||||
|
"statut",
|
||||||
|
"fichier_contrat"
|
||||||
|
]
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'numero_contrat': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'type_contrat': forms.Select(attrs={'class': 'form-select'}),
|
||||||
|
'date_debut': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||||
|
'date_fin': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||||
|
'salaire_mensuel': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'statut': forms.Select(attrs={'class': 'form-select'}),
|
||||||
|
'fichier_contrat': forms.FileInput(attrs={'class': 'form-control'}),
|
||||||
|
}
|
||||||
|
|
||||||
|
class DepartementForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Departement
|
||||||
|
fields = ['nom']
|
||||||
|
|
||||||
|
class FormationForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Formation
|
||||||
|
fields = [
|
||||||
|
'titre',
|
||||||
|
'organisme',
|
||||||
|
'date_obtention',
|
||||||
|
'date_fin',
|
||||||
|
'description',
|
||||||
|
'certificat'
|
||||||
|
]
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'titre': forms.TextInput(attrs={"class": "form-control"}),
|
||||||
|
'organisme': forms.TextInput(attrs={"class": "form-control"}),
|
||||||
|
'date_obtention': forms.DateInput(attrs={"class": "form-control", "type": "date"}),
|
||||||
|
'date_fin': forms.DateInput(attrs={"class": "form-control", "type": "date"}),
|
||||||
|
'description': forms.Textarea(attrs={"class": "form-control"}),
|
||||||
|
'certificat': forms.FileInput(attrs={"class": "form-control"}),
|
||||||
|
}
|
||||||
88
gestion_employe/migrations/0001_initial.py
Normal file
88
gestion_employe/migrations/0001_initial.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Generated by Django 5.2.13 on 2026-04-17 12:03
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gestion_projet', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Departement',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nom', models.CharField(max_length=100)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Employe',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('matricule', models.CharField(blank=True, max_length=10, null=True, unique=True)),
|
||||||
|
('fonction', models.CharField(blank=True, choices=[('directeur', 'Directeur'), ('assistant_direction', 'Assistante de direction'), ('comptable', 'Comptable'), ('raf', 'RAF'), ('data_manager', 'Data Manager'), ('logisticien', 'Logisticien'), ('post_doctorant', 'Post-Doctorant'), ('qualiticien', 'Qualiticien'), ('technicien_surface', 'Technicien de surface'), ('chauffeur', 'Chauffeur')], max_length=50, null=True)),
|
||||||
|
('date_embauche', models.DateField(blank=True, null=True)),
|
||||||
|
('adresse', models.CharField(blank=True, max_length=100, null=True)),
|
||||||
|
('telephone', models.CharField(blank=True, max_length=15, null=True)),
|
||||||
|
('sexe', models.CharField(blank=True, choices=[('m', 'Masculin'), ('f', 'Féminin')], max_length=1, null=True)),
|
||||||
|
('date_naissance', models.DateField(blank=True, null=True)),
|
||||||
|
('CV', models.FileField(blank=True, null=True, upload_to='cv/')),
|
||||||
|
('diplome', models.FileField(blank=True, null=True, upload_to='diplomes/')),
|
||||||
|
('rib', models.FileField(blank=True, null=True, upload_to='rib/')),
|
||||||
|
('photo', models.ImageField(blank=True, null=True, upload_to='photos/')),
|
||||||
|
('casier_judiciaire', models.FileField(blank=True, null=True, upload_to='casier/')),
|
||||||
|
('chef', models.BooleanField(default=False, verbose_name='Cet utilisateur est-il chef de ce département ?')),
|
||||||
|
('departement', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='gestion_employe.departement')),
|
||||||
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Contrat',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('numero_contrat', models.CharField(max_length=100, unique=True)),
|
||||||
|
('type_contrat', models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage')], max_length=50)),
|
||||||
|
('date_debut', models.DateField()),
|
||||||
|
('date_fin', models.DateField(blank=True, null=True)),
|
||||||
|
('salaire_mensuel', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
||||||
|
('statut', models.CharField(choices=[('actif', 'Actif'), ('termine', 'Terminé'), ('suspendu', 'Suspendu'), ('rupture_contrat', 'Rupture de Contrat')], max_length=50)),
|
||||||
|
('fichier_contrat', models.FileField(blank=True, null=True, upload_to='contrats/')),
|
||||||
|
('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_employe.employe')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Formation',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('titre', models.CharField(max_length=255, verbose_name='Nom du certificat')),
|
||||||
|
('organisme', models.CharField(max_length=255, verbose_name="Nom de l'organisme")),
|
||||||
|
('description', models.TextField(blank=True, null=True, verbose_name='Description de la formation')),
|
||||||
|
('date_obtention', models.DateField(blank=True, null=True, verbose_name="Date d'obtention")),
|
||||||
|
('date_fin', models.DateField(blank=True, null=True, verbose_name='Date de fin de validité')),
|
||||||
|
('certificat', models.FileField(blank=True, null=True, upload_to='documents/formations/', verbose_name='Certificat (PDF/Image)')),
|
||||||
|
('employe', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='employe_formation', to='gestion_employe.employe')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Affectation',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('date_affectation', models.DateField()),
|
||||||
|
('date_fin_daffectation', models.DateField(blank=True, null=True)),
|
||||||
|
('role', models.CharField(choices=[('chef_projet', 'Chef de projet'), ('doctorant', 'Doctorant'), ('mastorant', 'Mastorant'), ('consultant', 'Consultant'), ('stagiaire', 'Stagiaire'), ('laborantin', 'Laborantin'), ('medecin', 'Médecin'), ('autre', 'Autre')], default='Membre', max_length=20)),
|
||||||
|
('pourcentage_temps_affectation', models.DecimalField(decimal_places=2, max_digits=5)),
|
||||||
|
('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet')),
|
||||||
|
('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_employe.employe')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('projet', 'employe')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 6.0.4 on 2026-04-29 10:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('gestion_employe', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='contrat',
|
||||||
|
name='type_contrat',
|
||||||
|
field=models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage'), ('convention_bourse_entretien', "Convention de bourse d'entretien")], max_length=50),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
gestion_employe/migrations/__init__.py
Normal file
0
gestion_employe/migrations/__init__.py
Normal file
122
gestion_employe/models.py
Normal file
122
gestion_employe/models.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils import timezone
|
||||||
|
from gestion_projet.models import Projet
|
||||||
|
|
||||||
|
class Departement(models.Model):
|
||||||
|
"""Modèle représentant un département de l'entreprise."""
|
||||||
|
nom = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.nom
|
||||||
|
|
||||||
|
class Employe(models.Model):
|
||||||
|
"""Modèle représentant un employé de l'entreprise."""
|
||||||
|
FONCTION_LISTE = [
|
||||||
|
('directeur', 'Directeur'),
|
||||||
|
('assistant_direction', 'Assistante de direction'),
|
||||||
|
('comptable', 'Comptable'),
|
||||||
|
('raf', 'RAF'),
|
||||||
|
('data_manager', 'Data Manager'),
|
||||||
|
('logisticien', 'Logisticien'),
|
||||||
|
('post_doctorant', 'Post-Doctorant'),
|
||||||
|
('qualiticien', 'Qualiticien'),
|
||||||
|
('technicien_surface', 'Technicien de surface'),
|
||||||
|
('chauffeur', 'Chauffeur'),
|
||||||
|
]
|
||||||
|
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||||
|
matricule = models.CharField(max_length=10, unique=True, null=True, blank=True)
|
||||||
|
departement = models.ForeignKey(Departement, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
fonction = models.CharField(max_length=50, blank=True, null=True, choices=FONCTION_LISTE)
|
||||||
|
date_embauche = models.DateField(blank=True, null=True)
|
||||||
|
adresse = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
telephone = models.CharField(max_length=15, null=True, blank=True)
|
||||||
|
sexe = models.CharField(max_length=1, null=True, blank=True, choices=[('m', 'Masculin'), ('f', 'Féminin')])
|
||||||
|
date_naissance = models.DateField(blank=True, null=True)
|
||||||
|
|
||||||
|
CV = models.FileField(upload_to='cv/', blank=True, null=True)
|
||||||
|
diplome = models.FileField(upload_to='diplomes/', blank=True, null=True)
|
||||||
|
rib = models.FileField(upload_to='rib/', blank=True, null=True)
|
||||||
|
photo = models.ImageField(upload_to='photos/', blank=True, null=True)
|
||||||
|
casier_judiciaire = models.FileField(upload_to='casier/', blank=True, null=True)
|
||||||
|
|
||||||
|
chef = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
verbose_name="Cet utilisateur est-il chef de ce département ?"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.user.first_name or 'N/A'} {self.user.last_name or ' '} ({self.matricule or ' '})"
|
||||||
|
|
||||||
|
class Contrat(models.Model):
|
||||||
|
"""Modèle représentant un contrat de travail d'un employé."""
|
||||||
|
TYPE_CONTRAT = [
|
||||||
|
('contrat_duree_determinee', 'Contrat à Durée Déterminée'),
|
||||||
|
('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'),
|
||||||
|
('contrat_prestation', 'Contrat de Prestation de Service'),
|
||||||
|
('contrat_stage', 'Contrat de Stage'),
|
||||||
|
('convention_bourse_entretien', 'Convention de bourse d\'entretien'),
|
||||||
|
]
|
||||||
|
|
||||||
|
STATUT_CONTRAT = [
|
||||||
|
('actif', 'Actif'),
|
||||||
|
('termine', 'Terminé'),
|
||||||
|
('suspendu', 'Suspendu'),
|
||||||
|
('rupture_contrat', 'Rupture de Contrat'),
|
||||||
|
]
|
||||||
|
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
|
||||||
|
numero_contrat = models.CharField(max_length=100, unique=True)
|
||||||
|
type_contrat = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=TYPE_CONTRAT,
|
||||||
|
)
|
||||||
|
date_debut = models.DateField()
|
||||||
|
date_fin = models.DateField(null=True, blank=True)
|
||||||
|
salaire_mensuel = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
|
||||||
|
statut = models.CharField(max_length=50, choices=STATUT_CONTRAT)
|
||||||
|
fichier_contrat = models.FileField(upload_to='contrats/', null=True, blank=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nombre_jours_restant(self):
|
||||||
|
if self.date_fin:
|
||||||
|
return (self.date_fin - timezone.now().date()).days
|
||||||
|
return 100
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.numero_contrat} - {self.type_contrat}"
|
||||||
|
|
||||||
|
class Affectation(models.Model):
|
||||||
|
"""Modèle représentant l'affectation d'un employé à un projet avec un rôle spécifique."""
|
||||||
|
ROLE_CHOICES = [
|
||||||
|
('chef_projet', 'Chef de projet'),
|
||||||
|
('doctorant','Doctorant'),
|
||||||
|
('mastorant','Mastorant'),
|
||||||
|
('consultant','Consultant'),
|
||||||
|
('stagiaire','Stagiaire'),
|
||||||
|
('laborantin','Laborantin'),
|
||||||
|
('medecin','Médecin'),
|
||||||
|
('autre','Autre'),
|
||||||
|
]
|
||||||
|
|
||||||
|
employe = models.ForeignKey(Employe, on_delete=models.CASCADE)
|
||||||
|
projet = models.ForeignKey(Projet, on_delete=models.CASCADE)
|
||||||
|
date_affectation = models.DateField()
|
||||||
|
date_fin_daffectation = models.DateField(null=True, blank=True)
|
||||||
|
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='Membre')
|
||||||
|
pourcentage_temps_affectation = models.DecimalField(max_digits=5, decimal_places=2)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('projet', 'employe')
|
||||||
|
|
||||||
|
class Formation(models.Model):
|
||||||
|
"""Modèle représentant une formation suivie par un employé."""
|
||||||
|
employe = models.ForeignKey(Employe, on_delete=models.CASCADE, null=True, related_name='employe_formation')
|
||||||
|
titre = models.CharField(max_length=255, verbose_name="Nom du certificat")
|
||||||
|
organisme = models.CharField(max_length=255, verbose_name="Nom de l'organisme")
|
||||||
|
description = models.TextField(blank=True, null=True, verbose_name="Description de la formation")
|
||||||
|
date_obtention = models.DateField(blank=True, null=True, verbose_name="Date d'obtention")
|
||||||
|
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)")
|
||||||
|
def __str__(self):
|
||||||
|
return self.titre
|
||||||
263
gestion_employe/static/gestion_employe/js/index.js
Normal file
263
gestion_employe/static/gestion_employe/js/index.js
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
const $ = (element) => document.getElementById(element);
|
||||||
|
|
||||||
|
const url_liste_employe = $("tableau_liste_employe").dataset.url;
|
||||||
|
const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
|
||||||
|
columns: [
|
||||||
|
{"title": "Matricule", "field": "matricule"},
|
||||||
|
{"title": "Nom & Prénom", "field": "employe"},
|
||||||
|
{"title": "Fonction", "field": "fonction"},
|
||||||
|
{"title": "Projet", "field": "projet"},
|
||||||
|
{"title": "Téléphone", "field": "telephone"},
|
||||||
|
],
|
||||||
|
// ajaxURL: url_liste_employe,
|
||||||
|
pagination: true,
|
||||||
|
paginationSize: 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
fetch(url_liste_employe)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success){
|
||||||
|
console.log(data.data)
|
||||||
|
tableau_liste_employe.setData(data.data);
|
||||||
|
}else{
|
||||||
|
alert(data.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
tableau_liste_employe.on("rowClick", function (row, rowData) {
|
||||||
|
const data = rowData.getData();
|
||||||
|
document.getElementById('detail-id').value = data.id;
|
||||||
|
document.getElementById('detail-matricule').value = data.matricule;
|
||||||
|
document.getElementById('detail-employe').value = data.employe;
|
||||||
|
document.getElementById('detail-fonction').value = data.fonction;
|
||||||
|
document.getElementById('detail-departement').value = data.departement;
|
||||||
|
document.getElementById('detail-sexe').value = "";
|
||||||
|
document.getElementById('detail-dateNaissance').value = "";
|
||||||
|
document.getElementById('detail-email').value = data.email;
|
||||||
|
document.getElementById('detail-telephone').value = data.telephone;
|
||||||
|
document.getElementById('detail-adresse').value = data.adresse;
|
||||||
|
document.getElementById('detail-dateEmbauche').value = data.date_embauche;
|
||||||
|
document.getElementById('detail-dateNaissance').value = data.date_naissance;
|
||||||
|
document.getElementById('detail-sexe').value = data.sexe;
|
||||||
|
|
||||||
|
document.getElementById('document-diplome').href = data.diplome;
|
||||||
|
document.getElementById('document-diplome').textContent = data.diplome || "Aucun diplôme";
|
||||||
|
document.getElementById('document-cv').href = data.CV;
|
||||||
|
document.getElementById('document-cv').textContent = data.CV || "Aucun CV";
|
||||||
|
document.getElementById('document-rib').href = data.rib;
|
||||||
|
document.getElementById('document-rib').textContent = data.rib || "Aucun RIB";
|
||||||
|
document.getElementById('document-casier-judiciaire').href = data.casier_judiciaire;
|
||||||
|
document.getElementById('document-casier-judiciaire').textContent = data.casier_judiciaire || "Aucun casier judiciaire";
|
||||||
|
|
||||||
|
document.getElementById("document-employe").innerHTML = data.employe || "Employé inconnu";
|
||||||
|
|
||||||
|
document.getElementById("formations-list").innerHTML = "";
|
||||||
|
for (const formation of data.formations) {
|
||||||
|
document.getElementById("formations-list").innerHTML += `
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Titre :</label>
|
||||||
|
<input type="text" class="form-control" value="${formation.titre}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Organisme :</label>
|
||||||
|
<input type="text" class="form-control" value="${formation.organisme}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date d'Optention :</label>
|
||||||
|
<input type="text" class="form-control" value="${formation.date_obtention}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date de validitée :</label>
|
||||||
|
<input type="text" class="form-control" value="${formation.date_fin}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
document.getElementById("contrat-employe").innerHTML = data.employe || "Employé inconnu";
|
||||||
|
document.getElementById("employeIdInput").value = data.id;
|
||||||
|
|
||||||
|
document.getElementById("contrats-list").innerHTML = "";
|
||||||
|
for (const contrat of data.contrats) {
|
||||||
|
document.getElementById("contrats-list").innerHTML += `
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Numéro de contrat :</label>
|
||||||
|
<input type="text" class="form-control" value="${contrat.numero_contrat}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Type de contrat :</label>
|
||||||
|
<select class="form-select">
|
||||||
|
<option value="">Sélectionnez un projet</option>
|
||||||
|
<option value="contrat_duree_determinee" ${contrat.type_contrat == 'contrat_duree_determinee' ? 'selected' : ''}>Contrat à Durée Déterminée</option>
|
||||||
|
<option value="contrat_duree_indeterminee" ${contrat.type_contrat == 'contrat_duree_indeterminee' ? 'selected' : ''}>Contrat à Durée Indéterminée</option>
|
||||||
|
<option value="contrat_prestation" ${contrat.type_contrat == 'contrat_prestation' ? 'selected' : ''}>Contrat de Prestation de Service</option>
|
||||||
|
<option value="contrat_stage" ${contrat.type_contrat == 'contrat_stage' ? 'selected' : ''}>Contrat de Stage</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date de début :</label>
|
||||||
|
<input type="text" class="form-control" value="${contrat.date_debut}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date de fin :</label>
|
||||||
|
<input type="text" class="form-control" value="${contrat.date_fin}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Salaire mensuel :</label>
|
||||||
|
<input type="text" class="form-control" value="${contrat.salaire_mensuel}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Statut :</label>
|
||||||
|
<input type="text" class="form-control" value="${contrat.statut}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Fichier de contrat :</label>
|
||||||
|
<a href="${contrat.fichier_contrat}" target="_blank">Voir le document</a>
|
||||||
|
</div>
|
||||||
|
<button class='d-block m-auto btn btn-danger btn-supprimer-contrat' data-contratid="${contrat.numero_contrat}">
|
||||||
|
<i class='bi bi-trash'></i> Supprimer le contrat
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
const supprimerButtons = document.getElementsByClassName("btn-supprimer-contrat");
|
||||||
|
Array.from(supprimerButtons).forEach(button => {
|
||||||
|
button.addEventListener("click", function() {
|
||||||
|
const contratId = this.dataset.contratid;
|
||||||
|
fetch(`contrat/supprimer/`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"id": contratId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
alert(data.message);
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
document.getElementById("affectation-nom-employe").textContent = data.employe || "Employé inconnu";
|
||||||
|
document.getElementById("affecter_employe_id").value = data.id;
|
||||||
|
|
||||||
|
document.getElementById("affectations-list").innerHTML = "";
|
||||||
|
for (const affectation of data.affectations) {
|
||||||
|
document.getElementById("affectations-list").innerHTML += `
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Nom du projet :</label>
|
||||||
|
<input type="text" class="form-control" value="${affectation.projet}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Rôle :</label>
|
||||||
|
<input type="text" class="form-control" value="${affectation.role}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Pourcentation d'affectation :</label>
|
||||||
|
<input type="text" class="form-control" value="${affectation.pourcentage_temps_affectation}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date d'affectation :</label>
|
||||||
|
<input type="date" class="form-control" value="${affectation.date_affectation}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date de fin d'affectation :</label>
|
||||||
|
<input type="date" class="form-control" value="${affectation.date_fin_daffectation}">
|
||||||
|
</div>
|
||||||
|
<button class='d-block m-auto btn btn-danger btn-supprimer-affectation' data-contratid="${affectation.id}">
|
||||||
|
<i class='bi bi-trash'></i> Supprimer l'affectation
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(document.getElementsByClassName("btn-supprimer-affectation")).forEach(button => {
|
||||||
|
button.addEventListener("click", function() {
|
||||||
|
const affectationId = this.dataset.contratid;
|
||||||
|
fetch(`affectation/supprimer/`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"id": affectationId
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
alert(data.message);
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
const modal = new bootstrap.Modal($("modalDetailEmploye"));
|
||||||
|
modal.show();
|
||||||
|
})
|
||||||
|
|
||||||
|
$("enregistrerDetail").addEventListener("click", function() {
|
||||||
|
const id_ = document.getElementById('detail-id').value;
|
||||||
|
const fonction = document.getElementById('detail-fonction').value;
|
||||||
|
const dateEmbauche = document.getElementById('detail-dateEmbauche').value;
|
||||||
|
const matricule = document.getElementById('detail-matricule').value;
|
||||||
|
|
||||||
|
const url_enregistrer_detail = $("modalDetailEmployeBody").dataset.url;
|
||||||
|
fetch(url_enregistrer_detail, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"id": id_,
|
||||||
|
"fonction": fonction,
|
||||||
|
"date_embauche": dateEmbauche,
|
||||||
|
"matricule": matricule
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
alert(data.message);
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
$("input-recherche").addEventListener("input", function() {
|
||||||
|
const recherche = this.value;
|
||||||
|
if (recherche){
|
||||||
|
tableau_liste_employe.setFilter("employe", "like", recherche);
|
||||||
|
}else{
|
||||||
|
tableau_liste_employe.clearFilter();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const listeContratExpirant = new Tabulator("#listeContratExpirant", {
|
||||||
|
columns: [
|
||||||
|
{"title": "Employé", "field": "employe"},
|
||||||
|
{"title": "Type de contrat", "field": "type_contrat"},
|
||||||
|
{"title": "Date de début", "field": "date_debut", formatter: 'datetime', formatterParams:{
|
||||||
|
inputFormat:"yyyy-MM-dd",
|
||||||
|
outputFormat:"dd/MM/yy",
|
||||||
|
}},
|
||||||
|
{"title": "Date de fin", "field": "date_fin", formatter: 'datetime', formatterParams:{
|
||||||
|
inputFormat:"yyyy-MM-dd",
|
||||||
|
outputFormat:"dd/MM/yy",
|
||||||
|
}},
|
||||||
|
{"title": "Statut", "field": "statut"},
|
||||||
|
{"title": "Lien du fichier", "field": "fichier_contrat", formatter:"link", formatterParams:{
|
||||||
|
target:"_blank",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ajaxURL: $("boutonContratExpirants").dataset.urlexpirants,
|
||||||
|
})
|
||||||
46
gestion_employe/static/gestion_employe/js/mon_profil.js
Normal file
46
gestion_employe/static/gestion_employe/js/mon_profil.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
const $ = (element) => document.getElementById(element);
|
||||||
|
|
||||||
|
url_certificat = $("tableau-certificat").dataset.url;
|
||||||
|
const tableau_certificat = new Tabulator("#tableau-certificat", {
|
||||||
|
columns: [
|
||||||
|
{"title": "Nom du certificat", "field": "titre"},
|
||||||
|
{"title": "Nom de l'organisme", "field": "organisme"},
|
||||||
|
{"title": "Date d'obtention", "field": "date_obtention", formatter:"datetime", formatterParams:{
|
||||||
|
inputFormat:"yyyy-MM-dd",
|
||||||
|
outputFormat:"dd/MM/yy",
|
||||||
|
}},
|
||||||
|
{"title": "Date de fin de validité", "field": "date_fin", formatter:"datetime", formatterParams:{
|
||||||
|
inputFormat:"yyyy-MM-dd",
|
||||||
|
outputFormat:"dd/MM/yy",
|
||||||
|
}},
|
||||||
|
],
|
||||||
|
ajaxURL: url_certificat,
|
||||||
|
})
|
||||||
|
const enregistrerProfil = $("enregistrerProfil");
|
||||||
|
|
||||||
|
enregistrerProfil.addEventListener("click", (e) => {
|
||||||
|
|
||||||
|
const url = $("information-personnelles").dataset.url;
|
||||||
|
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
formData.append("nom", $("nom").value);
|
||||||
|
formData.append("prenom", $("prenom").value);
|
||||||
|
formData.append("email", $("email").value);
|
||||||
|
formData.append("telephone", $("telephone").value);
|
||||||
|
formData.append("adresse", $("adresse").value);
|
||||||
|
formData.append("sexe", $("sexe").value);
|
||||||
|
formData.append("date_naissance", $("date_naissance").value);
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"X-CSRFToken": csrftoken
|
||||||
|
},
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => alert(data.message))
|
||||||
|
.catch(error => console.error("Erreur:", error));
|
||||||
|
});
|
||||||
67
gestion_employe/templates/gestion_employe/index.html
Normal file
67
gestion_employe/templates/gestion_employe/index.html
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block 'titre_page' %} Gestion des projets {% endblock %}
|
||||||
|
{% block 'css' %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'contenu' %}
|
||||||
|
<div class="col px-2">
|
||||||
|
<h3>Gestion des employés</h3>
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<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>
|
||||||
|
<h3>{{ nombre_employes }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col text-white bg-info d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-3">
|
||||||
|
<span class="fs-5">Nombre de prestataires</span>
|
||||||
|
<h3>{{ nombre_cps }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col text-white bg-warning d-flex flex-column justify-content-center align-items-center border rounded p-4">
|
||||||
|
<span class="fs-5">Nombre de stagiaires</span>
|
||||||
|
<h3>{{ nombre_stage }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col d-flex justify-content-between mb-3">
|
||||||
|
<h3>
|
||||||
|
<i class="bi bi-people"></i>
|
||||||
|
La liste des employés
|
||||||
|
</h3>
|
||||||
|
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modalListeDesContratExpirants" id="boutonContratExpirants" data-urlexpirants="{% url 'gestion_employe:liste-contrat-expirants' %}">
|
||||||
|
Contrats proches de la fin <span class="badge badge-light">{{ nombre_expirants }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-2 d-flex justify-content-end">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col d-flex justify-content-center">
|
||||||
|
<input class="form-control d-block" type="search" placeholder="Recherche par nom et prénom..." id="input-recherche">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<div id="tableau_liste_employe" data-url={% url 'gestion_employe:liste-employes' %}></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'modal' %}
|
||||||
|
{% include "gestion_employe/parts/modalDetailEmploye.html" %}
|
||||||
|
{% include "gestion_employe/parts/modalCreationContrat.html" %}
|
||||||
|
{% include "gestion_employe/parts/modalAffectationProjet.html" %}
|
||||||
|
{% include "gestion_employe/parts/modalDocument.html" %}
|
||||||
|
{% include "gestion_employe/parts/modalListeContratExpirants.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'js' %}
|
||||||
|
<script type="text/javascript" src="{% static 'gestion_employe/js/index.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
105
gestion_employe/templates/gestion_employe/modifier-profil.html
Normal file
105
gestion_employe/templates/gestion_employe/modifier-profil.html
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block 'titre_page' %} Gestion des employés - Mon profil {% endblock %}
|
||||||
|
{% block 'contenu' %}
|
||||||
|
<div class="card shadow-sm border-0 w-100" style="max-width: 850px;">
|
||||||
|
<div class="card-header bg-orange-dark">
|
||||||
|
<h5 class="mb-0 fw-bold">
|
||||||
|
<i class="bi bi-pencil-square me-2"></i> Modifier mon profil
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="POST" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="accordion" id="accordionProfil">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingInfo">
|
||||||
|
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseInfo" aria-expanded="false" aria-controls="collapseInfo">
|
||||||
|
<i class="bi bi-person-lines-fill me-2"></i> Informations personnelles
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseInfo" class="accordion-collapse collapse" aria-labelledby="headingInfo" data-bs-parent="#accordionProfil">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Photo de profil :</label>
|
||||||
|
<input type="file" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Prénom :</label>
|
||||||
|
<input type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Nom :</label>
|
||||||
|
<input type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Email :</label>
|
||||||
|
<input type="email" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Téléphone :</label>
|
||||||
|
<input type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Adresse :</label>
|
||||||
|
<input type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingDocs">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDocs" aria-expanded="false" aria-controls="collapseDocs">
|
||||||
|
<i class="bi bi-file-earmark-text me-2"></i> Documents
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseDocs" class="accordion-collapse collapse" aria-labelledby="headingDocs" data-bs-parent="#accordionProfil">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>CV :</label>
|
||||||
|
<input type="file" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Diplome :</label>
|
||||||
|
<input type="file" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>RIB :</label>
|
||||||
|
<input type="file" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Casier judiciaire :</label>
|
||||||
|
<input type="file" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end mt-4 gap-2 flex-wrap">
|
||||||
|
<a href="{% url 'gestion_employe:mon-profil' %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-x-circle me-1"></i> Annuler
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="bi bi-check-circle me-1"></i> Enregistrer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'modal' %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'js' %}
|
||||||
|
{% endblock %}
|
||||||
241
gestion_employe/templates/gestion_employe/monprofil.html
Normal file
241
gestion_employe/templates/gestion_employe/monprofil.html
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block 'titre_page' %} Gestion des employés - Mon profil {% endblock %}
|
||||||
|
{% block 'contenu' %}
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<h5 class="fw-bold">
|
||||||
|
<i class="bi bi-pencil-square me-2"></i> Les informations de mon profil
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn btn-warning" data-bs-toggle="modal" data-bs-target="#modalModifierProfil">
|
||||||
|
<i class="bi bi-pencil-square me-1"></i> Modifier le mot de passe
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{% if message.tags == 'error' %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not expiration_contrat %}
|
||||||
|
<div class="alert alert-danger fade show alert-dismissible mt-2">
|
||||||
|
<strong>Important :</strong> Les informations sur votre contrat n'ont pas été renseignées, veuillez contacter les ressources humaines.
|
||||||
|
</div>
|
||||||
|
{% elif contrat_nb_jours_restant %}
|
||||||
|
<div class="alert alert-danger fade show alert-dismissible mt-2">
|
||||||
|
<strong>Important :</strong> Votre contrat de travail expire dans {{ contrat_nb_jours_restant }} jours, veuillez contacter les ressources humaines.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="accordion mt-2" id="accordionInformationEmploye">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingContrats">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseProfil" aria-expanded="false" aria-controls="collapseProfil">
|
||||||
|
<i class="bi bi-file-earmark-text me-2"></i> Mon identité
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseProfil" class="accordion-collapse" aria-labelledby="headingContrats" data-bs-parent="#accordionInformationEmploye">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>photo</label>
|
||||||
|
{% if employe.photo %}
|
||||||
|
<span>Fichier actuel : <a href="{{ employe.photo.url }}">{{employe.photo}}</a></span>
|
||||||
|
{% endif %}
|
||||||
|
<input type="file" id="photo" name="photo" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Matricule :</label>
|
||||||
|
<input type="text" class="form-control" id="matricule" value="{{ employe.matricule|default:'' }}" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Nom :</label>
|
||||||
|
<input type="text" class="form-control" id="nom" value="{{ employe.user.last_name }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Prénom :</label>
|
||||||
|
<input type="text" class="form-control" id="prenom" value="{{ employe.user.first_name }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Sexe :</label>
|
||||||
|
<select id='sexe' class="form-select">
|
||||||
|
<option value='h' {% if employe.sexe == 'h' %}selected{% endif %}>Homme</option>
|
||||||
|
<option value='f' {% if employe.sexe == 'f' %}selected{% endif %}>Femme</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date de naissance :</label>
|
||||||
|
<input type="date" class="form-control" id="date_naissance" value="{{ employe.date_naissance|date:'Y-m-d' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Département :</label>
|
||||||
|
<input type="text" class="form-control" id="departement" value="{{ employe.departement.nom|default:'' }}" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Email :</label>
|
||||||
|
<input type="email" class="form-control" id="email" value="{{ employe.user.email|default:'' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Téléphone :</label>
|
||||||
|
<input type="text" class="form-control" id="telephone" value="{{ employe.telephone|default:'' }}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Adresse :</label>
|
||||||
|
<input type="text" class="form-control" id="adresse" value="{{ employe.adresse|default:'' }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-success d-block m-auto" id="enregistrerProfil">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingContrats">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseContrats" aria-expanded="false" aria-controls="collapseContrats">
|
||||||
|
<i class="bi bi-file-earmark-text me-2"></i> Contrats
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseContrats" class="accordion-collapse collapse" aria-labelledby="headingContrats" data-bs-parent="#accordionInformationEmploye">
|
||||||
|
<div class="accordion-body">
|
||||||
|
{% for contrat in contrats %}
|
||||||
|
<div class="mb-3 p-3 border rounded">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Numéro :</label>
|
||||||
|
<input type="text" class="form-control" value="{{ contrat.numero_contrat }}" id="numero_contrat" readonly>
|
||||||
|
</div><div class="form-group mb-2">
|
||||||
|
<label>Type de Contrat :</label>
|
||||||
|
<input type="text" class="form-control" value="{{ contrat.type_contrat }}" id="type_contrat" readonly>
|
||||||
|
</div><div class="form-group mb-2">
|
||||||
|
<label>Date début :</label>
|
||||||
|
<input type="date" class="form-control" value="{{ contrat.date_debut|date:'Y-m-d' }}" id="date_debut" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date fin :</label>
|
||||||
|
<input type="date" class="form-control" value="{{ contrat.date_fin|date:'Y-m-d' }}" id="date_fin" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Salaire :</label>
|
||||||
|
<input type="number" class="form-control" value="{{ contrat.salaire_mensuel }}" id="salaire" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Statut :</label>
|
||||||
|
<input type="text" class="form-control" value="{{ contrat.statut }}" id="satut" readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>fichier :</label>
|
||||||
|
<a href="{{ contrat.fichier_contrat }}" target="_blank" class="btn btn-outline-primary">Voir le contrat</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<p class="text-danger">Aucun contrat trouvé.</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingProjets">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseProjets" aria-expanded="false" aria-controls="collapseProjets">
|
||||||
|
<i class="bi bi-briefcase me-2"></i> Projets
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseProjets" class="accordion-collapse collapse" aria-labelledby="headingProjets" data-bs-parent="#accordionInformationEmploye">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for projet in projets %}
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
{{ projet.nom_projet }} ({{ projet.pourcentage_temps_affectation }}%) du <strong>{{ projet.date_debut|date:"d/m/Y" }}</strong> au
|
||||||
|
<strong>{{ projet.date_fin|date:"d/m/Y" }}</strong> en tant que {{ projet.role }}.
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<li class="list-group-item text-muted text-center">Vous n'êtes affecté à aucun projet.</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingDocs">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDocs" aria-expanded="false" aria-controls="collapseDocs">
|
||||||
|
<i class="bi bi-file-earmark-text me-2"></i> Documents
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseDocs" class="accordion-collapse collapse" aria-labelledby="headingDocs" data-bs-parent="#accordionInformationEmploye">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<form method="POST" action="{% url 'gestion_employe:enregistrement-document-rh' %}" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>photo</label>
|
||||||
|
{% if employe.photo %}
|
||||||
|
<span>Fichier actuel : <a href="{{ employe.photo.url }}">{{employe.photo}}</a></span>
|
||||||
|
{% endif %}
|
||||||
|
<input type="file" class="form-control" name="photo">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>CV</label>
|
||||||
|
{% if employe.CV %}
|
||||||
|
<span>Fichier actuel : <a href="{{ employe.CV.url }}">{{employe.CV}}</a></span>
|
||||||
|
{% endif %}
|
||||||
|
<input type="file" class="form-control" name="cv">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Diplôme</label>
|
||||||
|
{% if employe.diplome %}
|
||||||
|
<span>Fichier actuel : <a href="{{ employe.diplome.url }}">{{employe.diplome}}</a></span>
|
||||||
|
{% endif %}
|
||||||
|
<input type="file" class="form-control" name="diplome">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Casier judiciaire</label>
|
||||||
|
{% if employe.casier_judiciaire %}
|
||||||
|
<span>Fichier actuel : <a href="{{ employe.casier_judiciaire.url }}">{{employe.casier_judiciaire}}</a></span>
|
||||||
|
{% endif %}
|
||||||
|
<input type="file" class="form-control" name="casier_judiciaire">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>RIB</label>
|
||||||
|
{% if employe.rib %}
|
||||||
|
<span>Fichier actuel : <a href="{{ employe.rib.url }}">{{employe.rib}}</a></span>
|
||||||
|
{% endif %}
|
||||||
|
<input type="file" class="form-control" name="rib">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-success d-block m-auto">Enregistrer</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingCertificat">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseCertificat" aria-expanded="false" aria-controls="collapseCertificat">
|
||||||
|
<i class="bi bi-file-earmark-text me-2"></i> Certificats
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseCertificat" class="accordion-collapse collapse" aria-labelledby="headingCertificat" data-bs-parent="#accordionInformationEmploye">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div id="tableau-certificat" data-url="{% url 'gestion_employe:liste-formation' %}"></div>
|
||||||
|
<button class="btn btn-success d-block m-auto" data-bs-toggle="modal" data-bs-target="#modalAjouterFormation">Ajouter un certificat</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'modal' %}
|
||||||
|
{% include "gestion_employe/parts/modificationMotPasse.html" %}
|
||||||
|
{% include "gestion_employe/parts/modalAjoutFormation.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'js' %}
|
||||||
|
|
||||||
|
<script src="{% static 'gestion_employe/js/mon_profil.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<div class="modal fade" id="modalAffectation" tabindex="-1" aria-labelledby="modalAffectationLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog ">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title" id="modalAffectationLabel" >
|
||||||
|
Affectation de projet (<span id="affectation-nom-employe"></span>)
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form action="{% url 'gestion_employe:affecter_employe_projet' %}" method="POST">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="affecter_employe_id" id="affecter_employe_id">
|
||||||
|
{{ affectation_form.as_p }}
|
||||||
|
<div class="modal-footer bg-light">
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
<i class="bi bi-check-circle"></i> affecter
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="bi bi-x-circle me-1"></i> Annuler
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<div class="modal fade" id="modalAjouterFormation" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title">Ajouter un certificat</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form method="post" action="{% url 'gestion_employe:ajouter_formation' %}" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ formation_form.as_p }}
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-success">Enregistrer</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<div class="modal fade" id="modalContrat" tabindex="-1" aria-labelledby="modalContratLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog ">
|
||||||
|
<div class="modal-content bg-white">
|
||||||
|
<div class="modal-header ">
|
||||||
|
<h5 class="modal-title" id="modalContratLabel">
|
||||||
|
<i class="bi bi-file-earmark-text me-2"></i>
|
||||||
|
Contrat de <span id="contrat-employe"></span>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form action="{% url 'gestion_employe:creation-contrat' %}" method="POST" id="contrat_form" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="employe_id" value="" id="employeIdInput">
|
||||||
|
{{ contrat_form.as_p }}
|
||||||
|
<div class="modal-footer bg-light">
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
<i class="bi bi-check-circle me-1"></i> Enregistrer
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="bi bi-x-circle me-1"></i> Annuler
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% comment %} <fieldset class="border p-3 mb-4">
|
||||||
|
<legend class="fw-bold mb-3 text-muted">Informations du contrat</legend>
|
||||||
|
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Numero de contrat :</label>
|
||||||
|
<input type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Type contrat :</label>
|
||||||
|
<input type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>date de début :</label>
|
||||||
|
<input type="date" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>date de fin :</label>
|
||||||
|
<input type="date" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Salaire mensuel :</label>
|
||||||
|
<input type="text" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Fichier du contrat :</label>
|
||||||
|
<input type="file" class="form-control">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset> {% endcomment %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
{% load tags_personnaliser %}
|
||||||
|
<div class="modal fade" id="modalDetailEmploye" tabindex="-1" aria-labelledby="modalDetailEmployeLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header ">
|
||||||
|
<h5 class="modal-title bi-pencil-square" id="modalDetailEmployeLabel">
|
||||||
|
Détails de l'employé <span id="detail-employe"></span>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="modalDetailEmployeBody" data-url="{% url 'gestion_employe:enregistrer-detail-employe' %}">
|
||||||
|
<div class="row g-3">
|
||||||
|
<input type="hidden" id="detail-id">
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Matricule :</label>
|
||||||
|
<input type="text" class="form-control" id="detail-matricule">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date d'embauche :</label>
|
||||||
|
<input type="date" class="form-control" id="detail-dateEmbauche">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Département :</label>
|
||||||
|
<input type="text" class="form-control" id="detail-departement">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Sexe :</label>
|
||||||
|
<select id="detail-sexe" class="form-select">
|
||||||
|
<option value="">-- Sélectionner --</option>
|
||||||
|
<option value="h">Masculin</option>
|
||||||
|
<option value="f">Féminin</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date de naissance :</label>
|
||||||
|
<input type="date" class="form-control" id="detail-dateNaissance">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Fonction :</label>
|
||||||
|
<select class="form-select" id='detail-fonction'>
|
||||||
|
<option value='chauffeur'>Chauffeur</option>
|
||||||
|
<option value='technicien_surface'>Technicien de surface</option>
|
||||||
|
<option value='qualiticien'>Qualiticien</option>
|
||||||
|
<option value='post_doctorant'>Post-Doctorant</option>
|
||||||
|
<option value='logisticien'>Logisticien</option>
|
||||||
|
<option value='data_manager'>Data Manager</option>
|
||||||
|
<option value='raf'>RAF</option>
|
||||||
|
<option value='comptable'>Comptable</option>
|
||||||
|
<option value='assistant_direction'>Assistante de direction</option>
|
||||||
|
<option value='directeur'>Directeur</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Email :</label>
|
||||||
|
<input type="email" class="form-control" id="detail-email">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Téléphone :</label>
|
||||||
|
<input type="text" class="form-control" id="detail-telephone">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Adresse :</label>
|
||||||
|
<input type="text" class="form-control" id="detail-adresse">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
{% if user|has_group:"ressource_humaine" %}
|
||||||
|
<div class="col">
|
||||||
|
<button type="button" class="btn btn-success d-block m-auto" id="enregistrerDetail">
|
||||||
|
<i class="bi bi-save"></i> Enregistrer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col text-end">
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalDocuments">
|
||||||
|
<i class="bi bi-folder2-open"></i> Voir les documents RH
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="my-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<h5>
|
||||||
|
<i class="bi bi-file-earmark-text me-2"></i> Liste des Contrats
|
||||||
|
</h5>
|
||||||
|
{% if user|has_group:"ressource_humaine" %}
|
||||||
|
<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 }}">
|
||||||
|
<i class="bi bi-file-earmark-text"></i> Créer contrat
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="row" id="contrats-list"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="my-4">
|
||||||
|
<div class="row d-flex g-3">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<h5><i class="bi bi-pin-angle me-2"></i> Affectations</h5>
|
||||||
|
{% if user|has_group:"ressource_humaine" %}
|
||||||
|
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modalAffectation" onclick="setEmployeId()">
|
||||||
|
<i class="bi bi-file-earmark-text"></i>Affecter
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div id="affectations-list"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="my-4">
|
||||||
|
<div class="row g-3">
|
||||||
|
<h5 class="mb-3"><i class="bi bi-award-fill me-2"></i> Formations</h5>
|
||||||
|
<div class="row" id="formations-list"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer bg-light">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="bi bi-x-circle"></i> Fermer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<div class="modal fade" id="modalDocuments" tabindex="-1" aria-labelledby="modalDocumentsLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog ">
|
||||||
|
<div class="modal-content border-0 shadow-lg">
|
||||||
|
<div class="modal-header ">
|
||||||
|
<h5 class="modal-title" id="modalDocumentsLabel">
|
||||||
|
<i class="bi bi-folder2-open me-2"></i> Documents RH de <span id="document-employe"></span>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col">
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<strong>Diplôme :</strong>
|
||||||
|
<a id="document-diplome" target="_blank"></a>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<strong>CV :</strong>
|
||||||
|
<a id="document-cv" target="_blank"></a>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<strong>RIB :</strong>
|
||||||
|
<a id="document-rib" target="_blank"></a>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<strong>Casier judiciaire :</strong>
|
||||||
|
<a id="document-casier-judiciaire" target="_blank"></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer bg-light">
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="bi bi-x-circle"></i> Fermer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<div class="modal fade" id="modalListeDesContratExpirants" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Contrats expirants dans un maximum de 60 jours</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div id='listeContratExpirant'></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<div class="modal fade" id="modalModifierProfil" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title"><i class="bi bi-person-lines-fill me-2"></i> Modifier le mot de passe</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form method="POST" action="{% url 'gestion_employe:modifier-mot-passe' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Votre mot de passe actuel :</label>
|
||||||
|
<input class="form-control" type="password" name="ancien-mdp">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Votre nouveau mot de passe :</label>
|
||||||
|
<input class="form-control" type="password" name="nouveau-mdp">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Confirmez votre nouveau mot de passe :</label>
|
||||||
|
<input class="form-control" type="password" name="confirmation-mdp">
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
Fermer
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
<i class="bi bi-check-circle me-1"></i> Enregistrer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
24
gestion_employe/templatetags/tags_personnaliser.py
Normal file
24
gestion_employe/templatetags/tags_personnaliser.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
"""Tags personnalisés pour les templates d'authentification.
|
||||||
|
|
||||||
|
Ce fichier contient des tags personnalisés pour les templates du projet lié à
|
||||||
|
la gestion de l'accès des utilisateurs aux différentes pages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
from django.utils import timezone
|
||||||
|
from gestion_employe.models import Affectation
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def has_group(user, group_name):
|
||||||
|
"""Vérifiez si un utilisateur appartient à un groupe spécifique."""
|
||||||
|
return user.groups.filter(name=group_name).exists()
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def is_chef_projet(user):
|
||||||
|
try:
|
||||||
|
affectation = Affectation.objects.get(employe__user__username = user, date_fin_daffectation__gte = timezone.now().date())
|
||||||
|
except Affectation.DoesNotExist:
|
||||||
|
return False
|
||||||
|
return affectation.role == "chef_projet"
|
||||||
3
gestion_employe/tests.py
Normal file
3
gestion_employe/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
102
gestion_employe/urls.py
Normal file
102
gestion_employe/urls.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
from . import views
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
|
app_name = 'gestion_employe'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
'',
|
||||||
|
views.index,
|
||||||
|
name='index'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'liste-employes/',
|
||||||
|
views.liste_employe,
|
||||||
|
name='liste-employes'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'enregistrer-detail-employe/',
|
||||||
|
views.enregistrer_detail_employe,
|
||||||
|
name='enregistrer-detail-employe'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'affectation/supprimer/',
|
||||||
|
views.suppression_affectation,
|
||||||
|
name='supprimer-affectation'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'contrat/creation/',
|
||||||
|
views.creation_contrat,
|
||||||
|
name='creation-contrat'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'contrat/supprimer/',
|
||||||
|
views.suppression_contrat,
|
||||||
|
name='supprimer-contrat'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'Affectation/affectation/',
|
||||||
|
views.affecter_employe_projet,
|
||||||
|
name='affecter_employe_projet'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'mon-profil/',
|
||||||
|
views.mon_profil,
|
||||||
|
name='mon-profil'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'enregistrement-document-rh',
|
||||||
|
views.enregistrement_document,
|
||||||
|
name='enregistrement-document-rh'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'modifier-employe',
|
||||||
|
views.modifier_employer,
|
||||||
|
name='modifier-employe'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"mes-formations/ajouter/",
|
||||||
|
views.ajouter_formation,
|
||||||
|
name="ajouter_formation"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"mes-formations/liste/",
|
||||||
|
views.liste_formation,
|
||||||
|
name="liste-formation"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"mes-formations/modifier/<int:pk>/",
|
||||||
|
views.modifier_formation,
|
||||||
|
name="modifier_formation"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"mes-formations/supprimer/<int:pk>/",
|
||||||
|
views.supprimer_formation,
|
||||||
|
name="supprimer_formation"
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'modifier-mot-passe/',
|
||||||
|
views.modifier_mot_passe,
|
||||||
|
name='modifier-mot-passe'
|
||||||
|
),
|
||||||
|
# path(
|
||||||
|
# 'creation-departement/',
|
||||||
|
# views.creation_departement,
|
||||||
|
# name='creation-departement'
|
||||||
|
# ),
|
||||||
|
# path(
|
||||||
|
# 'modifier-departement/',
|
||||||
|
# views.modifier_departement,
|
||||||
|
# name='modifier-departement'
|
||||||
|
# ),
|
||||||
|
# path(
|
||||||
|
# 'suppression-departement/',
|
||||||
|
# views.supprimer_departement,
|
||||||
|
# name='suppression-departement/'
|
||||||
|
# ),
|
||||||
|
path(
|
||||||
|
"liste-contrat-expirants",
|
||||||
|
views.liste_contrat_expirants,
|
||||||
|
name='liste-contrat-expirants'
|
||||||
|
)
|
||||||
|
]
|
||||||
447
gestion_employe/views.py
Normal file
447
gestion_employe/views.py
Normal file
@@ -0,0 +1,447 @@
|
|||||||
|
import json
|
||||||
|
from datetime import timedelta, datetime
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.auth import authenticate
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.forms.models import model_to_dict
|
||||||
|
from django.db.models import Sum
|
||||||
|
|
||||||
|
from .models import Employe, Contrat, Affectation, Formation
|
||||||
|
from .forms import AffectationForm, ContratForm, FormationForm
|
||||||
|
from fonction_utilitaire import fonctions_utilitaire
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def index(request):
|
||||||
|
"""Vue d'index"""
|
||||||
|
employes = Employe.objects.all().order_by('-user__date_joined')
|
||||||
|
nombre_employes = Employe.objects.count()
|
||||||
|
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
|
||||||
|
nombre_stage = Contrat.objects.filter(type_contrat='contrat_stage').count()
|
||||||
|
date_limite = timezone.now().date() + timedelta(days=60)
|
||||||
|
nombre_expirants = Contrat.objects.filter(
|
||||||
|
date_fin__lte=date_limite,
|
||||||
|
date_fin__gte=timezone.now().date(),
|
||||||
|
statut='actif'
|
||||||
|
).count()
|
||||||
|
|
||||||
|
return render(request, 'gestion_employe/index.html', {
|
||||||
|
'employes': employes,
|
||||||
|
'nombre_employes': nombre_employes,
|
||||||
|
'nombre_cps': nombre_cps,
|
||||||
|
'nombre_stage': nombre_stage,
|
||||||
|
'nombre_expirants': nombre_expirants,
|
||||||
|
'affectation_form': AffectationForm(),
|
||||||
|
'contrat_form': ContratForm()
|
||||||
|
})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def liste_contrat_expirants(request):
|
||||||
|
""" Liste des contrats proches """
|
||||||
|
|
||||||
|
date_limite = timezone.now().date() + timedelta(days = fonctions_utilitaire.DUREE_FIN_CONTRAT)
|
||||||
|
contats_expirants = [
|
||||||
|
{
|
||||||
|
'employe': f"{contrat.employe.user.first_name} {contrat.employe.user.last_name}",
|
||||||
|
'type_contrat': dict(Contrat.TYPE_CONTRAT).get(contrat.type_contrat),
|
||||||
|
'date_debut': contrat.date_debut,
|
||||||
|
'date_fin': contrat.date_fin,
|
||||||
|
'statut': contrat.statut,
|
||||||
|
'fichier_contrat': contrat.fichier_contrat.url if contrat.fichier_contrat else ""
|
||||||
|
}
|
||||||
|
for contrat in
|
||||||
|
Contrat.objects.filter(date_fin__lte=date_limite, date_fin__gte=timezone.now().date(), statut='actif')
|
||||||
|
]
|
||||||
|
|
||||||
|
return JsonResponse(contats_expirants, safe=False)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def affecter_employe_projet(request):
|
||||||
|
"""Vue pour affecter un employé à un projet avec vérification des contraintes d'affectation"""
|
||||||
|
if request.method == 'POST':
|
||||||
|
employe_id = request.POST.get('affecter_employe_id')
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(id=employe_id)
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
messages.error(request, "L'employé spécifié n'existe pas.")
|
||||||
|
return redirect('gestion_employe:index')
|
||||||
|
|
||||||
|
form = AffectationForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
projet = form.cleaned_data['projet']
|
||||||
|
date_fin_affectation = form.cleaned_data['date_fin_daffectation']
|
||||||
|
temps_nouveau = form.cleaned_data['pourcentage_temps_affectation']
|
||||||
|
date_affectation = form.cleaned_data['date_affectation']
|
||||||
|
|
||||||
|
if (date_fin_affectation and date_affectation):
|
||||||
|
total_affectation = (
|
||||||
|
Affectation.objects.filter(employe=employe)
|
||||||
|
.aggregate(total_pourcentage_affectation = Sum('pourcentage_temps_affectation'))
|
||||||
|
['total_pourcentage_affectation'] or 0
|
||||||
|
)
|
||||||
|
if (date_fin_affectation < date_affectation):
|
||||||
|
messages.warning(request, "La date de fin d'affectation ne peut pas être antérieure à la date de début.")
|
||||||
|
return redirect('gestion_employe:index')
|
||||||
|
elif date_fin_affectation > projet.date_fin:
|
||||||
|
messages.warning(request, f"La date de fin de l'affectation ({date_fin_affectation}) ne peut pas dépasser la date de fin du projet ({projet.date_fin}).")
|
||||||
|
return redirect('gestion_employe:index')
|
||||||
|
elif total_affectation + temps_nouveau > 100:
|
||||||
|
messages.warning(
|
||||||
|
request,
|
||||||
|
f"Les pourcentages d'affectation de l'employé {employe.first_name} {employe.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)."
|
||||||
|
)
|
||||||
|
return redirect('gestion_employe:index')
|
||||||
|
|
||||||
|
Affectation.objects.update_or_create(
|
||||||
|
employe=employe,
|
||||||
|
projet=projet,
|
||||||
|
defaults={
|
||||||
|
'date_affectation': form.cleaned_data['date_affectation'],
|
||||||
|
'date_fin_daffectation': date_fin_affectation,
|
||||||
|
'role': form.cleaned_data['role'],
|
||||||
|
'pourcentage_temps_affectation': temps_nouveau,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
messages.success(request, f"L'employé {employe.user.first_name} {employe.user.last_name} a été affecté au projet {projet.nom_projet}.")
|
||||||
|
return redirect('gestion_employe:index')
|
||||||
|
else:
|
||||||
|
messages.error(request, "Erreur : Formulaire non valide.")
|
||||||
|
return redirect('gestion_employe:index')
|
||||||
|
else:
|
||||||
|
return redirect('gestion_employe:index')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def mon_profil(request):
|
||||||
|
"""Vue pour afficher et modifier le profil de l'utilisateur connecté"""
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(user__username=request.user)
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
messages.error(request, "Impossible d'acceder au menu 'Mon profil' car votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'Administrateur.")
|
||||||
|
return redirect("gestion_conges:conge")
|
||||||
|
|
||||||
|
contrats = Contrat.objects.filter(employe=employe, statut='actif').first()
|
||||||
|
projets = Affectation.objects.filter(
|
||||||
|
employe = employe,
|
||||||
|
date_fin_daffectation__gte = timezone.now().date()
|
||||||
|
).select_related('projet')
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
'gestion_employe/monprofil.html',
|
||||||
|
{
|
||||||
|
'employe': employe,
|
||||||
|
'contrats': [{
|
||||||
|
**model_to_dict(contrats),
|
||||||
|
"type_contrat": dict(Contrat.TYPE_CONTRAT).get(contrats.type_contrat),
|
||||||
|
"statut": dict(Contrat.STATUT_CONTRAT).get(contrats.statut),
|
||||||
|
"fichier_contrat": contrats.fichier_contrat.url if contrats.fichier_contrat else "",
|
||||||
|
} if contrats else []],
|
||||||
|
'projets': [
|
||||||
|
{
|
||||||
|
**model_to_dict(a.projet),
|
||||||
|
"date_affectation": a.date_affectation,
|
||||||
|
"date_fin_daffectation": a.date_fin_daffectation,
|
||||||
|
"role": dict(Affectation.ROLE_CHOICES).get(a.role),
|
||||||
|
"pourcentage_temps_affectation": a.pourcentage_temps_affectation
|
||||||
|
} for a in projets
|
||||||
|
],
|
||||||
|
"formation_form": FormationForm(),
|
||||||
|
"expiration_contrat": contrats.nombre_jours_restant <= fonctions_utilitaire.DUREE_FIN_CONTRAT if contrats else False,
|
||||||
|
"contrat_nb_jours_restant": contrats.nombre_jours_restant if contrats else None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def modifier_mot_passe(request):
|
||||||
|
"""Vue pour permettre à un utilisateur de modifier son mot de passe et ses informations de profil"""
|
||||||
|
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):
|
||||||
|
"""Vue pour permettre à un utilisateur de modifier les informations d'un employé"""
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(user__username=request.user)
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
return JsonResponse({"message": "Employé non trouvé."})
|
||||||
|
if request.method == "POST":
|
||||||
|
data = json.loads(request.body)
|
||||||
|
user = User.objects.get(username=request.user)
|
||||||
|
user.last_name = data['nom']
|
||||||
|
user.first_name = data['prenom']
|
||||||
|
user.email = data['email']
|
||||||
|
employe.telephone = data['telephone']
|
||||||
|
employe.adresse = data['adresse']
|
||||||
|
employe.sexe = data['sexe']
|
||||||
|
if request.FILES.get("photo"):
|
||||||
|
employe.photo = request.FILES["photo"]
|
||||||
|
if data['date_naissance']:
|
||||||
|
difference = relativedelta(timezone.now().date(), datetime.strptime(data['date_naissance'], "%Y-%m-%d").date())
|
||||||
|
if difference.years >= 18:
|
||||||
|
employe.date_naissance = data['date_naissance']
|
||||||
|
else:
|
||||||
|
return JsonResponse({"message": "Veuillez entrez une date de naissance correcte."})
|
||||||
|
employe.save()
|
||||||
|
user.save()
|
||||||
|
return JsonResponse({"message": "Profil mis à jour avec succès."})
|
||||||
|
|
||||||
|
def enregistrement_document(request):
|
||||||
|
employe = Employe.objects.get(user=request.user)
|
||||||
|
if request.method == "POST":
|
||||||
|
if request.FILES.get("photo"):employe.photo = request.FILES["photo"]
|
||||||
|
if "cv" in request.FILES:employe.CV = request.FILES["cv"]
|
||||||
|
if "diplome" in request.FILES: employe.diplome = request.FILES["diplome"]
|
||||||
|
if "rib" in request.FILES: employe.rib = request.FILES["rib"]
|
||||||
|
if "casier_judiciaire" in request.FILES:employe.casier_judiciaire = request.FILES["casier_judiciaire"]
|
||||||
|
employe.save()
|
||||||
|
messages.success(request, "Documents enregistrés avec succès.")
|
||||||
|
|
||||||
|
return redirect("gestion_employe:mon-profil")
|
||||||
|
|
||||||
|
def suppression_contrat(request):
|
||||||
|
"""Vue pour permettre à un utilisateur de supprimer un contrat"""
|
||||||
|
id_contrat = json.loads(request.body)['id']
|
||||||
|
try:
|
||||||
|
contrat = Contrat.objects.get(numero_contrat = id_contrat)
|
||||||
|
except Contrat.DoesNotExist:
|
||||||
|
messages.error(request, "Contrat non trouvé.")
|
||||||
|
return JsonResponse({"message": "Contrat non trouvé."}, status=404)
|
||||||
|
|
||||||
|
contrat.delete()
|
||||||
|
return JsonResponse({"message": "Contrat supprimé avec succès."})
|
||||||
|
|
||||||
|
def suppression_affectation(request):
|
||||||
|
"""Vue pour permettre à un utilisateur de supprimer une affectation"""
|
||||||
|
id_affectation = json.loads(request.body)['id']
|
||||||
|
try:
|
||||||
|
affectation = Affectation.objects.get(id=id_affectation)
|
||||||
|
except Affectation.DoesNotExist:
|
||||||
|
return JsonResponse({"message": "Affectation non trouvée."}, status=404)
|
||||||
|
|
||||||
|
affectation.delete()
|
||||||
|
return JsonResponse({"message": "Affectation supprimée avec succès."})
|
||||||
|
|
||||||
|
def creation_contrat(request):
|
||||||
|
"""Vue pour permettre à un utilisateur de créer un contrat pour un employé"""
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(id=request.POST.get('employe_id'))
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
messages.error(request, "Employé non trouvé.")
|
||||||
|
return redirect('employe-index')
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = ContratForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
contrat = form.save(commit=False)
|
||||||
|
contrat.employe = employe
|
||||||
|
contrat.save()
|
||||||
|
messages.success(request, "Contrat créé avec succès.")
|
||||||
|
return redirect('gestion_employe:index')
|
||||||
|
messages.error(request, "Formulaire non valide")
|
||||||
|
else:
|
||||||
|
form = ContratForm(initial={'employe': employe})
|
||||||
|
return render(request, 'gestion_employe/index.html', {'contrat_form': form})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def enregistrer_detail_employe(request):
|
||||||
|
"""Vue pour permettre à un utilisateur de modifier les détails d'un employé via une requête AJAX"""
|
||||||
|
if request.method == "POST":
|
||||||
|
data = json.loads(request.body)
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(id=data['id'])
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
return JsonResponse({"error": "Employé non trouvé."}, status=404)
|
||||||
|
|
||||||
|
employe.fonction = data['fonction']
|
||||||
|
employe.date_embauche = data['date_embauche']
|
||||||
|
employe.matricule = data['matricule']
|
||||||
|
employe.save()
|
||||||
|
return JsonResponse({"message": "Détails de l'employé mis à jour avec succès."})
|
||||||
|
else:
|
||||||
|
return JsonResponse({"message": "Méthode non autorisée."}, status=405)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def liste_employe(request):
|
||||||
|
""" Vue pour retourner la liste de tous les employés """
|
||||||
|
employes = Employe.objects.exclude(user__first_name = '', user__last_name = '')
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for employe in employes:
|
||||||
|
if employe.user.first_name != ' ' and employe.user.last_name != ' ':
|
||||||
|
projets = [
|
||||||
|
", ".join([
|
||||||
|
a.projet.nom_projet for a in Affectation.objects.filter(
|
||||||
|
employe=employe,
|
||||||
|
date_fin_daffectation__gte=timezone.now().date()
|
||||||
|
)
|
||||||
|
])
|
||||||
|
]
|
||||||
|
formations = [
|
||||||
|
{
|
||||||
|
"titre": formation.titre,
|
||||||
|
"organisme": formation.organisme,
|
||||||
|
"description": formation.description,
|
||||||
|
"date_obtention": formation.date_obtention,
|
||||||
|
"date_fin": formation.date_fin,
|
||||||
|
"certificat": formation.certificat.url if formation.certificat else "",
|
||||||
|
} for formation in Formation.objects.filter(employe=employe)
|
||||||
|
]
|
||||||
|
|
||||||
|
contrats = [
|
||||||
|
{
|
||||||
|
"numero_contrat": contrat.numero_contrat,
|
||||||
|
"type_contrat": contrat.type_contrat,
|
||||||
|
"date_debut": contrat.date_debut,
|
||||||
|
"date_fin": contrat.date_fin,
|
||||||
|
"salaire_mensuel": contrat.salaire_mensuel,
|
||||||
|
"statut": contrat.statut,
|
||||||
|
"fichier_contrat": contrat.fichier_contrat.url if contrat.fichier_contrat else "",
|
||||||
|
} for contrat in Contrat.objects.filter(employe=employe, statut='actif')
|
||||||
|
]
|
||||||
|
|
||||||
|
affectations = [
|
||||||
|
{**model_to_dict(affectation), "projet": affectation.projet.nom_projet}
|
||||||
|
for affectation in Affectation.objects.filter(
|
||||||
|
employe=employe,
|
||||||
|
date_fin_daffectation__gte=timezone.now().date()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
data.append(
|
||||||
|
{
|
||||||
|
"id": employe.id,
|
||||||
|
"employe": f"{employe.user.first_name} {employe.user.last_name}",
|
||||||
|
"matricule": employe.matricule,
|
||||||
|
"email": employe.user.email,
|
||||||
|
"formations": formations,
|
||||||
|
"affectations": affectations,
|
||||||
|
"projet": projets,
|
||||||
|
"contrats": contrats,
|
||||||
|
"departement": employe.departement.nom if employe.departement else "",
|
||||||
|
"fonction": employe.fonction,
|
||||||
|
"date_embauche": employe.date_embauche,
|
||||||
|
"adresse": employe.adresse,
|
||||||
|
"telephone": employe.telephone,
|
||||||
|
"sexe": employe.sexe,
|
||||||
|
"CV": employe.CV.url if employe.CV else "",
|
||||||
|
"diplome": employe.diplome.url if employe.diplome else "",
|
||||||
|
"rib": employe.rib.url if employe.rib else "",
|
||||||
|
"photo": employe.photo.url if employe.photo else "",
|
||||||
|
"casier_judiciaire": employe.casier_judiciaire.url if employe.casier_judiciaire else "",
|
||||||
|
"date_naissance": employe.date_naissance,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return JsonResponse({'success': True, 'data': data}, safe=False)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def ajouter_formation(request):
|
||||||
|
"""Vue pour permettre à un employé d'ajouter une formation à son profil"""
|
||||||
|
employe = Employe.objects.get(user__username=request.user)
|
||||||
|
if request.method == "POST":
|
||||||
|
formation = FormationForm(request.POST, request.FILES)
|
||||||
|
if formation.is_valid():
|
||||||
|
ma_formation = formation.save(commit=False)
|
||||||
|
ma_formation.employe = employe
|
||||||
|
ma_formation.save()
|
||||||
|
messages.success(request, "Formation ajoutée avec succès ")
|
||||||
|
return redirect("gestion_employe:mon-profil")
|
||||||
|
messages.error(request, "Formulaire non valide. Veuillez vérifier les informations saisies.")
|
||||||
|
return redirect("gestion_employe:mon-profil")
|
||||||
|
|
||||||
|
def liste_formation(request):
|
||||||
|
formations = Formation.objects.filter(employe__user__username=request.user).order_by("-date_obtention")
|
||||||
|
return JsonResponse([
|
||||||
|
{
|
||||||
|
**model_to_dict(formation),
|
||||||
|
"certificat": formation.certificat.url if formation.certificat else ""
|
||||||
|
}
|
||||||
|
for formation in formations
|
||||||
|
], safe=False)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def modifier_formation(request, id_formation):
|
||||||
|
"""Vue pour permettre à un employé de modifier une formation de son profil"""
|
||||||
|
try:
|
||||||
|
formation = Formation.objects.get(id=id_formation, employes=request.user)
|
||||||
|
except Formation.DoesNotExist:
|
||||||
|
messages.error(request, "Formation non trouvée.")
|
||||||
|
return redirect("mes_formations")
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
formation = FormationForm(request.POST, request.FILES, instance=formation)
|
||||||
|
if formation.is_valid():
|
||||||
|
messages.success(request, "Formation mise à jour ")
|
||||||
|
|
||||||
|
formation.save()
|
||||||
|
messages.error(request, "Formulaire non valide. Veuillez vérifier les informations saisies.")
|
||||||
|
return redirect("mes_formations")
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def supprimer_formation(request, id_formation):
|
||||||
|
"""Vue pour permettre à un employé de supprimer une formation de son profil"""
|
||||||
|
try:
|
||||||
|
formation = Formation.objects.get(id=id_formation, employes=request.user)
|
||||||
|
except Formation.DoesNotExist:
|
||||||
|
messages.error(request, "Formation non trouvée.")
|
||||||
|
return redirect("mes_formations")
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
formation.delete()
|
||||||
|
messages.success(request, "Formation supprimée ")
|
||||||
|
return redirect("mes_formations")
|
||||||
|
|
||||||
|
# @login_required
|
||||||
|
# def creation_departement(request):
|
||||||
|
# """Gère la création d'un nouveau département via un formulaire."""
|
||||||
|
# if request.method == 'POST':
|
||||||
|
# form_departement = DepartementForm(request.POST)
|
||||||
|
# if form_departement.is_valid():
|
||||||
|
# form_departement.save()
|
||||||
|
# messages.success(request, "Département ajouté avec succès.")
|
||||||
|
# else:
|
||||||
|
# messages.error(request, "Erreur lors de l'ajout du département.")
|
||||||
|
# return redirect('parametres-rh')
|
||||||
|
|
||||||
|
# @login_required
|
||||||
|
# def modifier_departement(request, id):
|
||||||
|
# """Gère la modification d'un département existant via un formulaire pré-rempli."""
|
||||||
|
# departement = Departement.objects.get(id=id)
|
||||||
|
# form = DepartementForm(instance=departement)
|
||||||
|
# if request.method == 'POST':
|
||||||
|
# nouveau_nom_departement = request.POST.get('nom')
|
||||||
|
# if nouveau_nom_departement:
|
||||||
|
# departement.nom = nouveau_nom_departement
|
||||||
|
# departement.save()
|
||||||
|
# messages.success(request, "Département modifié avec succès.")
|
||||||
|
# return redirect('parametres-rh')
|
||||||
|
# return render(request, 'gestion_employe/edit_departement.html', {
|
||||||
|
# 'form': form,
|
||||||
|
# 'departement': departement
|
||||||
|
# })
|
||||||
|
|
||||||
|
# @login_required
|
||||||
|
# def supprimer_departement(request, id):
|
||||||
|
# """Gère la suppression d'un département existant."""
|
||||||
|
# if request.method == "POST":
|
||||||
|
# departement = Departement.objects.get(id=id)
|
||||||
|
# departement.delete()
|
||||||
|
# messages.success(request, "Département supprimé avec succès !")
|
||||||
|
# return redirect('parametres-rh')
|
||||||
0
gestion_projet/__init__.py
Normal file
0
gestion_projet/__init__.py
Normal file
6
gestion_projet/admin.py
Normal file
6
gestion_projet/admin.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import DomaineDeRecherche
|
||||||
|
|
||||||
|
@admin.register(DomaineDeRecherche)
|
||||||
|
class DomaineDeRecherche(admin.ModelAdmin):
|
||||||
|
list_display = ('nom',)
|
||||||
176
gestion_projet/forms.py
Normal file
176
gestion_projet/forms.py
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
from django import forms
|
||||||
|
from gestion_projet.models import Projet
|
||||||
|
from .models import (
|
||||||
|
ActiviteProjet,
|
||||||
|
Bailleur,
|
||||||
|
DocumentProjet,
|
||||||
|
FinancementProjet,
|
||||||
|
LivrablesLivres,
|
||||||
|
DomaineDeRecherche
|
||||||
|
)
|
||||||
|
|
||||||
|
class ProjetForm(forms.ModelForm):
|
||||||
|
"""Formulaire de création et de modification d'un projet, avec validation des dates et personnalisation des champs."""
|
||||||
|
class Meta:
|
||||||
|
model = Projet
|
||||||
|
fields = (
|
||||||
|
'id_projet',
|
||||||
|
'nom_projet',
|
||||||
|
'date_debut',
|
||||||
|
'date_fin',
|
||||||
|
'numero_convention',
|
||||||
|
'domaine_recherche',
|
||||||
|
'type_projet',
|
||||||
|
'budget',
|
||||||
|
'budget_RH',
|
||||||
|
'description'
|
||||||
|
)
|
||||||
|
# domaine_recherche = forms.ModelMultipleChoiceField(
|
||||||
|
# queryset=DomaineDeRecherche.objects.all(),
|
||||||
|
# to_field_name="nom",
|
||||||
|
# required=False
|
||||||
|
# )
|
||||||
|
widgets = {
|
||||||
|
'id_projet': forms.TextInput(attrs={'class': "form-control"}),
|
||||||
|
'nom_projet': forms.TextInput(attrs={'class': "form-control"}),
|
||||||
|
'numero_convention': forms.TextInput(attrs={'class': "form-control"}),
|
||||||
|
'domaine_recherche': forms.SelectMultiple(attrs={'class': "form-control"}),
|
||||||
|
'type_projet': forms.Select(attrs={'class': "form-select"}),
|
||||||
|
'budget': forms.NumberInput(attrs={'class': "form-control"}),
|
||||||
|
'budget_RH': forms.NumberInput(attrs={'class': "form-control"}),
|
||||||
|
'description': forms.Textarea(attrs={'class': "form-control"}),
|
||||||
|
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': "form-control"}),
|
||||||
|
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': "form-control"}),
|
||||||
|
}
|
||||||
|
|
||||||
|
class BailleurForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Formulaire de création et de modification d'un bailleur,
|
||||||
|
avec validation des champs et personnalisation des labels.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Bailleur
|
||||||
|
fields = ('nom_organisme', 'contact', 'email', 'pays')
|
||||||
|
widgets = {
|
||||||
|
'nom_organisme':forms.TextInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
}),
|
||||||
|
'contact':forms.TextInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
}),
|
||||||
|
'email':forms.TextInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
}),
|
||||||
|
'pays':forms.TextInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
class DocumentProjetForm(forms.ModelForm):
|
||||||
|
"""
|
||||||
|
Formulaire pour ajouter ou modifier un document associé à un projet,
|
||||||
|
avec validation des champs et personnalisation des labels.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = DocumentProjet
|
||||||
|
fields = [
|
||||||
|
'nom_document',
|
||||||
|
'numero',
|
||||||
|
'date_validite',
|
||||||
|
'fichier',
|
||||||
|
'description'
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
'nom_document': forms.Select(attrs={'class': 'form-select'}),
|
||||||
|
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||||
|
'numero': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'date_validite': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||||
|
'fichier': forms.ClearableFileInput(attrs={'class': 'form-control'}),
|
||||||
|
}
|
||||||
|
|
||||||
|
class ActiviteProjetForm(forms.ModelForm):
|
||||||
|
"""Formulaire pour créer ou modifier une activité de projet, avec validation des champs et personnalisation des widgets."""
|
||||||
|
class Meta:
|
||||||
|
model = ActiviteProjet
|
||||||
|
fields = (
|
||||||
|
'titre',
|
||||||
|
'date_debut',
|
||||||
|
'date_fin',
|
||||||
|
'besoin_ressource_materielle',
|
||||||
|
'budget_prevu',
|
||||||
|
'description',
|
||||||
|
)
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'titre':forms.TextInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
'placeholder':'Titre de l’activité'
|
||||||
|
}),
|
||||||
|
'description':forms.Textarea(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
'rows':3,
|
||||||
|
'placeholder':'Description de l’activité'
|
||||||
|
}),
|
||||||
|
'date_debut':forms.DateInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
'type':'date'
|
||||||
|
}),
|
||||||
|
'date_fin':forms.DateInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
'type':'date'
|
||||||
|
}),
|
||||||
|
'besoin_ressource_materielle': forms.Textarea(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
'rows':3,
|
||||||
|
'placeholder':'Besoin de ressources matérielles'
|
||||||
|
}),
|
||||||
|
'budget_prevu': forms.NumberInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
'placeholder':'Budget prévu'
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
class FinancementProjetFrom(forms.ModelForm):
|
||||||
|
"""Formulaire pour créer ou modifier le financement relatif à un projet."""
|
||||||
|
class Meta:
|
||||||
|
model = FinancementProjet
|
||||||
|
fields = (
|
||||||
|
'projet',
|
||||||
|
'bailleur',
|
||||||
|
'pourcentage',
|
||||||
|
)
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'projet':forms.Select(attrs={
|
||||||
|
'class':'form-select',
|
||||||
|
}),
|
||||||
|
'bailleur':forms.Select(attrs={
|
||||||
|
'class':'form-select',
|
||||||
|
}),
|
||||||
|
'pourcentage':forms.NumberInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
class LivrablesLivresForm(forms.ModelForm):
|
||||||
|
"""Formulaire pour créer ou modifier un livrable livré dans le cadre d'une activité de projet."""
|
||||||
|
class Meta:
|
||||||
|
model = LivrablesLivres
|
||||||
|
fields = (
|
||||||
|
'activite',
|
||||||
|
'nom',
|
||||||
|
'fichier',
|
||||||
|
)
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'activite': forms.Select(attrs={
|
||||||
|
'class':'form-select',
|
||||||
|
}),
|
||||||
|
'nom': forms.TextInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
'placeholder':'Nom du livrable'
|
||||||
|
}),
|
||||||
|
'fichier': forms.ClearableFileInput(attrs={
|
||||||
|
'class':'form-control',
|
||||||
|
}),
|
||||||
|
}
|
||||||
107
gestion_projet/migrations/0001_initial.py
Normal file
107
gestion_projet/migrations/0001_initial.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# Generated by Django 5.2.13 on 2026-04-17 12:03
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ActiviteProjet',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('titre', models.CharField(max_length=200, verbose_name="Titre de l'activité")),
|
||||||
|
('description', models.TextField(blank=True, null=True, verbose_name="Description de l'activité")),
|
||||||
|
('date_debut', models.DateField(verbose_name='Date de début')),
|
||||||
|
('date_fin', models.DateField(verbose_name='Date de fin')),
|
||||||
|
('annuler', models.BooleanField(default=False, verbose_name="Annuler l'activité")),
|
||||||
|
('motif_annulation', models.TextField(blank=True, null=True, verbose_name="Motif d'annulation")),
|
||||||
|
('motif_changement_budget', models.TextField(blank=True, null=True, verbose_name='Motif de changement de budget')),
|
||||||
|
('budget_prevu', models.DecimalField(decimal_places=2, default=0, max_digits=15, verbose_name='Budget prévu')),
|
||||||
|
('budget_depense', models.DecimalField(decimal_places=2, default=0, max_digits=15, verbose_name='Budget dépensé')),
|
||||||
|
('besoin_ressource_materielle', models.TextField(verbose_name='Besoin de ressources matérielles')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Bailleur',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nom_organisme', models.CharField(max_length=200, unique=True)),
|
||||||
|
('contact', models.CharField(blank=True, max_length=100, null=True)),
|
||||||
|
('email', models.EmailField(blank=True, max_length=254, null=True)),
|
||||||
|
('pays', models.CharField(blank=True, max_length=100, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DomaineDeRecherche',
|
||||||
|
fields=[
|
||||||
|
('nom', models.CharField(choices=[('sciences_sociales', 'Sciences sociales'), ('naturelles', 'Naturelles'), ('humaines', 'Humaines'), ('veterinaires', 'Vétérinaires')], max_length=100, primary_key=True, serialize=False, verbose_name='Domaine de recherche')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Domaine de recherche',
|
||||||
|
'verbose_name_plural': 'Domaines de recherche',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LivrablesLivres',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('nom', models.CharField(max_length=255, verbose_name='Nom du livrable')),
|
||||||
|
('fichier', models.FileField(blank=True, null=True, upload_to='fichier_livrables/')),
|
||||||
|
('activite', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.activiteprojet')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Projet',
|
||||||
|
fields=[
|
||||||
|
('id_projet', models.CharField(blank=True, max_length=100, primary_key=True, serialize=False, unique=True, verbose_name='ID du projet')),
|
||||||
|
('nom_projet', models.CharField(max_length=200, verbose_name='Nom du projet')),
|
||||||
|
('date_debut', models.DateField(verbose_name='Date de début')),
|
||||||
|
('date_fin', models.DateField(verbose_name='Date de fin')),
|
||||||
|
('numero_convention', models.CharField(max_length=100, verbose_name='Numéro de convention')),
|
||||||
|
('description', models.TextField(verbose_name='Description')),
|
||||||
|
('type_projet', models.CharField(choices=[('laboratoire', 'Laboratoire'), ('épidémiologie', 'Épidémiologie'), ('sciences sociales', 'Sciences sociales'), ('cliniques', 'Cliniques'), ('autre', 'Autre')], default='épidémiologie', max_length=100, verbose_name='Type de projet')),
|
||||||
|
('budget', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Budget')),
|
||||||
|
('budget_RH', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Budget RH')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('bailleur', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='gestion_projet.bailleur', verbose_name='Bailleur de fonds')),
|
||||||
|
('domaine_recherche', models.ManyToManyField(to='gestion_projet.domainederecherche')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DocumentProjet',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('date_ajout', models.DateTimeField(auto_now_add=True, verbose_name="Date d'ajout")),
|
||||||
|
('nom_document', models.CharField(choices=[('protocole', 'Protocole d’étude'), ('ethique', "Approbation du comité d'éthique"), ('autorisation', 'Autorisation (DNLP)'), ('rapport_technique', 'Rapport technique'), ('rapport_financier', 'Rapport financier'), ('rapport_avancement', "Rapport d'avancement"), ('convention', 'Convention'), ('rapport_final', 'Rapport final'), ('autre', 'Autre')], max_length=100, verbose_name='Type de document')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='Description')),
|
||||||
|
('numero', models.CharField(blank=True, max_length=100, null=True, verbose_name='Numéro du document')),
|
||||||
|
('date_validite', models.DateField(blank=True, null=True, verbose_name='Date de validité')),
|
||||||
|
('fichier', models.FileField(upload_to='documents_projets/', verbose_name='Fichier à télécharger')),
|
||||||
|
('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='gestion_projet.projet', verbose_name='Projet')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='activiteprojet',
|
||||||
|
name='projet',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet', verbose_name='Projet'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='FinancementProjet',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('pourcentage', models.DecimalField(decimal_places=2, max_digits=5)),
|
||||||
|
('bailleur', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.bailleur')),
|
||||||
|
('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('projet', 'bailleur')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
gestion_projet/migrations/__init__.py
Normal file
0
gestion_projet/migrations/__init__.py
Normal file
309
gestion_projet/models.py
Normal file
309
gestion_projet/models.py
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
from django.db import models
|
||||||
|
from datetime import date
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
class Bailleur(models.Model):
|
||||||
|
"""Modèle représentant un bailleur de fonds pour les projets de recherche."""
|
||||||
|
nom_organisme = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
contact = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
email = models.EmailField(
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
pays = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.nom_organisme
|
||||||
|
|
||||||
|
class DomaineDeRecherche(models.Model):
|
||||||
|
"""Modèle représentant les domaines de recherche"""
|
||||||
|
|
||||||
|
DOMAINE_RECHERCHE = [
|
||||||
|
('sciences_sociales', 'Sciences sociales'),
|
||||||
|
('naturelles', 'Naturelles'),
|
||||||
|
('humaines', 'Humaines'),
|
||||||
|
('veterinaires', 'Vétérinaires')
|
||||||
|
]
|
||||||
|
|
||||||
|
nom = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
verbose_name="Domaine de recherche",
|
||||||
|
choices=DOMAINE_RECHERCHE,
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Domaine de recherche'
|
||||||
|
verbose_name_plural = 'Domaines de recherche'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.nom
|
||||||
|
|
||||||
|
class Projet(models.Model):
|
||||||
|
"""Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé."""
|
||||||
|
TYPE_PROJET = [
|
||||||
|
('laboratoire', 'Laboratoire'),
|
||||||
|
('épidémiologie', 'Épidémiologie'),
|
||||||
|
('sciences sociales', 'Sciences sociales'),
|
||||||
|
('cliniques', 'Cliniques'),
|
||||||
|
('autre', 'Autre'),
|
||||||
|
]
|
||||||
|
id_projet = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
unique=True,
|
||||||
|
primary_key=True,
|
||||||
|
verbose_name="ID du projet"
|
||||||
|
)
|
||||||
|
nom_projet = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
verbose_name="Nom du projet"
|
||||||
|
)
|
||||||
|
date_debut = models.DateField(
|
||||||
|
verbose_name="Date de début"
|
||||||
|
)
|
||||||
|
date_fin = models.DateField(
|
||||||
|
verbose_name="Date de fin"
|
||||||
|
)
|
||||||
|
numero_convention = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
verbose_name="Numéro de convention"
|
||||||
|
)
|
||||||
|
description = models.TextField(
|
||||||
|
verbose_name="Description"
|
||||||
|
)
|
||||||
|
type_projet = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
choices=TYPE_PROJET,
|
||||||
|
default='épidémiologie',
|
||||||
|
verbose_name="Type de projet"
|
||||||
|
)
|
||||||
|
domaine_recherche = models.ManyToManyField(DomaineDeRecherche)
|
||||||
|
budget=models.DecimalField(
|
||||||
|
max_digits=12,
|
||||||
|
decimal_places=2,
|
||||||
|
verbose_name="Budget"
|
||||||
|
)
|
||||||
|
budget_RH = models.DecimalField(
|
||||||
|
max_digits=12,
|
||||||
|
decimal_places=2,
|
||||||
|
verbose_name="Budget RH"
|
||||||
|
)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
bailleur = models.ForeignKey(
|
||||||
|
Bailleur,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Bailleur de fonds"
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def statut(self):
|
||||||
|
if self.date_fin < date.today():
|
||||||
|
return "Terminé"
|
||||||
|
return "En cours"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def avancement(self):
|
||||||
|
aujourd_hui = date.today()
|
||||||
|
if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin):
|
||||||
|
duree_projet = (self.date_fin - self.date_debut).days
|
||||||
|
temps_ecoule = (aujourd_hui - self.date_debut).days
|
||||||
|
if duree_projet > 0:
|
||||||
|
return round((temps_ecoule / duree_projet) * 100, 2)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.nom_projet}"
|
||||||
|
|
||||||
|
class FinancementProjet(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle représentant le financement d'un projet par un bailleur,
|
||||||
|
avec le pourcentage de contribution.
|
||||||
|
"""
|
||||||
|
projet = models.ForeignKey(
|
||||||
|
Projet,
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
bailleur = models.ForeignKey(
|
||||||
|
Bailleur,
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
pourcentage = models.DecimalField(
|
||||||
|
max_digits = 5,
|
||||||
|
decimal_places=2
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('projet', 'bailleur')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.bailleur.nom} - {self.projet.nom_projet} ({self.pourcentage}%)"
|
||||||
|
|
||||||
|
class DocumentProjet(models.Model):
|
||||||
|
"""Modèle représentant un document associé à un projet, avec des métadonnées et un fichier attaché."""
|
||||||
|
NOM_DOCUMENT_CHOICES = [
|
||||||
|
('protocole', 'Protocole d’étude'),
|
||||||
|
('ethique', "Approbation du comité d'éthique"),
|
||||||
|
('autorisation', 'Autorisation (DNLP)'),
|
||||||
|
('rapport_technique', 'Rapport technique'),
|
||||||
|
('rapport_financier', 'Rapport financier'),
|
||||||
|
('rapport_avancement', "Rapport d'avancement"),
|
||||||
|
('convention', 'Convention'),
|
||||||
|
('rapport_final', 'Rapport final'),
|
||||||
|
('autre', 'Autre'),
|
||||||
|
]
|
||||||
|
|
||||||
|
projet = models.ForeignKey(
|
||||||
|
Projet,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='documents',
|
||||||
|
verbose_name="Projet"
|
||||||
|
)
|
||||||
|
date_ajout = models.DateTimeField(
|
||||||
|
auto_now_add=True,
|
||||||
|
verbose_name="Date d'ajout"
|
||||||
|
)
|
||||||
|
nom_document = models.CharField(
|
||||||
|
max_length = 100,
|
||||||
|
choices = NOM_DOCUMENT_CHOICES,
|
||||||
|
verbose_name="Type de document"
|
||||||
|
)
|
||||||
|
description = models.TextField(
|
||||||
|
blank = True,
|
||||||
|
verbose_name = "Description"
|
||||||
|
)
|
||||||
|
numero = models.CharField(
|
||||||
|
max_length = 100,
|
||||||
|
blank = True,
|
||||||
|
null = True,
|
||||||
|
verbose_name = "Numéro du document"
|
||||||
|
)
|
||||||
|
date_validite = models.DateField(
|
||||||
|
blank = True,
|
||||||
|
null = True,
|
||||||
|
verbose_name = "Date de validité"
|
||||||
|
)
|
||||||
|
fichier = models.FileField(
|
||||||
|
upload_to = 'documents_projets/',
|
||||||
|
verbose_name = "Fichier à télécharger"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.nom_document} ({self.projet})"
|
||||||
|
|
||||||
|
class ActiviteProjet(models.Model):
|
||||||
|
"""Modèle représentant le planning d'un projet, avec des activités associées et un statut."""
|
||||||
|
projet = models.ForeignKey(
|
||||||
|
Projet,
|
||||||
|
on_delete = models.CASCADE,
|
||||||
|
verbose_name = "Projet"
|
||||||
|
)
|
||||||
|
titre = models.CharField(
|
||||||
|
max_length = 200,
|
||||||
|
verbose_name = "Titre de l'activité"
|
||||||
|
)
|
||||||
|
description = models.TextField(
|
||||||
|
blank = True,
|
||||||
|
null = True,
|
||||||
|
verbose_name = "Description de l'activité"
|
||||||
|
)
|
||||||
|
date_debut = models.DateField(verbose_name="Date de début")
|
||||||
|
date_fin = models.DateField(verbose_name="Date de fin")
|
||||||
|
annuler = models.BooleanField(
|
||||||
|
default = False,
|
||||||
|
verbose_name = "Annuler l'activité"
|
||||||
|
)
|
||||||
|
motif_annulation = models.TextField(
|
||||||
|
blank = True,
|
||||||
|
null = True,
|
||||||
|
verbose_name = "Motif d'annulation"
|
||||||
|
)
|
||||||
|
|
||||||
|
motif_changement_budget = models.TextField(
|
||||||
|
blank = True,
|
||||||
|
null = True,
|
||||||
|
verbose_name = "Motif de changement de budget"
|
||||||
|
)
|
||||||
|
budget_prevu = models.DecimalField(
|
||||||
|
max_digits = 15,
|
||||||
|
decimal_places = 2,
|
||||||
|
default = 0,
|
||||||
|
verbose_name = "Budget prévu"
|
||||||
|
)
|
||||||
|
budget_depense = models.DecimalField(
|
||||||
|
max_digits = 15,
|
||||||
|
decimal_places = 2,
|
||||||
|
default = 0,
|
||||||
|
verbose_name = "Budget dépensé"
|
||||||
|
)
|
||||||
|
besoin_ressource_materielle = models.TextField(
|
||||||
|
verbose_name="Besoin de ressources matérielles"
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def statut(self):
|
||||||
|
today = timezone.now().date()
|
||||||
|
if not self.annuler:
|
||||||
|
if self.date_fin < today:
|
||||||
|
return 'Terminé'
|
||||||
|
elif self.date_debut > today:
|
||||||
|
return 'À venir'
|
||||||
|
else:
|
||||||
|
return 'En cours'
|
||||||
|
else:
|
||||||
|
return 'Annulé'
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.titre} ({self.projet.nom_projet})"
|
||||||
|
|
||||||
|
# class LivrableAttendu(models.Model):
|
||||||
|
# """
|
||||||
|
# Modèle représentant un livrable attendu pour une activité de projet,
|
||||||
|
# avec des critères de validation.
|
||||||
|
# """
|
||||||
|
# activite = models.ForeignKey(
|
||||||
|
# ActiviteProjet,
|
||||||
|
# on_delete = models.CASCADE,
|
||||||
|
# related_name = "livrables_attendus"
|
||||||
|
# )
|
||||||
|
# nom = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
# def __str__(self):
|
||||||
|
# return f"{self.nom} (Activité: {self.activite.titre})"
|
||||||
|
|
||||||
|
class LivrablesLivres(models.Model):
|
||||||
|
"""Modèle représentant un livrable livré pour une activité de projet."""
|
||||||
|
activite = models.ForeignKey(
|
||||||
|
ActiviteProjet,
|
||||||
|
on_delete = models.CASCADE
|
||||||
|
)
|
||||||
|
# nom = models.ForeignKey(
|
||||||
|
# LivrableAttendu,
|
||||||
|
# on_delete = models.CASCADE
|
||||||
|
# )
|
||||||
|
nom = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name="Nom du livrable"
|
||||||
|
)
|
||||||
|
|
||||||
|
fichier = models.FileField(
|
||||||
|
upload_to = 'fichier_livrables/',
|
||||||
|
blank = True,
|
||||||
|
null = True
|
||||||
|
)
|
||||||
|
def __str__(self):
|
||||||
|
return self.nom
|
||||||
19
gestion_projet/static/gestion_projet/js/creation_projet.js
Normal file
19
gestion_projet/static/gestion_projet/js/creation_projet.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const boutonEnregistrerProjet = $("btnEnregistrerProjet");
|
||||||
|
|
||||||
|
boutonEnregistrerProjet.addEventListener("click", function() {
|
||||||
|
const formulaire = $("formCreationProjet");
|
||||||
|
const formData = new FormData(formulaire);
|
||||||
|
fetch(formulaire.action, {
|
||||||
|
method: "POST",
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
"X-CSRFToken": formData.get("csrfmiddlewaretoken")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
window.location.reload();
|
||||||
|
alert("Projet enregistré avec succès !");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
|
||||||
|
|
||||||
|
btnEnregistrerBailleur.addEventListener('click', function() {
|
||||||
|
const form = document.getElementById('formBailleur');
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
fetch(form.action, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': formData.get('csrfmiddlewaretoken')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert('Bailleur enregistré avec succès !');
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Ce bailleur existe déjà dans la base de données.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
const btn_enregistrer_financement = document.getElementById('btn_enregistrer_financement');
|
||||||
|
|
||||||
|
btn_enregistrer_financement.addEventListener('click', function() {
|
||||||
|
const form = document.getElementById('form_financement');
|
||||||
|
const formData = new FormData(form);
|
||||||
|
fetch(form.action, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': formData.get('csrfmiddlewaretoken')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert(data.message);
|
||||||
|
window.location.reload();
|
||||||
|
}else {
|
||||||
|
alert(data.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
64
gestion_projet/static/gestion_projet/js/index.js
Normal file
64
gestion_projet/static/gestion_projet/js/index.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
const $ = (element) => document.getElementById(element)
|
||||||
|
const url_liste_projet = $("tableau-liste-projet").dataset.url;
|
||||||
|
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
|
||||||
|
fitColumns: true,
|
||||||
|
responsiveLayout : true,
|
||||||
|
columns: [
|
||||||
|
{title: "Projet", field: "nom_projet"},
|
||||||
|
{title: "Source de financement", field: "source_financement"},
|
||||||
|
{title: "Budget Total", field: "budget"},
|
||||||
|
{title: "Budget RH", field: "budget_RH"},
|
||||||
|
{title: "Avancement", field: "avancement", formatter: "progress"},
|
||||||
|
{title: "Statut", field: "statut"},
|
||||||
|
],
|
||||||
|
ajaxURL: url_liste_projet
|
||||||
|
})
|
||||||
|
|
||||||
|
const employes_affectes_projet = new Tabulator("#employes_affectes_projet", {
|
||||||
|
columns: [
|
||||||
|
{title: "Employé", field: "employe"},
|
||||||
|
{title: "Pourcentage d'affectation", field: "pourcentage_affectation"},
|
||||||
|
],
|
||||||
|
placeholder: "Aucun employé affecté pour ce projet",
|
||||||
|
})
|
||||||
|
|
||||||
|
const bailleurs_projet = new Tabulator("#bailleurs_projet", {
|
||||||
|
columns: [
|
||||||
|
{title: "Bailleur", field: "bailleur"},
|
||||||
|
{title: "Pourcentage de financement", field: "pourcentage_financement"},
|
||||||
|
],
|
||||||
|
placeholder: "Aucun bailleur attribué pour ce projet",
|
||||||
|
})
|
||||||
|
|
||||||
|
tableau_liste_projet.on("rowClick", function (row, rowData) {
|
||||||
|
const data = rowData.getData();
|
||||||
|
const modal = new bootstrap.Modal($("modalDetailProjet"));
|
||||||
|
|
||||||
|
$("detail_id_projet").value = data.id_projet;
|
||||||
|
$("detail_nom_projet").value = data.nom_projet;
|
||||||
|
$("detail_date_debut").value = data.date_debut;
|
||||||
|
$("detail_date_fin").value = data.date_fin;
|
||||||
|
$("detail_numero_convention").value = data.numero_convention;
|
||||||
|
$("detail_type_projet").value = data.type_projet;
|
||||||
|
Array.from($("detail_domaine_recherche").options).forEach(option => {
|
||||||
|
if (data.domaine_recherche.includes(option.value)) {
|
||||||
|
option.selected = true;
|
||||||
|
} else {
|
||||||
|
option.selected = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("detail_budget").value = data.budget;
|
||||||
|
$("detail_budget_rh").value = data.budget_RH;
|
||||||
|
$("detail_description").value = data.description;
|
||||||
|
$("detail_statut").value = data.statut;
|
||||||
|
|
||||||
|
employes_affectes_projet.setData(`projet/liste-employes-par-projet/${$("detail_id_projet").value}`);
|
||||||
|
bailleurs_projet.setData(`projet/bailleurs/${data.id_projet}/`);
|
||||||
|
|
||||||
|
modal.show();
|
||||||
|
})
|
||||||
|
|
||||||
|
// $('detail-projet-form').addEventListener('submit', (e) => {
|
||||||
|
// e.preventDefault();
|
||||||
|
// new FormData($("detail-projet-form"));
|
||||||
|
// })
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
const urlListeDocument = document.getElementById('listeDocuments').dataset.urllistedocument;
|
||||||
|
|
||||||
|
const table_liste_documents = new Tabulator(document.getElementById('listeDocuments'), {
|
||||||
|
layout: "fitColumns",
|
||||||
|
placeholder: "Aucun document trouvé",
|
||||||
|
columns: [
|
||||||
|
{ title: "Nom du Document", field: "nom_document" },
|
||||||
|
{ title: "Numéro", field: "numero" },
|
||||||
|
{ title: "Date de Validité", field: "date_validite", formatter: "datetime", formatterParams: {
|
||||||
|
inputFormat: "yyyy-MM-dd",
|
||||||
|
outputFormat: "dd/MM/yyyy"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ title: "Lien vers le Document", field: "lien_document", formatter:"link", formatterParams:{
|
||||||
|
target:"_blank",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ajaxURL: urlListeDocument,
|
||||||
|
});
|
||||||
59
gestion_projet/static/gestion_projet/js/suivi-activites.js
Normal file
59
gestion_projet/static/gestion_projet/js/suivi-activites.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
const $ = (element) => document.getElementById(element)
|
||||||
|
|
||||||
|
const url_liste_activite = $("tableau-liste-activite").dataset.urllisteactivite
|
||||||
|
const tableau_liste_activite = new Tabulator("#tableau-liste-activite", {
|
||||||
|
columns: [
|
||||||
|
{title: "Activité", field: "titre"},
|
||||||
|
{title: "Date début", field: "date_debut"},
|
||||||
|
{title: "Date fin", field: "date_fin"},
|
||||||
|
{title: "Budget prévu", field: "budget_prevu"},
|
||||||
|
{title: "Budget dépensé", field: "budget_depense"},
|
||||||
|
{title: "Motif de changement de budget", field: "motif_changement_budget"},
|
||||||
|
{title: "Statut", field: "statut"},
|
||||||
|
],
|
||||||
|
ajaxURL: url_liste_activite,
|
||||||
|
})
|
||||||
|
tableau_liste_activite.on("rowClick", function (row, rowData) {
|
||||||
|
const data = rowData.getData();
|
||||||
|
$("idDetailActivite").value = data.id;
|
||||||
|
$("titreDetailActivite").value = data.titre;
|
||||||
|
$("descriptionDetailActivite").value = data.description;
|
||||||
|
$("date_debutDetailActivite").value = data.date_debut;
|
||||||
|
$("date_finDetailActivite").value = data.date_fin;
|
||||||
|
$("statutDetailActivite").value = data.statut;
|
||||||
|
$("budget_prevuDetailActivite").value = data.budget_prevu;
|
||||||
|
$("besoin_ressources_materiellesDetailActivite").value = data.besoin_ressource_materielle;
|
||||||
|
const modal = new bootstrap.Modal($("modalDetailActivite"));
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
fetch(`liste-des-livrables/${data.id}/`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(livrables => {
|
||||||
|
tableau_liste_livrable.setData(livrables);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const tableau_liste_livrable = new Tabulator("#listeLivrables", {
|
||||||
|
columns: [
|
||||||
|
{title: "Livrable", field: "titre"},
|
||||||
|
{title: "Lien du livrable", field: "lien", formatter: "link", formatterParams: {blank: true}},
|
||||||
|
],
|
||||||
|
placeholder: "Aucun livrable trouvé",
|
||||||
|
})
|
||||||
|
|
||||||
|
$("btnMiseAJourDepense").addEventListener("click", function() {
|
||||||
|
const modal = new bootstrap.Modal($("modalDepenseActivite"));
|
||||||
|
bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
|
||||||
|
const idActivite = $("idDetailActivite").value;
|
||||||
|
const budgetPrevu = $("budget_prevuDetailActivite").value;
|
||||||
|
|
||||||
|
$("id_activite_depense").value = idActivite;
|
||||||
|
$("budget_prevu").value = budgetPrevu;
|
||||||
|
modal.show();
|
||||||
|
})
|
||||||
|
|
||||||
|
$("btnAnnulerActivite").addEventListener("click", function(event) {
|
||||||
|
new bootstrap.Modal($("modalAnnulerActivite")).show();
|
||||||
|
$("id_activite_annulation").value = $("idDetailActivite").value;
|
||||||
|
bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
|
||||||
|
})
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block 'titre_page' %} Gestion des projets {% endblock %}
|
||||||
|
{% block 'contenu' %}
|
||||||
|
<button class="btn btn-primary mb-3">
|
||||||
|
<i class="bi bi-caret-left-fill"></i> Retour
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<div class="row d-flex justify-content-center">
|
||||||
|
<div class="col-8 card bordered rounded py-4 px-2 ">
|
||||||
|
<h5>Ajout du financement au projet (Nom du projet ici)</h5>
|
||||||
|
<hr>
|
||||||
|
<form method="POST" action="{% url 'gestion_projet:creation-projet' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ formulaire_creation_projet.as_p }}
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="submit" class="btn btn-danger">Annuler</button>
|
||||||
|
<a href="{% url 'gestion_projet:ajouter_financement' %}" class="btn btn-primary ms-2">Enregistrer et ajouter un financement</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'modal' %}
|
||||||
|
{% include "gestion_projet/parts/modalAjoutProjet.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'js' %}
|
||||||
|
<script type="text/javascript" src="{% static 'gestion_projet/js/index.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
32
gestion_projet/templates/gestion_projet/creation_projet.html
Normal file
32
gestion_projet/templates/gestion_projet/creation_projet.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block 'titre_page' %} Gestion des projets {% endblock %}
|
||||||
|
{% block 'contenu' %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% comment %} <h3>Enregistrement d'un nouveau projet</h3> {% endcomment %}
|
||||||
|
<div class="row d-flex justify-content-center">
|
||||||
|
<div class="col-8 card bordered rounded py-4 px-2 ">
|
||||||
|
<h5>Enregistrement d'un nouveau projet</h5>
|
||||||
|
<hr>
|
||||||
|
<form method="POST" action="{% url 'gestion_projet:creation-projet' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ formulaire_creation_projet.as_p }}
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="submit" class="btn btn-danger">Annuler</button>
|
||||||
|
<button class="btn btn-primary ms-2">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'modal' %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'js' %}
|
||||||
|
{% endblock %}
|
||||||
61
gestion_projet/templates/gestion_projet/index.html
Normal file
61
gestion_projet/templates/gestion_projet/index.html
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% load tags_personnaliser %}
|
||||||
|
{% block 'titre_page' %} Gestion des projets {% endblock %}
|
||||||
|
{% block 'contenu' %}
|
||||||
|
<h3>Gestion des projets</h3>
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<div class="row">
|
||||||
|
<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>
|
||||||
|
<h3>{{projet_en_cours}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-2">
|
||||||
|
<span class="fs-5">Budget Total (GNF)</span>
|
||||||
|
<h3>{{budget_total}}</h3>
|
||||||
|
</div>
|
||||||
|
<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">Personnel sous projet</span>
|
||||||
|
<h3>{{nombre_personnel}}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h5 class="mb-4" >La liste des projets </h5>
|
||||||
|
{% if user|has_group:"ressource_humaine" %}
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalProjet">
|
||||||
|
<i class="bi bi-plus-circle"></i> Ajouter un projet
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-info" data-bs-toggle="modal" data-bs-target="#modalBailleur">
|
||||||
|
<i class="bi bi-person"></i> Ajouter un bailleur
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div id="tableau-liste-projet" data-url="{% url 'gestion_projet:liste-projet' %}"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'modal' %}
|
||||||
|
{% include "gestion_projet/parts/modalAjoutProjet.html" %}
|
||||||
|
{% include "gestion_projet/parts/modalFinancement.html" %}
|
||||||
|
{% include "gestion_projet/parts/creation_bailleur.html" %}
|
||||||
|
{% include "gestion_projet/parts/modalDetailProjet.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'js' %}
|
||||||
|
<script type="text/javascript" src="{% static 'gestion_projet/js/index.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'gestion_projet/js/creation_projet.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'gestion_projet/js/enregistrement_bailleur.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'gestion_projet/js/enregistrement_financement.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="modal fade" id="modalBailleur" tabindex="-1" aria-labelledby="modalBailleurLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="modalBailleurLabel">Ajouter un Bailleur</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<form id="formBailleur" method="POST" action="{% url 'gestion_projet:creation-bailleur' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form_ajout_bailleur.as_p }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" id="btnEnregistrerBailleur" class="btn btn-success"><i class="bi bi-save me-1"></i> Enregistrer</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="modal fade" id="modalListeDocument" tabindex="-1" aria-labelledby="modalListeDocumentLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title fw-bold" id="modalListeDocumentLabel">Liste des Documents</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<div id="listeDocuments" data-urllistedocument="{% url 'gestion_projet:liste-documents-projet' %}"></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="modal fade" id="modalAjoutActivite" tabindex="-1" aria-labelledby="modalAjoutActiviteLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="modalAjoutActiviteLabel">Ajouter une Activité</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="{% url 'gestion_projet:ajouter-activite' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-body">
|
||||||
|
{{ form_ajout_activite.as_p }}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Ajouter</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="modal fade" id="modalAjoutDocument" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Ajouter un Document</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="{% url 'gestion_projet:ajouter-document' %}" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-body">
|
||||||
|
{{ form_ajout_document.as_p }}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Ajouter</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="modal fade" id="modalAjouterLivrable" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Ajouter un livrable - (Nom du livrable)</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form method="post" action="{% url 'gestion_projet:ajouter-livrable' %}" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form_ajout_livrable.as_p }}
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button type="submit" class="btn btn-success">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<!-- Modale Projet -->
|
||||||
|
<div class="modal fade" id="modalProjet" tabindex="-1" aria-labelledby="modalProjetLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title" id="modalProjetLabel">Ajouter un projet</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-3">
|
||||||
|
<form method="POST" id="formCreationProjet" action="{% url 'gestion_projet:creation-projet' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ formulaire_creation_projet.as_p }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" id="btnEnregistrerProjet" class="btn btn-success">Enregistrer</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="modal fade" id="modalAnnulerActivite" tabindex="-1" aria-labelledby="modalAnnulerActiviteLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title fw-bold" id="modalAnnulerActiviteLabel">Annuler l'activité</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body p-4">
|
||||||
|
<form method="POST" action="{% url 'gestion_projet:annuler-activite' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="id_activite" id="id_activite_annulation">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="motif_annulation" class="form-label">Motif d'annulation</label>
|
||||||
|
<textarea class="form-control" name="motif_annulation" id="motif_annulation" rows="4" required></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button type="submit" class="btn btn-danger">
|
||||||
|
<i class="bi bi-x-circle"></i> Confirmer l'annulation
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<div class="modal fade" id="modalDetailActivite" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-orange-dark">
|
||||||
|
<h5 class="modal-title">Détails de l'activité</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="">
|
||||||
|
<input type="hidden" id="idDetailActivite">
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Titre :</label>
|
||||||
|
<input type="text" class="form-control" id="titreDetailActivite">
|
||||||
|
</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>
|
||||||
|
<button class="btn btn-danger d-block mx-auto" id="btnAnnulerActivite">
|
||||||
|
Annuler cette activité
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-success" id="btnMiseAJourDepense">
|
||||||
|
<i class="bi bi-plus-circle"></i> Mise à jour du budget
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjouterLivrable">
|
||||||
|
<i class="bi bi-plus-circle"></i> Ajouter un livrable
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
{% load tags_personnaliser %}
|
||||||
|
<div class="modal fade" id="modalDetailProjet{{ activite.id }}" tabindex="-1">
|
||||||
|
<div class="modal-dialog ">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header ">
|
||||||
|
<h5 class="modal-title">Détails du projet </h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col" id="detail-projet-container">
|
||||||
|
<form method='post' action="{% url 'gestion_projet:mises-a-jour-projet' %}" id="detail-projet-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Id projet:</label>
|
||||||
|
<input type="text" class="form-control" id="detail_id_projet" name='id_projet' readonly>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label> Nom projet :</label>
|
||||||
|
<input type="text" class="form-control" id="detail_nom_projet" name='nom_projet'>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Numero de convention:</label>
|
||||||
|
<input type="text" class="form-control" id="detail_numero_convention" name="numero_convention">
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date de début :</label>
|
||||||
|
<input type="date" class="form-control" id="detail_date_debut" name='date_debut'>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Date de fin :</label>
|
||||||
|
<input type="date" class="form-control" id="detail_date_fin" name='date_fin'>
|
||||||
|
</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">
|
||||||
|
<label> Domaine de recherche </label>
|
||||||
|
<select class="form-select" multiple id="detail_domaine_recherche" name="domaine_recherche">
|
||||||
|
<option value="sciences_sociales">Sciences sociales</option>
|
||||||
|
<option value="naturelles">Naturelles</option>
|
||||||
|
<option value="humaines">Humaines</option>
|
||||||
|
<option value="veterinaires">Vétérinaires</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Budget :</label>
|
||||||
|
<input type="number" class="form-control" id="detail_budget" name='budget'>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Budget RH:</label>
|
||||||
|
<input type="number" class="form-control" id="detail_budget_rh" name='budget_RH'>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-2">
|
||||||
|
<label>Description :</label>
|
||||||
|
<textarea class="form-control" id="detail_description" name='description'></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-check-group mb-2">
|
||||||
|
<label class="form-check-label">Statut :</label>
|
||||||
|
<input type="text" class="form-control" id="detail_statut" readonly>
|
||||||
|
</div>
|
||||||
|
<button type='submit' class="btn btn-warning">
|
||||||
|
<i class="bi bi-pencil"></i> Modifier Projet
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<hr class="my-4">
|
||||||
|
<div class="accordion" id="accordionDetailProjet">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="employesAffectes">
|
||||||
|
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
|
||||||
|
Employés Affectés
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="employesAffectes" data-bs-parent="#accordionDetailProjet">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div id="employes_affectes_projet"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header" id="bailleursProjet">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
||||||
|
Liste des bailleurs
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="bailleursProjet" data-bs-parent="#accordionDetailProjet">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div id="bailleurs_projet"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
{% if user|has_group:"ressource_humaine" %}
|
||||||
|
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modal_ajout_financement">
|
||||||
|
<i class="bi bi-plus-circle"></i> Ajouter un financement
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="bi bi-x-circle"></i> Fermer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="modal fade" id="modal_ajout_financement" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Ajout de financement</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form method="POST" id="form_financement" action="{% url 'gestion_projet:ajouter_financement' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form_ajout_financement.as_p }}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" id="btn_enregistrer_financement" class="btn btn-success">Enregistrer</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<div class="modal fade" id="modalDepenseActivite" tabindex="-1" aria-labelledby="modalDepenseActiviteLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="modalDepenseActiviteLabel">
|
||||||
|
<i class="bi bi-cash-stack me-2"></i> Mise à jour des dépenses
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form method="POST" action="{% url 'gestion_projet:mettre-a-jour-depense' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="id_activite" id="id_activite_depense">
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Budget prévu (GNF)</label>
|
||||||
|
<input type="number" class="form-control" name="budget_prevu" id="budget_prevu" value="" disabled>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="budget_depense" class="form-label">Montant dépensé (GNF)</label>
|
||||||
|
<input type="number" name="budget_depense" id="budget_depense" class="form-control" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Motif de différence</label>
|
||||||
|
<textarea class="form-control" name="motif" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
|
||||||
|
<button type="submit" class="btn btn-success">
|
||||||
|
<i class="bi bi-save"></i> Enregistrer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
64
gestion_projet/templates/gestion_projet/suivi_activite.html
Normal file
64
gestion_projet/templates/gestion_projet/suivi_activite.html
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
{% extends "BASE.html" %}
|
||||||
|
{% load static %}
|
||||||
|
{% block 'titre_page' %} Gestion des projets {% endblock %}
|
||||||
|
{% block 'contenu' %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
|
||||||
|
{{ message }}
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<h5 class="mb-4 fw-bold text-uppercase text-orange-dark">
|
||||||
|
<i class="bi bi-kanban-fill me-2"></i> Suivi des Activités ({{ nom_projet }} )
|
||||||
|
</h5>
|
||||||
|
<div class="row d-flex justify-content-center mb-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">Budget Total (GNF)</span>
|
||||||
|
<h3>{{ budget_total }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col text-white bg-info d-flex flex-column justify-content-center align-items-center border rounded mx-3 p-4">
|
||||||
|
<span class="fs-5">Budget RH (GNF)</span>
|
||||||
|
<h3>{{ budget_RH }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="col text-white bg-warning d-flex flex-column justify-content-center align-items-center border rounded p-4">
|
||||||
|
<span class="fs-5">Budget Dépensé (GNF)</span>
|
||||||
|
<h3>{{budget_depense}}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="mt-4 d-flex justify-content-between mb-3">
|
||||||
|
<h5><i class="bi bi-people"></i> La liste des Activités</h5>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjoutActivite">
|
||||||
|
<i class="bi bi-plus-circle me-2"></i> Ajouter une Activité
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjoutDocument">
|
||||||
|
<i class="bi bi-plus-circle me-2"></i> Ajouter un document
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modalListeDocument">
|
||||||
|
Documents du projet
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<div id="tableau-liste-activite" data-urllisteactivite="{% url 'gestion_projet:liste-activites-projet' %}"></div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'modal' %}
|
||||||
|
{% include "gestion_projet/parts/modalAjoutActivite.html" %}
|
||||||
|
{% include "gestion_projet/parts/modalDetailActivite.html" %}
|
||||||
|
{% include "gestion_projet/parts/modalAjoutDocument.html" %}
|
||||||
|
{% include "gestion_projet/parts/modalMiseAJourDepense.html" %}
|
||||||
|
{% include "gestion_projet/parts/modalAjoutLivrable.html" %}
|
||||||
|
{% include "gestion_projet/parts/liste_document_projet.html" %}
|
||||||
|
{% include "gestion_projet/parts/modalAnnulerActivite.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
{% block 'js' %}
|
||||||
|
<script type="text/javascript" src="{% static 'gestion_projet/js/suivi-activites.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'gestion_projet/js/liste_documents_projet.js' %}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
3
gestion_projet/tests.py
Normal file
3
gestion_projet/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
122
gestion_projet/urls.py
Normal file
122
gestion_projet/urls.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'gestion_projet'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
'',
|
||||||
|
views.index,
|
||||||
|
name='index'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'modifier-financement/<int:financement_id>/',
|
||||||
|
views.modifier_financement_projet,
|
||||||
|
name='modifier-financement'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'projet/creation/',
|
||||||
|
views.creation_projet,
|
||||||
|
name='creation-projet'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'projet/modifier/<int:projet_id>/',
|
||||||
|
views.modification_projet,
|
||||||
|
name='modifier-projet'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'liste-projet/',
|
||||||
|
views.liste_projet,
|
||||||
|
name='liste-projet'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'projet/suppression/<int:id>/',
|
||||||
|
views.suppression_projet,
|
||||||
|
name='projet-suppression'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'projet/ajouter-financement',
|
||||||
|
views.ajouter_financement_projet,
|
||||||
|
name='ajouter_financement'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'creation-bailleur',
|
||||||
|
views.creation_bailleur,
|
||||||
|
name='creation-bailleur'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'projet/affectation/<int:projet_id>',
|
||||||
|
views.affecter_employe_projet,
|
||||||
|
name='affecter-employe-projet'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'projet/ajout-de-document/',
|
||||||
|
views.ajouter_document_projet,
|
||||||
|
name='ajouter-document'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'projet/bailleurs/<str:projet_id>/',
|
||||||
|
views.liste_bailleurs,
|
||||||
|
name='liste-bailleurs'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'activite/',
|
||||||
|
views.activites_projet,
|
||||||
|
name='activites-projet'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'activite/ajouter/',
|
||||||
|
views.ajouter_activite_projet,
|
||||||
|
name='ajouter-activite'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'activite/modifier/<int:id>/',
|
||||||
|
views.modifier_activite_projet,
|
||||||
|
name='modifier-activite'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'activite/annuler/',
|
||||||
|
views.annuler_activite_projet,
|
||||||
|
name='annuler-activite'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'activite/liste/',
|
||||||
|
views.liste_activites_projet,
|
||||||
|
name='liste-activites-projet'
|
||||||
|
),
|
||||||
|
# path(
|
||||||
|
# 'projet/ajout-de-document/',
|
||||||
|
# views.ajouter_document_projet,
|
||||||
|
# name='ajouter-document'
|
||||||
|
# ),
|
||||||
|
path(
|
||||||
|
'projet/liste-des-documents/',
|
||||||
|
views.liste_documents_projet,
|
||||||
|
name='liste-documents-projet'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'activite/liste-des-livrables/<int:activite_id>/',
|
||||||
|
views.liste_livrables_activite,
|
||||||
|
name='liste-livrables-activite'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'projet/ajout-de-livrable/',
|
||||||
|
views.ajouter_livrables_projet,
|
||||||
|
name='ajouter-livrable'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'activite/mise-a-jour-depense/',
|
||||||
|
views.mises_a_jour_depense_activite,
|
||||||
|
name='mettre-a-jour-depense'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'projet/liste-employes-par-projet/<str:projet_id>',
|
||||||
|
views.liste_employes_affectes,
|
||||||
|
name='liste-employes-affectes'
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'projet/mises-a-jour-projet',
|
||||||
|
views.mises_a_jour_projet,
|
||||||
|
name='mises-a-jour-projet'
|
||||||
|
)
|
||||||
|
]
|
||||||
485
gestion_projet/views.py
Normal file
485
gestion_projet/views.py
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
from datetime import date
|
||||||
|
from decimal import Decimal, InvalidOperation
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.db.models import Sum
|
||||||
|
from django.forms.models import model_to_dict
|
||||||
|
from gestion_employe.forms import AffectationForm
|
||||||
|
from gestion_employe.models import Affectation, Employe
|
||||||
|
from gestion_projet.forms import ProjetForm
|
||||||
|
from gestion_projet.models import Projet
|
||||||
|
from .models import (
|
||||||
|
DocumentProjet,
|
||||||
|
Bailleur,
|
||||||
|
FinancementProjet,
|
||||||
|
ActiviteProjet,
|
||||||
|
LivrablesLivres,
|
||||||
|
)
|
||||||
|
from .forms import (
|
||||||
|
ActiviteProjetForm,
|
||||||
|
DocumentProjetForm,
|
||||||
|
FinancementProjetFrom,
|
||||||
|
BailleurForm,
|
||||||
|
LivrablesLivresForm
|
||||||
|
)
|
||||||
|
|
||||||
|
def liste_projet(request):
|
||||||
|
""" Vue pour retourner la liste de tous les projet """
|
||||||
|
projets = Projet.objects.all().order_by('-created_at')
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for p in projets:
|
||||||
|
financement = FinancementProjet.objects.filter(projet=p).select_related('bailleur')
|
||||||
|
data.append({
|
||||||
|
"id_projet": p.id_projet,
|
||||||
|
"nom_projet": p.nom_projet,
|
||||||
|
"date_debut": p.date_debut,
|
||||||
|
"date_fin": p.date_fin,
|
||||||
|
"numero_convention": p.numero_convention,
|
||||||
|
"description": p.description,
|
||||||
|
"type_projet": p.type_projet,
|
||||||
|
"budget": p.budget,
|
||||||
|
"budget_RH": p.budget_RH,
|
||||||
|
"created_at": p.created_at,
|
||||||
|
"statut": p.statut,
|
||||||
|
"avancement": p.avancement,
|
||||||
|
"domaine_recherche": [d.nom for d in p.domaine_recherche.all()],
|
||||||
|
"source_financement": [f.bailleur.nom_organisme for f in financement],
|
||||||
|
})
|
||||||
|
|
||||||
|
return JsonResponse(data, safe=False)
|
||||||
|
|
||||||
|
def liste_employes_affectes(request, projet_id):
|
||||||
|
""" Vue pour retourner la liste des employés affectés à un projet spécifique """
|
||||||
|
employes = Employe.objects.filter(affectation__projet_id=projet_id).distinct()
|
||||||
|
data = []
|
||||||
|
for employe in employes:
|
||||||
|
data.append({
|
||||||
|
"employe": f"{employe.user.first_name} {employe.user.last_name}",
|
||||||
|
"pourcentage_affectation": Affectation.objects.get(employe=employe, projet__id_projet=projet_id).pourcentage_temps_affectation
|
||||||
|
})
|
||||||
|
return JsonResponse(data, safe=False)
|
||||||
|
|
||||||
|
def liste_bailleurs(request, projet_id):
|
||||||
|
""" Vue pour retourner la liste des bailleurs associés à un projet spécifique """
|
||||||
|
bailleurs = FinancementProjet.objects.filter(projet_id=projet_id).select_related('bailleur')
|
||||||
|
data = []
|
||||||
|
for b in bailleurs:
|
||||||
|
data.append({
|
||||||
|
"bailleur": b.bailleur.nom_organisme,
|
||||||
|
"pourcentage_financement": b.pourcentage
|
||||||
|
})
|
||||||
|
|
||||||
|
return JsonResponse(data, safe=False)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def index(request):
|
||||||
|
projets = Projet.objects.all().order_by('-created_at')
|
||||||
|
nombre_personnel = Affectation.objects.values('employe_id').distinct().count()
|
||||||
|
budget_total = sum([projet.budget for projet in projets if projet.budget or 0])
|
||||||
|
context = {
|
||||||
|
'form': AffectationForm(),
|
||||||
|
'form_ajout_financement': FinancementProjetFrom(),
|
||||||
|
'form_ajout_bailleur': BailleurForm(),
|
||||||
|
'bailleurs': Bailleur.objects.all(),
|
||||||
|
'nombre_personnel': nombre_personnel,
|
||||||
|
'budget_total': budget_total,
|
||||||
|
'formulaire_creation_projet': ProjetForm(),
|
||||||
|
'projet_en_cours': Projet.objects.filter(date_fin__gte=date.today()).count(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'gestion_projet/index.html', context)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def creation_projet(request):
|
||||||
|
"""Vue pour créer un nouveau projet via un formulaire"""
|
||||||
|
formulaire_creation_projet = ProjetForm()
|
||||||
|
if request.method == "POST":
|
||||||
|
form = ProjetForm(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
messages.success(request, "Projet créé avec succès.")
|
||||||
|
else:
|
||||||
|
messages.error(request, "Le formulaire transmis est invalide.")
|
||||||
|
else:
|
||||||
|
form = ProjetForm()
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"gestion_projet/creation_projet.html",
|
||||||
|
{
|
||||||
|
"formulaire_creation_projet": formulaire_creation_projet
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def mises_a_jour_projet(request):
|
||||||
|
""" Vue de mises à jour des informations du projet """
|
||||||
|
if request.method == "POST":
|
||||||
|
try:
|
||||||
|
projet = Projet.objects.get(id_projet = request.POST["id_projet"])
|
||||||
|
except Projet.DoesNotExist:
|
||||||
|
messages.error(request, "Ce projet n'existe pas.")
|
||||||
|
else:
|
||||||
|
projet_form = ProjetForm(request.POST, instance=projet)
|
||||||
|
if projet_form.is_valid():
|
||||||
|
projet_form.save()
|
||||||
|
messages.success(request, f"Le projet d'identifiant {request.POST['id_projet']} a été mis à jour avec succès.")
|
||||||
|
else:
|
||||||
|
messages.error(request, f"Les informations de modification transmises pour la modification du projet {request.POST['id_projet']} ne sont pas valides.")
|
||||||
|
else:
|
||||||
|
messages.error(request, "La méthode de transmission des données n'est pas valide.")
|
||||||
|
return redirect('gestion_projet:index')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def creation_bailleur(request):
|
||||||
|
form = BailleurForm(request.POST)
|
||||||
|
if request.method == 'POST':
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return JsonResponse({'success': True})
|
||||||
|
return JsonResponse({'success': False})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def ajouter_financement_projet(request):
|
||||||
|
"""Ajoute un financement à un projet en vérifiant que le total ne dépasse pas 100%"""
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
pourcentage_recuperer = request.POST.get('pourcentage')
|
||||||
|
bailleur_id = request.POST.get('bailleur')
|
||||||
|
projet_id = request.POST.get('projet')
|
||||||
|
try:
|
||||||
|
projet = Projet.objects.get(id_projet=projet_id)
|
||||||
|
except Projet.DoesNotExist:
|
||||||
|
return JsonResponse({'success': False, 'message': "Le projet spécifié n’existe pas."})
|
||||||
|
|
||||||
|
try:
|
||||||
|
pourcentage_nouveau = Decimal(pourcentage_recuperer) if pourcentage_recuperer else Decimal(0)
|
||||||
|
except (InvalidOperation, TypeError):
|
||||||
|
return JsonResponse({'success': False, 'message': "Le pourcentage saisi n’est pas valide."})
|
||||||
|
|
||||||
|
financement_total_actuel = sum(financement.pourcentage for financement in FinancementProjet.objects.filter(projet=projet))
|
||||||
|
if financement_total_actuel + pourcentage_nouveau > 100:
|
||||||
|
return JsonResponse({'success': False, 'message': "Le total des financements dépasse 100%."})
|
||||||
|
|
||||||
|
if bailleur_id:
|
||||||
|
FinancementProjet.objects.create(
|
||||||
|
projet=projet,
|
||||||
|
bailleur_id=bailleur_id,
|
||||||
|
pourcentage=pourcentage_nouveau
|
||||||
|
)
|
||||||
|
return JsonResponse({'success': True, 'message': "Financement ajouté avec succès."})
|
||||||
|
else:
|
||||||
|
return JsonResponse({'success': False, 'message': "Aucun bailleur sélectionné."})
|
||||||
|
return JsonResponse({'success': False, 'message': "Requête invalide."})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def modification_projet(request, projet_id):
|
||||||
|
"""Vue pour éditer un projet existant via un formulaire pré-rempli"""
|
||||||
|
try:
|
||||||
|
projet = Projet.objects.get(id=projet_id)
|
||||||
|
except Projet.DoesNotExist:
|
||||||
|
messages.error(request, "Le projet spécifié n’existe pas.")
|
||||||
|
return redirect('projet-index')
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = ProjetForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
messages.success(request, "Le projet a été modifié avec succès.")
|
||||||
|
return redirect('projet-index')
|
||||||
|
messages.error(request, "Erreur lors de la modification du projet.")
|
||||||
|
form = ProjetForm(instance=projet)
|
||||||
|
return render(request, 'gestion_projet/projet-edit.html', {'form': form, 'projets': projet})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def suppression_projet(request, id):
|
||||||
|
"""Vue pour supprimer un projet spécifique après confirmation de l'utilisateur"""
|
||||||
|
try:
|
||||||
|
projet = Projet.objects.get(id=id)
|
||||||
|
except Projet.DoesNotExist:
|
||||||
|
messages.error(request, "Le projet spécifié n’existe pas.")
|
||||||
|
return redirect('projet-index')
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
projet.delete()
|
||||||
|
messages.success(request, "Le projet a été supprimé avec succès.")
|
||||||
|
return redirect('projet-index')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def affecter_employe_projet(request, projet_id):
|
||||||
|
"""Vue pour affecter un employé à un projet avec vérification des contraintes d'affectation"""
|
||||||
|
try:
|
||||||
|
projet = Projet.objects.get(id=projet_id)
|
||||||
|
except Projet.DoesNotExist:
|
||||||
|
messages.error(request, "Le projet spécifié n’existe pas.")
|
||||||
|
return redirect('projet-index')
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = AffectationForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
employe = Employe.objects.get(id=form.cleaned_data['employe'].id)
|
||||||
|
date_fin_affectation = form.cleaned_data['date_fin_daffectation']
|
||||||
|
temps_nouveau = form.cleaned_data['temps_affectation']
|
||||||
|
date_affectation = form.cleaned_data['date_affectation']
|
||||||
|
|
||||||
|
if (date_fin_affectation and date_affectation):
|
||||||
|
total_affectation = (
|
||||||
|
Affectation.objects.filter(employe=employe)
|
||||||
|
.aggregate(total_pourcentage_affectation=Sum('temps_affectation'))
|
||||||
|
['total_pourcentage_affectation'] or 0
|
||||||
|
)
|
||||||
|
if (date_fin_affectation < date_affectation):
|
||||||
|
messages.warning(request, "La date de fin d'affectation ne peut pas être antérieure à la date de début.")
|
||||||
|
return redirect('projet-index')
|
||||||
|
elif date_fin_affectation > projet.date_fin:
|
||||||
|
messages.warning(request, f"La date de fin de l'affectation ({date_fin_affectation}) ne peut pas dépasser la date de fin du projet ({projet.date_fin}).")
|
||||||
|
return redirect('projet-index')
|
||||||
|
elif total_affectation + temps_nouveau > 100:
|
||||||
|
messages.warning(
|
||||||
|
request,
|
||||||
|
f"Les pourcentages d'affectation de l'employé {employe.first_name} {employe.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)."
|
||||||
|
)
|
||||||
|
return redirect('projet-index')
|
||||||
|
|
||||||
|
Affectation.objects.update_or_create(
|
||||||
|
projet=projet,
|
||||||
|
employe=employe,
|
||||||
|
defaults={
|
||||||
|
'date_affectation': form.cleaned_data['date_affectation'],
|
||||||
|
'date_fin_daffectation': date_fin_affectation,
|
||||||
|
'role': form.cleaned_data['role'],
|
||||||
|
'temps_affectation': temps_nouveau
|
||||||
|
}
|
||||||
|
)
|
||||||
|
form = AffectationForm(initial={'projet': projet})
|
||||||
|
messages.error(request, "Erreur : Formulaire non valide.")
|
||||||
|
return redirect('projet-index')
|
||||||
|
|
||||||
|
def modifier_financement_projet(request, financement_id):
|
||||||
|
try:
|
||||||
|
financement = FinancementProjet.objects.get(id=financement_id)
|
||||||
|
except FinancementProjet.DoesNotExist:
|
||||||
|
messages.error(request, "Le financement spécifié n’existe pas.")
|
||||||
|
return redirect('projet-index')
|
||||||
|
projet = financement.projet
|
||||||
|
if request.method == 'POST':
|
||||||
|
try:
|
||||||
|
nouveau_pourcentage = Decimal(request.POST.get('pourcentage', '0'))
|
||||||
|
except InvalidOperation:
|
||||||
|
messages.error(request, "Le pourcentage saisi est invalide.")
|
||||||
|
return redirect('projet-index')
|
||||||
|
pourcentage_total_financement = (
|
||||||
|
FinancementProjet.objects.filter(projet=projet)
|
||||||
|
.exclude(id=financement.id)
|
||||||
|
.aggregate(total_financement=Sum('pourcentage'))['pourcentage_total_financement'] or 0
|
||||||
|
)
|
||||||
|
if pourcentage_total_financement + nouveau_pourcentage > 100:
|
||||||
|
messages.error(request, f"Le total des financements dépasse 100% ({pourcentage_total_financement + nouveau_pourcentage}%).")
|
||||||
|
return redirect('projet-index')
|
||||||
|
financement.pourcentage = nouveau_pourcentage
|
||||||
|
financement.save()
|
||||||
|
messages.success(request, "Financement modifié avec succès.")
|
||||||
|
return redirect('projet-index')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def activites_projet(request):
|
||||||
|
try:
|
||||||
|
employe = Employe.objects.get(user=request.user)
|
||||||
|
except Employe.DoesNotExist:
|
||||||
|
messages.error(request, "Impossible d'accéder au menu 'Suivi des activités' car votre profil Utilisateur n'est lié à aucun profil Employe. Veuillez contacter l'administrateur.")
|
||||||
|
return redirect("gestion_conges:conge")
|
||||||
|
|
||||||
|
try:
|
||||||
|
Affectation.objects.get(employe=employe, date_fin_daffectation__gte = timezone.now().date(), role='chef_projet')
|
||||||
|
except Affectation.DoesNotExist :
|
||||||
|
messages.error(request, "Seuls les chefs de projet ont accès à l'onglet 'Suivi des Activités'")
|
||||||
|
return redirect("gestion_conges:conge")
|
||||||
|
|
||||||
|
projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
|
||||||
|
if projet :
|
||||||
|
context = {
|
||||||
|
**model_to_dict(projet),
|
||||||
|
"nom_projet": projet.projet.nom_projet,
|
||||||
|
"budget_total": projet.projet.budget,
|
||||||
|
"budget_RH": projet.projet.budget_RH,
|
||||||
|
"form_ajout_activite": ActiviteProjetForm(),
|
||||||
|
"form_ajout_document": DocumentProjetForm(),
|
||||||
|
"form_ajout_livrable": LivrablesLivresForm(),
|
||||||
|
}
|
||||||
|
else :
|
||||||
|
context = {
|
||||||
|
"form_ajout_activite": ActiviteProjetForm(),
|
||||||
|
"form_ajout_document": DocumentProjetForm(),
|
||||||
|
"form_ajout_livrable": LivrablesLivresForm(),
|
||||||
|
}
|
||||||
|
return render(request, 'gestion_projet/suivi_activite.html', context)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def ajouter_activite_projet(request):
|
||||||
|
"""Vue pour ajouter une activité à un projet spécifique via un formulaire"""
|
||||||
|
employe = Employe.objects.get(user=request.user)
|
||||||
|
projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = ActiviteProjetForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
activite = form.save(commit=False)
|
||||||
|
activite.projet = projet.projet
|
||||||
|
activite.budget_depense = request.POST["budget_prevu"]
|
||||||
|
activite.save()
|
||||||
|
messages.success(request, "Activité ajoutée avec succès !")
|
||||||
|
else:
|
||||||
|
messages.error(request, "Erreur : vérifiez les informations saisies.")
|
||||||
|
return redirect('gestion_projet:activites-projet')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def liste_activites_projet(request):
|
||||||
|
"""Vue pour retourner la liste des activités d'un projet spécifique"""
|
||||||
|
employe = Employe.objects.get(user=request.user)
|
||||||
|
projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
|
||||||
|
if projet:
|
||||||
|
activites = ActiviteProjet.objects.filter(projet_id=projet.projet.id_projet).order_by('-date_debut')
|
||||||
|
else:
|
||||||
|
activites = []
|
||||||
|
data = []
|
||||||
|
for a in activites:
|
||||||
|
data.append({
|
||||||
|
"id": a.id,
|
||||||
|
"titre": a.titre,
|
||||||
|
"date_debut": a.date_debut,
|
||||||
|
"date_fin": a.date_fin,
|
||||||
|
"statut": a.statut,
|
||||||
|
"budget_prevu": a.budget_prevu,
|
||||||
|
"budget_depense": a.budget_depense,
|
||||||
|
"motif_changement_budget": a.motif_changement_budget,
|
||||||
|
"besoin_ressource_materielle": a.besoin_ressource_materielle,
|
||||||
|
"description": a.description,
|
||||||
|
})
|
||||||
|
return JsonResponse(data, safe=False)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def liste_livrables_activite(request, activite_id):
|
||||||
|
"""Vue pour retourner la liste des livrables attendus d'une activité spécifique"""
|
||||||
|
livrables = LivrablesLivres.objects.filter(activite__id=activite_id)
|
||||||
|
data = []
|
||||||
|
for livrable in livrables:
|
||||||
|
print(livrable.fichier.url)
|
||||||
|
data.append({
|
||||||
|
"titre": livrable.nom,
|
||||||
|
"lien": livrable.fichier.url if livrable.fichier else "",
|
||||||
|
})
|
||||||
|
return JsonResponse(data, safe=False)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def mises_a_jour_depense_activite(request):
|
||||||
|
"""Vue pour retourner la liste des activités d'un projet spécifique avec leurs dépenses mises à jour"""
|
||||||
|
if request.method == "POST":
|
||||||
|
activite_id = request.POST.get("id_activite")
|
||||||
|
budget_depense = request.POST.get("budget_depense")
|
||||||
|
motif = request.POST.get("motif", "").strip()
|
||||||
|
try:
|
||||||
|
activite = ActiviteProjet.objects.get(id=activite_id)
|
||||||
|
activite.budget_depense = Decimal(budget_depense)
|
||||||
|
if Decimal(budget_depense) != activite.budget_prevu:
|
||||||
|
activite.motif_changement_budget = motif
|
||||||
|
else:
|
||||||
|
activite.motif_changement_budget = ""
|
||||||
|
activite.save()
|
||||||
|
messages.success(request, f"Dépenses mises à jour pour l’activité '{activite.titre}'.")
|
||||||
|
except (ActiviteProjet.DoesNotExist, InvalidOperation):
|
||||||
|
messages.error(request, "Erreur lors de la mise à jour des dépenses.")
|
||||||
|
return redirect("gestion_projet:activites-projet")
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def ajouter_livrables_projet(request):
|
||||||
|
"""Vue pour ajouter un livrable à une activité de projet spécifique via un formulaire"""
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = LivrablesLivresForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
messages.success(request, "Livrable ajouté avec succès !")
|
||||||
|
else:
|
||||||
|
messages.error(request, "Erreur : vérifiez les informations saisies.")
|
||||||
|
return redirect('gestion_projet:activites-projet')
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def ajouter_document_projet(request):
|
||||||
|
"""Ajoute un document à un projet"""
|
||||||
|
employe = Employe.objects.get(user=request.user)
|
||||||
|
projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
|
||||||
|
if request.method == "POST":
|
||||||
|
form = DocumentProjetForm(request.POST, request.FILES)
|
||||||
|
if form.is_valid():
|
||||||
|
document = form.save(commit=False)
|
||||||
|
document.projet = projet.projet
|
||||||
|
document.save()
|
||||||
|
messages.success(request, "Document ajouté avec succès !")
|
||||||
|
else:
|
||||||
|
messages.error(request, "Erreur : le document n’a pas pu être enregistré.")
|
||||||
|
return redirect('gestion_projet:activites-projet')
|
||||||
|
|
||||||
|
def liste_documents_projet(request):
|
||||||
|
employe = Employe.objects.get(user=request.user)
|
||||||
|
projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
|
||||||
|
if projet:
|
||||||
|
documents = DocumentProjet.objects.filter(projet__id_projet=projet.projet.id_projet)
|
||||||
|
else:
|
||||||
|
documents = []
|
||||||
|
data = []
|
||||||
|
for d in documents:
|
||||||
|
data.append({
|
||||||
|
"nom_document": d.nom_document,
|
||||||
|
"numero": d.numero,
|
||||||
|
"date_validite": d.date_validite,
|
||||||
|
"lien_document": d.fichier.url if d.fichier else "",
|
||||||
|
})
|
||||||
|
return JsonResponse(data, safe=False)
|
||||||
|
|
||||||
|
def modifier_activite_projet(request, id):
|
||||||
|
"""Vue pour modifier une activité de projet spécifique via un formulaire pré-rempli"""
|
||||||
|
try:
|
||||||
|
activite = ActiviteProjet.objects.get(id=id)
|
||||||
|
except ActiviteProjet.DoesNotExist:
|
||||||
|
messages.error(request, "L'activité spécifiée n’existe pas.")
|
||||||
|
return redirect('activites-projet')
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = ActiviteProjetForm(request.POST, instance=activite)
|
||||||
|
if form.is_valid():
|
||||||
|
activite.besoin_ressource_materielle = bool(request.POST.get("besoin_ressource_materielle"))
|
||||||
|
form.save()
|
||||||
|
messages.success(request, f"Activité « {activite.titre} » modifiée avec succès.")
|
||||||
|
else:
|
||||||
|
messages.error(request, "Erreur lors de la modification de l'activité.")
|
||||||
|
return redirect('activites-projet')
|
||||||
|
|
||||||
|
form = ActiviteProjetForm(instance=activite)
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
'gestion_projet/activite.html', {
|
||||||
|
'form': form,
|
||||||
|
'activite': activite,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def annuler_activite_projet(request):
|
||||||
|
"""Vue pour annuler une activité de projet spécifique après confirmation de l'utilisateur"""
|
||||||
|
print(request.POST)
|
||||||
|
if request.method != "POST":
|
||||||
|
messages.error(request, "Requête invalide.")
|
||||||
|
return redirect('gestion_projet:activites-projet')
|
||||||
|
try:
|
||||||
|
activite = ActiviteProjet.objects.get(id=request.POST.get('id_activite'))
|
||||||
|
except ActiviteProjet.DoesNotExist:
|
||||||
|
messages.error(request, "L'activité spécifiée n’existe pas.")
|
||||||
|
return redirect('gestion_projet:activites-projet')
|
||||||
|
if request.method == "POST":
|
||||||
|
activite.annuler = True
|
||||||
|
activite.motif_annulation = request.POST.get("motif_annulation", "").strip()
|
||||||
|
activite.save()
|
||||||
|
messages.success(request, f"L'activité '{activite.titre}' a été annulée avec succès.")
|
||||||
|
return redirect('gestion_projet:activites-projet')
|
||||||
0
gestion_salle/__init__.py
Normal file
0
gestion_salle/__init__.py
Normal file
3
gestion_salle/admin.py
Normal file
3
gestion_salle/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
5
gestion_salle/apps.py
Normal file
5
gestion_salle/apps.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class GestionSalleConfig(AppConfig):
|
||||||
|
name = 'gestion_salle'
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user