Première groose modification

This commit is contained in:
2026-04-27 14:27:07 +02:00
parent 4f9df2214c
commit f470cebfac
62 changed files with 437 additions and 160 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
db.sqlite3
venv/*
media/*

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -81,22 +81,20 @@ WSGI_APPLICATION = 'SIRH.wsgi.application'
# DATABASES = { # DATABASES = {
# 'default': { # 'default': {
# 'ENGINE': 'django.db.backends.sqlite3', # 'ENGINE': 'django.db.backends.mysql',
# 'NAME': BASE_DIR / 'db.sqlite3', # 'NAME': 'sirh',
# } # 'USER': 'test',
# 'PASSWORD': 'test-django',
# 'HOST': 'localhost',
# 'PORT': '3306',
# } # }
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'sirh', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'USER': 'test', }
'PASSWORD': 'test-django',
'HOST': 'localhost', # Or the server IP
'PORT': '3306', # Default MySQL port
} }
}
# Password validation # Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
@@ -157,3 +155,5 @@ EMAIL_USE_TLS = False
EMAIL_HOST_USER = 'support.it@cerfig.org' EMAIL_HOST_USER = 'support.it@cerfig.org'
EMAIL_HOST_PASSWORD = 'Cerfig2025' EMAIL_HOST_PASSWORD = 'Cerfig2025'
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER DEFAULT_FROM_EMAIL = EMAIL_HOST_USER

View File

@@ -2,6 +2,20 @@
{% load tags_personnaliser %} {% load tags_personnaliser %}
<div class="col-3 bg-danger d-flex flex-column vh-100 pt-5 sticky-top"> <div class="col-3 bg-danger d-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"> <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 <i class="bi bi-person-circle"></i> Mon profil
</a> </a>

View File

@@ -1,7 +1,8 @@
from django.utils import timezone from django.utils import timezone
from gestion_employe.models import Contrat from gestion_employe.models import Contrat
from gestion_conge.models import Conge from gestion_conge.models import Conge
from django.core.mail import send_mail
from gestion_salle.models import Reservation
QUOTA_CONGE_ANNUEL = 30 QUOTA_CONGE_ANNUEL = 30
NOMBRE_PAGINATION = 8 NOMBRE_PAGINATION = 8
@@ -11,7 +12,6 @@ DUREE_FIN_CONTRAT = 90
def solde_conge(employe): def solde_conge(employe):
"""Fonction de calcul du solde de congé restant l'employé""" """Fonction de calcul du solde de congé restant l'employé"""
contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first() contrat = Contrat.objects.filter(employe=employe, statut='actif').order_by('-date_debut').first()
if contrat is None or not contrat.date_debut: if contrat is None or not contrat.date_debut:
return { return {
"success": False, "success": False,
@@ -32,3 +32,37 @@ def solde_conge(employe):
"quota_annuel": QUOTA_CONGE_ANNUEL - jours_conges_valider, "quota_annuel": QUOTA_CONGE_ANNUEL - jours_conges_valider,
"nombre_jours_valide": jours_conges_valider "nombre_jours_valide": jours_conges_valider
} }
def envoyer_email(type_notification, utilisateur, contexte=None):
sujet = ""
message = ""
nom = f"{utilisateur.first_name} {utilisateur.last_name}"
if type_notification == "reservation_creee":
sujet = "Confirmation de votre réservation"
message = f"Bonjour {nom}, votre réservation a été enregistrée."
elif type_notification == "reservation_validee":
sujet = "Réservation validée"
message = f"Bonjour {nom}, votre réservation a été validée."
elif type_notification == "reservation_refusee":
sujet = "Réservation refusée"
message = f"Bonjour {nom}, votre réservation a été refusée."
elif type_notification == "reservation_annulee":
sujet = "Réservation annulée"
message = f"Bonjour {nom}, votre réservation a été annulée."
elif type_notification == "lien_zoom":
sujet = "Lien Zoom ajouté"
message = f"Bonjour {nom}, un lien Zoom a été ajouté à votre réservation : {Reservation.lien_zoom}"
send_mail(
sujet,
message,
"admin@tonsite.com",
[utilisateur.email],
fail_silently=False,
)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -36,14 +36,12 @@ if(bouton_enregistrer_detail){
if(document.getElementById("validation_hierarchique_refuse")){ if(document.getElementById("validation_hierarchique_refuse")){
document.getElementById("validation_hierarchique_refuse").addEventListener('click', function(){ document.getElementById("validation_hierarchique_refuse").addEventListener('click', function(){
if(this.checked){ if(this.checked){
alert("coucou");
document.getElementById("motif_refus_container").className="d-block form-group mt-3"; document.getElementById("motif_refus_container").className="d-block form-group mt-3";
}else{ }else{
document.getElementById("motif_refus_container").className="d-none"; document.getElementById("motif_refus_container").className="d-none";
} }
}) })
} }
if(document.getElementById("validation_hierarchique_refuse")){ if(document.getElementById("validation_hierarchique_refuse")){
document.getElementById("validation_hierarchique_refuse").addEventListener('click', function(){ document.getElementById("validation_hierarchique_refuse").addEventListener('click', function(){
if(this.checked){ if(this.checked){
@@ -67,9 +65,9 @@ if(document.getElementById("validation_hierarchique_valide")){
if(document.getElementById("validation_direction_valide")){ if(document.getElementById("validation_direction_valide")){
document.getElementById("validation_direction_valide").addEventListener('click', function(){ document.getElementById("validation_direction_valide").addEventListener('click', function(){
if(this.checked){ if(this.checked){
document.getElementById("motif_refus_container").className="d-block form-group mt-3";
}else{
document.getElementById("motif_refus_container").className="d-none"; document.getElementById("motif_refus_container").className="d-none";
}else{
document.getElementById("motif_refus_container").className="d-block form-group mt-3";
} }
}) })
} }

View File

@@ -65,7 +65,7 @@
<h5 class="text-center">Validation par le directeur</h5> <h5 class="text-center">Validation par le directeur</h5>
<div class="d-flex align-items-center justify-content-center mb-2"> <div class="d-flex align-items-center justify-content-center mb-2">
<div class="form-check form-check-inline"> <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 %}> <input class="form-check-input" type="radio" name="validation_direction" id="validation_direction_valide" value="valide" >
<label class="form-check-label" for="validation_direction_valide">Valide</label> <label class="form-check-label" for="validation_direction_valide">Valide</label>
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,6 +1,7 @@
const $ = (element) => document.getElementById(element); const $ = (element) => document.getElementById(element);
const url_liste_employe = $("tableau_liste_employe").dataset.url; const url_liste_employe = $("tableau_liste_employe").dataset.url;
const tableau_liste_employe = new Tabulator("#tableau_liste_employe", { const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
columns: [ columns: [
{"title": "Matricule", "field": "matricule"}, {"title": "Matricule", "field": "matricule"},
@@ -12,6 +13,8 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", {
// ajaxURL: url_liste_employe, // ajaxURL: url_liste_employe,
pagination: true, pagination: true,
paginationSize: 10, paginationSize: 10,
}) })
fetch(url_liste_employe) fetch(url_liste_employe)
@@ -44,6 +47,8 @@ tableau_liste_employe.on("rowClick", function (row, rowData) {
document.getElementById('document-diplome').href = data.diplome; document.getElementById('document-diplome').href = data.diplome;
document.getElementById('document-diplome').textContent = data.diplome || "Aucun diplôme"; document.getElementById('document-diplome').textContent = data.diplome || "Aucun diplôme";
document.getElementById('document-photo').href = data.photo;
document.getElementById('document-photo').textContent = data.photo || "Aucune photo";
document.getElementById('document-cv').href = data.CV; document.getElementById('document-cv').href = data.CV;
document.getElementById('document-cv').textContent = data.CV || "Aucun CV"; document.getElementById('document-cv').textContent = data.CV || "Aucun CV";
document.getElementById('document-rib').href = data.rib; document.getElementById('document-rib').href = data.rib;
@@ -124,6 +129,7 @@ tableau_liste_employe.on("rowClick", function (row, rowData) {
` `
}; };
const supprimerButtons = document.getElementsByClassName("btn-supprimer-contrat"); const supprimerButtons = document.getElementsByClassName("btn-supprimer-contrat");
Array.from(supprimerButtons).forEach(button => { Array.from(supprimerButtons).forEach(button => {
button.addEventListener("click", function() { button.addEventListener("click", function() {
@@ -144,8 +150,46 @@ tableau_liste_employe.on("rowClick", function (row, rowData) {
location.reload(); location.reload();
}) })
}); });
}) }),
document.addEventListener("click", function (e) {
if (e.target.closest(".btn-modifier-contrat")) {
const button = e.target.closest(".btn-modifier-contrat");
const parent = button.closest(".col-6");
const contratId = button.dataset.contratid;
const data = {
type_contrat: parent.querySelector("select").value,
date_debut: parent.querySelectorAll("input")[1].value,
date_fin: parent.querySelectorAll("input")[2].value,
salaire_mensuel: parent.querySelectorAll("input")[3].value,
statut: parent.querySelectorAll("input")[4].value,
};
fetch(`/contrat/modifier/${contratId}/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": document.querySelector('[name=csrfmiddlewaretoken]').value
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
if (data.message) {
alert(data.message);
} else {
alert(data.error || "Erreur");
}
})
.catch(error => {
console.error("Erreur:", error);
});
}
});
document.getElementById("affectation-nom-employe").textContent = data.employe || "Employé inconnu"; document.getElementById("affectation-nom-employe").textContent = data.employe || "Employé inconnu";
document.getElementById("affecter_employe_id").value = data.id; document.getElementById("affecter_employe_id").value = data.id;

View File

@@ -16,27 +16,36 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
], ],
ajaxURL: url_certificat, ajaxURL: url_certificat,
}) })
const enregistrerProfil = $("enregistrerProfil"); const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => { enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url; const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value; const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData();
formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value);
formData.append("telephone", $("telephone").value);
formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value);
const photoInput = $("photo");
if (photoInput.files.length > 0) {
formData.append("photo", photoInput.files[0]);
}
fetch(url, { fetch(url, {
method: 'POST', method: "POST",
headers: { headers: {
'Content-Type': 'application/json', "X-CSRFToken": csrftoken
'X-CSRFToken': csrftoken
}, },
body: JSON.stringify({ body: formData
"nom": $("nom").value,
"prenom": $("prenom").value,
"email": $("email").value,
"telephone": $("telephone").value,
"adresse": $("adresse").value,
"sexe": $("sexe").value,
"date_naissance": $("date_naissance").value,
})
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => alert(data.message)) .then(data => alert(data.message))
}) .catch(error => console.error("Erreur:", error));
});

View File

@@ -19,17 +19,14 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
{% if not expiration_contrat %}
{% if expiration_contrat %} <div class="alert alert-danger fade show alert-dismissible mt-2">
{% if contrat_nb_jours_restant %} <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"> <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. <strong>Important :</strong> Votre contrat de travail expire dans {{ contrat_nb_jours_restant }} jours, veuillez contacter les ressources humaines.
</div> </div>
{% endif %}
{% else %}
<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>
{% endif %} {% endif %}
<div class="accordion mt-2" id="accordionInformationEmploye"> <div class="accordion mt-2" id="accordionInformationEmploye">
@@ -44,6 +41,13 @@
<div class="row" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}"> <div class="row" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}">
{% csrf_token %} {% csrf_token %}
<div class="col"> <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"> <div class="form-group mb-2">
<label>Matricule :</label> <label>Matricule :</label>
<input type="text" class="form-control" id="matricule" value="{{ employe.matricule|default:'' }}" readonly> <input type="text" class="form-control" id="matricule" value="{{ employe.matricule|default:'' }}" readonly>
@@ -63,12 +67,13 @@
<option value='f' {% if employe.sexe == 'f' %}selected{% endif %}>Femme</option> <option value='f' {% if employe.sexe == 'f' %}selected{% endif %}>Femme</option>
</select> </select>
</div> </div>
</div>
<div class="col">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Date de naissance :</label> <label>Date de naissance :</label>
<input type="date" class="form-control" id="date_naissance" value="{{ employe.date_naissance|date:'Y-m-d' }}"> <input type="date" class="form-control" id="date_naissance" value="{{ employe.date_naissance|date:'Y-m-d' }}">
</div> </div>
</div>
<div class="col">
<div class="form-group mb-2"> <div class="form-group mb-2">
<label>Département :</label> <label>Département :</label>
<input type="text" class="form-control" id="departement" value="{{ employe.departement.nom|default:'' }}" readonly> <input type="text" class="form-control" id="departement" value="{{ employe.departement.nom|default:'' }}" readonly>
@@ -167,6 +172,13 @@
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col"> <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"> <div class="form-group mb-2">
<label>CV</label> <label>CV</label>
{% if employe.CV %} {% if employe.CV %}

View File

@@ -88,22 +88,23 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<h5> <h5>G
<i class="bi bi-file-earmark-text me-2"></i> Liste des Contrats <i class="bi bi-file-earmark-text me-2"></i> Liste des Contrats
</h5> </h5>
{% if user|has_group:"ressource_humaine" %} {% if user|has_group:"ressource_humaine" %}
<div> <div>
<button class="btn btn-secondary" disabled>
<i class="bi bi-file-earmark-lock"></i> Contrat actif
</button>
<button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modalContrat{{ item.employe.id }}"> <button type="button" class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modalContrat{{ item.employe.id }}">
<i class="bi bi-file-earmark-text"></i> Créer contrat <i class="bi bi-file-earmark-text"></i> Créer contrat
</button> </button>
<!-- <button class="btn btn-primary btn-modifier-contrat" data-contratid="${contrat.id}">
<i class="bi bi-pencil"></i> Modifier
</button> -->
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% csrf_token %} {% csrf_token %}
<div class="row" id="contrats-list"></div> <div class="row" id="contrats-list">
</div>
</div> </div>
</div> </div>
<hr class="my-4"> <hr class="my-4">

View File

@@ -11,6 +11,10 @@
<div class="row g-3"> <div class="row g-3">
<div class="col"> <div class="col">
<ul class="list-group"> <ul class="list-group">
<li class="list-group-item">
<strong>Photo :</strong>
<a id="document-photo" target="_blank"></a>
</li>
<li class="list-group-item"> <li class="list-group-item">
<strong>Diplôme :</strong> <strong>Diplôme :</strong>
<a id="document-diplome" target="_blank"></a> <a id="document-diplome" target="_blank"></a>

View File

@@ -34,6 +34,12 @@ urlpatterns = [
views.suppression_contrat, views.suppression_contrat,
name='supprimer-contrat' name='supprimer-contrat'
), ),
path(
"contrat/modifier/",
views.modifier_contrat,
name="modifier-contrat"
),
path( path(
'Affectation/affectation/', 'Affectation/affectation/',
views.affecter_employe_projet, views.affecter_employe_projet,

View File

@@ -1,12 +1,12 @@
import json import json
from datetime import timedelta, datetime from datetime import date, timedelta, datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.utils import timezone from django.utils import timezone
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.shortcuts import render, redirect from django.shortcuts import get_object_or_404, render, redirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import JsonResponse from django.http import JsonResponse
@@ -124,8 +124,8 @@ def mon_profil(request):
except Employe.DoesNotExist: except Employe.DoesNotExist:
messages.error(request, "Impossible d'acceder au menu 'Mon profil' car votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'Administrateur.") messages.error(request, "Impossible d'acceder au menu 'Mon profil' car votre profil Utilisateur n'est lié à aucun profil Employé. Veuillez contacter l'Administrateur.")
return redirect("gestion_conges:conge") return redirect("gestion_conges:conge")
contrats = Contrat.objects.filter(employe=employe, statut='actif').first() contrats = Contrat.objects.filter(employe=employe, statut='actif').first()
projets = Affectation.objects.filter( projets = Affectation.objects.filter(
employe = employe, employe = employe,
date_fin_daffectation__gte = timezone.now().date() date_fin_daffectation__gte = timezone.now().date()
@@ -140,6 +140,7 @@ def mon_profil(request):
**model_to_dict(contrats), **model_to_dict(contrats),
"type_contrat": dict(Contrat.TYPE_CONTRAT).get(contrats.type_contrat), "type_contrat": dict(Contrat.TYPE_CONTRAT).get(contrats.type_contrat),
"statut": dict(Contrat.STATUT_CONTRAT).get(contrats.statut), "statut": dict(Contrat.STATUT_CONTRAT).get(contrats.statut),
# "salaire_mensuel": dict(Contrat.SALAIRR_MENSSUEL).get(contrats.salaire_mensuel),
"fichier_contrat": contrats.fichier_contrat.url if contrats.fichier_contrat else "", "fichier_contrat": contrats.fichier_contrat.url if contrats.fichier_contrat else "",
} if contrats else []], } if contrats else []],
'projets': [ 'projets': [
@@ -192,7 +193,8 @@ def modifier_employer(request):
employe.telephone = data['telephone'] employe.telephone = data['telephone']
employe.adresse = data['adresse'] employe.adresse = data['adresse']
employe.sexe = data['sexe'] employe.sexe = data['sexe']
if request.FILES.get("photo"):
employe.photo = request.FILES["photo"]
if data['date_naissance']: if data['date_naissance']:
difference = relativedelta(timezone.now().date(), datetime.strptime(data['date_naissance'], "%Y-%m-%d").date()) difference = relativedelta(timezone.now().date(), datetime.strptime(data['date_naissance'], "%Y-%m-%d").date())
if difference.years >= 18: if difference.years >= 18:
@@ -201,18 +203,18 @@ def modifier_employer(request):
return JsonResponse({"message": "Veuillez entrez une date de naissance correcte."}) return JsonResponse({"message": "Veuillez entrez une date de naissance correcte."})
employe.save() employe.save()
user.save() user.save()
return JsonResponse({"message": "Profil mis à jour avec succès."}) return JsonResponse({"message": "Profil mis à jour avec succès."})
def enregistrement_document(request): def enregistrement_document(request):
employe = Employe.objects.get(user__username=request.user) employe = Employe.objects.get(user=request.user)
if request.method == "POST": if request.method == "POST":
employe.CV = request.FILES["cv"] if 'cv' in request.FILES else employe.CV if request.FILES.get("photo"):employe.photo = request.FILES["photo"]
employe.diplome = request.FILES["diplome"] if 'diplome' in request.FILES else employe.diplome if "cv" in request.FILES:employe.CV = request.FILES["cv"]
employe.rib = request.FILES["rib"] if 'rib' in request.FILES else employe.rib if "diplome" in request.FILES: employe.diplome = request.FILES["diplome"]
employe.casier_judiciaire = request.FILES["casier_judiciaire"] if 'casier_judiciaire' in request.FILES else employe.casier_judiciaire if "rib" in request.FILES: employe.rib = request.FILES["rib"]
messages.success(request, "Documents enregistrés avec succès.") if "casier_judiciaire" in request.FILES:employe.casier_judiciaire = request.FILES["casier_judiciaire"]
employe.save() employe.save()
messages.success(request, "Documents enregistrés avec succès.")
return redirect("gestion_employe:mon-profil") return redirect("gestion_employe:mon-profil")
@@ -240,14 +242,25 @@ def suppression_affectation(request):
return JsonResponse({"message": "Affectation supprimée avec succès."}) return JsonResponse({"message": "Affectation supprimée avec succès."})
def creation_contrat(request): def creation_contrat(request):
"""Vue pour permettre à un utilisateur de créer un contrat pour un employé""" """Créer un contrat pour un employé (avec contrôle d'existence de contrat actif)"""
try: try:
employe = Employe.objects.get(id=request.POST.get('employe_id')) employe = Employe.objects.get(id=request.POST.get('employe_id'))
except Employe.DoesNotExist: except Employe.DoesNotExist:
messages.error(request, "Employé non trouvé.") messages.error(request, "Employé non trouvé.")
return redirect('employe-index') return redirect('employe-index')
contrat_actif = Contrat.objects.filter(
employe=employe,
date_fin__gte=date.today()
).exists()
if request.method == "POST": if request.method == "POST":
if contrat_actif:
messages.error(
request,
"Impossible de créer un contrat : cet employé a déjà un contrat actif."
)
return redirect('gestion_employe:index')
form = ContratForm(request.POST, request.FILES) form = ContratForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
contrat = form.save(commit=False) contrat = form.save(commit=False)
@@ -256,9 +269,13 @@ def creation_contrat(request):
messages.success(request, "Contrat créé avec succès.") messages.success(request, "Contrat créé avec succès.")
return redirect('gestion_employe:index') return redirect('gestion_employe:index')
messages.error(request, "Formulaire non valide") messages.error(request, "Formulaire non valide")
else: else:
form = ContratForm(initial={'employe': employe}) form = ContratForm(initial={'employe': employe})
return render(request, 'gestion_employe/index.html', {'contrat_form': form})
return render(request, 'gestion_employe/index.html', {
'contrat_form': form
})
@login_required @login_required
def enregistrer_detail_employe(request): def enregistrer_detail_employe(request):
@@ -281,11 +298,12 @@ def enregistrer_detail_employe(request):
@login_required @login_required
def liste_employe(request): def liste_employe(request):
""" Vue pour retourner la liste de tous les employés """ """ Vue pour retourner la liste de tous les employés """
employes = Employe.objects.exclude(user__first_name = '', user__last_name = '') employes = Employe.objects.exclude(user__first_name = '', user__last_name = '')
data = [] data = []
for employe in employes: for employe in employes:
if employe.user.first_name == ' ' and employe.user.last_name == ' ':
projets = [ projets = [
", ".join([ ", ".join([
a.projet.nom_projet for a in Affectation.objects.filter( a.projet.nom_projet for a in Affectation.objects.filter(
@@ -351,6 +369,35 @@ def liste_employe(request):
) )
return JsonResponse({'success': True, 'data': data}, safe=False) return JsonResponse({'success': True, 'data': data}, safe=False)
@login_required
def modifier_contrat(request):
"""vue pour la modification du 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)
try:
data = json.loads(request.body)
id_contrat = data.get("id")
contrat = get_object_or_404(Contrat, id=id_contrat)
contrat.type_contrat = data.get("type_contrat")
contrat.date_debut = data.get("date_debut")
contrat.date_fin = data.get("date_fin")
contrat.salaire_mensuel = data.get("salaire_mensuel")
contrat.statut = data.get("statut")
contrat.save()
return JsonResponse({"message": "Contrat modifié avec succès"})
except Exception as e:
return JsonResponse({"message": str(e)}, status=400)
@login_required @login_required
def ajouter_formation(request): def ajouter_formation(request):
"""Vue pour permettre à un employé d'ajouter une formation à son profil""" """Vue pour permettre à un employé d'ajouter une formation à son profil"""

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,5 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur'); const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
let table;
btnEnregistrerBailleur.addEventListener('click', function() { btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur'); const form = document.getElementById('formBailleur');
@@ -21,3 +22,30 @@ btnEnregistrerBailleur.addEventListener('click', function() {
} }
}); });
}); });
document.addEventListener("DOMContentLoaded", function () {
table = new Tabulator("#table-bailleurs", {
ajaxURL: "/gestion-projet/bailleurs/",
layout: "fitColumns",
pagination: "local",
paginationSize: 5,
columns: [
{title: "#", formatter: "rownum", width: 60},
{title: "Organisme", field: "nom_organisme"},
{title: "Contact", field: "contact"},
{title: "Email", field: "email"},
{title: "Pays", field: "pays"},
],
rowDblClick: function(e, row) {
const data = row.getData();
if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) {
supprimerBailleur(data.id);
}
}
});
});

View File

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

View File

@@ -106,6 +106,11 @@
<i class="bi bi-plus-circle"></i> Ajouter un financement <i class="bi bi-plus-circle"></i> Ajouter un financement
</button> </button>
{% endif %} {% endif %}
{% if user|has_group:"direction" %}
<a href="#" class="btn btn-primary">
<i class="bi bi-graph-up"></i> Voir activités
</a>
{% endif %}
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"> <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle"></i> Fermer <i class="bi bi-x-circle"></i> Fermer
</button> </button>

View File

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

View File

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

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.

View File

@@ -21,3 +21,5 @@
</div> </div>
</div> </div>
</div> </div>

View File

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

View File

@@ -17,7 +17,6 @@ def index(request:HttpRequest):
except Employe.DoesNotExist: except Employe.DoesNotExist:
messages.error(request, "Impossible d'accéder au menu 'Reservation de salle' car votre profil Utilisateur n'est lié à aucun profil Employe. Veuillez contacter l'administrateur.") messages.error(request, "Impossible d'accéder au menu 'Reservation de salle' car votre profil Utilisateur n'est lié à aucun profil Employe. Veuillez contacter l'administrateur.")
return redirect('gestion_conges:conge') return redirect('gestion_conges:conge')
if request.method == "POST": if request.method == "POST":
form = ReservationForm(request.POST) form = ReservationForm(request.POST)
if form.is_valid(): if form.is_valid():
@@ -42,12 +41,24 @@ def index(request:HttpRequest):
besoin_ordi = besoin_ordi, besoin_ordi = besoin_ordi,
motif_reservation=motif_reservation, motif_reservation=motif_reservation,
) )
if heure_fin <= heure_debut:
messages.error(
request,
"Erreur : l'heure de fin ne peut pas être inférieure ou égale à l'heure de début."
)
return redirect('gestion_salle:reservation-salle')
first_name = (employe.user.first_name or "").strip()
last_name = (employe.user.last_name or "").strip()
if not first_name or not last_name:
messages.error(
request,
"Vous devez renseigner un nom et prénom valides avant de faire une réservation."
)
return redirect('gestion_salle:reservation-salle')
reservation.save() reservation.save()
date_debut = date_debut + timedelta(days=1) date_debut = date_debut + timedelta(days=1)
messages.success(request, "Réservation(s) créées avec succès.") messages.success(request, "Réservation(s) créées avec succès.")
return redirect('gestion_salle:reservation-salle') return redirect('gestion_salle:reservation-salle')
formulaire_reservation = ReservationForm() formulaire_reservation = ReservationForm()
departement = Employe.objects.get(user__username=request.user).departement departement = Employe.objects.get(user__username=request.user).departement
appartient_direction = 'direction' in request.user.groups.values_list('name', flat=True) appartient_direction = 'direction' in request.user.groups.values_list('name', flat=True)
@@ -86,7 +97,6 @@ def liste_reservation(request:HttpRequest):
"end": reservation.heure_fin, "end": reservation.heure_fin,
"color": color, "color": color,
}) })
return JsonResponse(liste_reservation, safe=False) return JsonResponse(liste_reservation, safe=False)
@login_required @login_required
@@ -105,21 +115,22 @@ def liste_reservation_attente(request):
def detail_reservation(request:HttpRequest, reservation_id:int): def detail_reservation(request:HttpRequest, reservation_id:int):
reservation = Reservation.objects.get(id=reservation_id) reservation = Reservation.objects.get(id=reservation_id)
employe = reservation.employe.user employe = reservation.employe.user
reservation_json = { reservation_json = {
'id_reservation': reservation_id, 'id_reservation': reservation_id,
'employe': f"{employe.first_name} {employe.last_name}", 'employe': f"{employe.first_name} {employe.last_name}",
'salle': reservation.salle, 'salle': reservation.salle,
'statut': reservation.statut, 'statut': reservation.statut,
'date_evenement': reservation.date_debut.strftime('%Y-%m-%d'), 'date_debut': reservation.date_debut.strftime('%Y-%m-%d'),
'date_fin': reservation.date_fin.strftime('%Y-%m-%d'),
'heure_debut': reservation.heure_debut.strftime('%H:%M'), 'heure_debut': reservation.heure_debut.strftime('%H:%M'),
'heure_fin': reservation.heure_fin.strftime('%H:%M'), 'heure_fin': reservation.heure_fin.strftime('%H:%M'),
'motif_reservation': reservation.motif_reservation, 'motif_reservation': reservation.motif_reservation,
'besoin_zoom': reservation.besoin_zoom, 'besoin_zoom': reservation.besoin_zoom,
'besoin_ordinateur': reservation.besoin_ordi, 'besoin_ordinateur': reservation.besoin_ordi,
'lien_zoom': reservation.lien_zoom or '', 'lien_zoom': reservation.lien_zoom or '',
'motif_refus': reservation.motif_refus or '',
} }
return JsonResponse(reservation_json, safe=True) return JsonResponse(reservation_json, safe=True)
@login_required @login_required