Files
sirh/gestion_projet/views.py
2026-04-30 12:19:37 +02:00

500 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 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
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')