Files
sirh/gestion_projet/views.py

485 lines
21 KiB
Python
Raw Normal View History

2026-04-30 13:28:57 +02:00
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é nexiste pas."})
try:
pourcentage_nouveau = Decimal(pourcentage_recuperer) if pourcentage_recuperer else Decimal(0)
except (InvalidOperation, TypeError):
return JsonResponse({'success': False, 'message': "Le pourcentage saisi nest 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é nexiste 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é nexiste 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é nexiste 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é nexiste 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 lactivité '{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 na 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 nexiste 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 nexiste 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')