Premiere version SIRH
This commit is contained in:
0
gestion_conge/__init__.py
Normal file
0
gestion_conge/__init__.py
Normal file
BIN
gestion_conge/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
gestion_conge/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
gestion_conge/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
gestion_conge/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/admin.cpython-310.pyc
Normal file
BIN
gestion_conge/__pycache__/admin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/admin.cpython-313.pyc
Normal file
BIN
gestion_conge/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/admin.cpython-314.pyc
Normal file
BIN
gestion_conge/__pycache__/admin.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/apps.cpython-310.pyc
Normal file
BIN
gestion_conge/__pycache__/apps.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/apps.cpython-313.pyc
Normal file
BIN
gestion_conge/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/apps.cpython-314.pyc
Normal file
BIN
gestion_conge/__pycache__/apps.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/forms.cpython-310.pyc
Normal file
BIN
gestion_conge/__pycache__/forms.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/forms.cpython-313.pyc
Normal file
BIN
gestion_conge/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/forms.cpython-314.pyc
Normal file
BIN
gestion_conge/__pycache__/forms.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/models.cpython-310.pyc
Normal file
BIN
gestion_conge/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/models.cpython-313.pyc
Normal file
BIN
gestion_conge/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/models.cpython-314.pyc
Normal file
BIN
gestion_conge/__pycache__/models.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/urls.cpython-310.pyc
Normal file
BIN
gestion_conge/__pycache__/urls.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/urls.cpython-313.pyc
Normal file
BIN
gestion_conge/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/urls.cpython-314.pyc
Normal file
BIN
gestion_conge/__pycache__/urls.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/views.cpython-310.pyc
Normal file
BIN
gestion_conge/__pycache__/views.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/views.cpython-313.pyc
Normal file
BIN
gestion_conge/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/__pycache__/views.cpython-314.pyc
Normal file
BIN
gestion_conge/__pycache__/views.cpython-314.pyc
Normal file
Binary file not shown.
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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
gestion_conge/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
gestion_conge/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
gestion_conge/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_conge/migrations/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
gestion_conge/migrations/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
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."})
|
||||
Reference in New Issue
Block a user