503 lines
22 KiB
Python
503 lines
22 KiB
Python
from datetime import date
|
||
from decimal import Decimal, InvalidOperation
|
||
from django.http import JsonResponse
|
||
from django.shortcuts import redirect, render
|
||
from django.urls import reverse
|
||
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é n’existe pas."})
|
||
|
||
try:
|
||
pourcentage_nouveau = Decimal(pourcentage_recuperer) if pourcentage_recuperer else Decimal(0)
|
||
except (InvalidOperation, TypeError):
|
||
return JsonResponse({'success': False, 'message': "Le pourcentage saisi n’est pas valide."})
|
||
|
||
financement_total_actuel = sum(financement.pourcentage for financement in FinancementProjet.objects.filter(projet=projet))
|
||
if financement_total_actuel + pourcentage_nouveau > 100:
|
||
return JsonResponse({'success': False, 'message': "Le total des financements dépasse 100%."})
|
||
|
||
if bailleur_id:
|
||
FinancementProjet.objects.create(
|
||
projet=projet,
|
||
bailleur_id=bailleur_id,
|
||
pourcentage=pourcentage_nouveau
|
||
)
|
||
return JsonResponse({'success': True, 'message': "Financement ajouté avec succès."})
|
||
else:
|
||
return JsonResponse({'success': False, 'message': "Aucun bailleur sélectionné."})
|
||
return JsonResponse({'success': False, 'message': "Requête invalide."})
|
||
|
||
@login_required
|
||
def modification_projet(request, projet_id):
|
||
"""Vue pour éditer un projet existant via un formulaire pré-rempli"""
|
||
try:
|
||
projet = Projet.objects.get(id=projet_id)
|
||
except Projet.DoesNotExist:
|
||
messages.error(request, "Le projet spécifié n’existe pas.")
|
||
return redirect('projet-index')
|
||
|
||
if request.method == "POST":
|
||
form = ProjetForm(request.POST)
|
||
if form.is_valid():
|
||
form.save()
|
||
messages.success(request, "Le projet a été modifié avec succès.")
|
||
return redirect('projet-index')
|
||
messages.error(request, "Erreur lors de la modification du projet.")
|
||
form = ProjetForm(instance=projet)
|
||
return render(request, 'gestion_projet/projet-edit.html', {'form': form, 'projets': projet})
|
||
|
||
@login_required
|
||
def suppression_projet(request, id):
|
||
"""Vue pour supprimer un projet spécifique après confirmation de l'utilisateur"""
|
||
try:
|
||
projet = Projet.objects.get(id=id)
|
||
except Projet.DoesNotExist:
|
||
messages.error(request, "Le projet spécifié n’existe pas.")
|
||
return redirect('projet-index')
|
||
|
||
if request.method == "POST":
|
||
projet.delete()
|
||
messages.success(request, "Le projet a été supprimé avec succès.")
|
||
return redirect('projet-index')
|
||
|
||
@login_required
|
||
def affecter_employe_projet(request, projet_id):
|
||
"""Vue pour affecter un employé à un projet avec vérification des contraintes d'affectation"""
|
||
try:
|
||
projet = Projet.objects.get(id=projet_id)
|
||
except Projet.DoesNotExist:
|
||
messages.error(request, "Le projet spécifié n’existe pas.")
|
||
return redirect('projet-index')
|
||
|
||
if request.method == 'POST':
|
||
form = AffectationForm(request.POST)
|
||
if form.is_valid():
|
||
employe = Employe.objects.get(id=form.cleaned_data['employe'].id)
|
||
date_fin_affectation = form.cleaned_data['date_fin_daffectation']
|
||
temps_nouveau = form.cleaned_data['temps_affectation']
|
||
date_affectation = form.cleaned_data['date_affectation']
|
||
|
||
if (date_fin_affectation and date_affectation):
|
||
total_affectation = (
|
||
Affectation.objects.filter(employe=employe)
|
||
.aggregate(total_pourcentage_affectation=Sum('temps_affectation'))
|
||
['total_pourcentage_affectation'] or 0
|
||
)
|
||
if (date_fin_affectation < date_affectation):
|
||
messages.warning(request, "La date de fin d'affectation ne peut pas être antérieure à la date de début.")
|
||
return redirect('projet-index')
|
||
elif date_fin_affectation > projet.date_fin:
|
||
messages.warning(request, f"La date de fin de l'affectation ({date_fin_affectation}) ne peut pas dépasser la date de fin du projet ({projet.date_fin}).")
|
||
return redirect('projet-index')
|
||
elif total_affectation + temps_nouveau > 100:
|
||
messages.warning(
|
||
request,
|
||
f"Les pourcentages d'affectation de l'employé {employe.first_name} {employe.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)."
|
||
)
|
||
return redirect('projet-index')
|
||
|
||
Affectation.objects.update_or_create(
|
||
projet=projet,
|
||
employe=employe,
|
||
defaults={
|
||
'date_affectation': form.cleaned_data['date_affectation'],
|
||
'date_fin_daffectation': date_fin_affectation,
|
||
'role': form.cleaned_data['role'],
|
||
'temps_affectation': temps_nouveau
|
||
}
|
||
)
|
||
form = AffectationForm(initial={'projet': projet})
|
||
messages.error(request, "Erreur : Formulaire non valide.")
|
||
return redirect('projet-index')
|
||
|
||
def modifier_financement_projet(request, financement_id):
|
||
try:
|
||
financement = FinancementProjet.objects.get(id=financement_id)
|
||
except FinancementProjet.DoesNotExist:
|
||
messages.error(request, "Le financement spécifié n’existe pas.")
|
||
return redirect('projet-index')
|
||
projet = financement.projet
|
||
if request.method == 'POST':
|
||
try:
|
||
nouveau_pourcentage = Decimal(request.POST.get('pourcentage', '0'))
|
||
except InvalidOperation:
|
||
messages.error(request, "Le pourcentage saisi est invalide.")
|
||
return redirect('projet-index')
|
||
pourcentage_total_financement = (
|
||
FinancementProjet.objects.filter(projet=projet)
|
||
.exclude(id=financement.id)
|
||
.aggregate(total_financement=Sum('pourcentage'))['pourcentage_total_financement'] or 0
|
||
)
|
||
if pourcentage_total_financement + nouveau_pourcentage > 100:
|
||
messages.error(request, f"Le total des financements dépasse 100% ({pourcentage_total_financement + nouveau_pourcentage}%).")
|
||
return redirect('projet-index')
|
||
financement.pourcentage = nouveau_pourcentage
|
||
financement.save()
|
||
messages.success(request, "Financement modifié avec succès.")
|
||
return redirect('projet-index')
|
||
|
||
@login_required
|
||
def activites_projet(request):
|
||
try:
|
||
employe = Employe.objects.get(user=request.user)
|
||
except Employe.DoesNotExist:
|
||
messages.error(request, "Impossible d'accéder au menu 'Suivi des activités' car votre profil Utilisateur n'est lié à aucun profil Employe. Veuillez contacter l'administrateur.")
|
||
return redirect("gestion_conges:conge")
|
||
|
||
try:
|
||
Affectation.objects.get(employe=employe, date_fin_daffectation__gte = timezone.now().date(), role='chef_projet')
|
||
except Affectation.DoesNotExist :
|
||
messages.error(request, "Seuls les chefs de projet ont accès à l'onglet 'Suivi des Activités'")
|
||
return redirect("gestion_conges:conge")
|
||
|
||
projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
|
||
if projet :
|
||
context = {
|
||
**model_to_dict(projet),
|
||
"nom_projet": projet.projet.nom_projet,
|
||
"budget_total": projet.projet.budget,
|
||
"budget_RH": projet.projet.budget_RH,
|
||
"form_ajout_activite": ActiviteProjetForm(),
|
||
"form_ajout_document": DocumentProjetForm(),
|
||
"form_ajout_livrable": LivrablesLivresForm(),
|
||
}
|
||
else :
|
||
context = {
|
||
"form_ajout_activite": ActiviteProjetForm(),
|
||
"form_ajout_document": DocumentProjetForm(),
|
||
"form_ajout_livrable": LivrablesLivresForm(),
|
||
}
|
||
return render(request, 'gestion_projet/suivi_activite.html', context)
|
||
|
||
|
||
@login_required
|
||
def ajouter_activite_projet(request):
|
||
"""Vue pour ajouter une activité à un projet spécifique via un formulaire"""
|
||
employe = Employe.objects.get(user=request.user)
|
||
projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
|
||
|
||
if request.method == "POST":
|
||
form = ActiviteProjetForm(request.POST)
|
||
if form.is_valid():
|
||
activite = form.save(commit=False)
|
||
activite.projet = projet.projet
|
||
activite.budget_depense = request.POST["budget_prevu"]
|
||
activite.save()
|
||
messages.success(request, "Activité ajoutée avec succès !")
|
||
else:
|
||
messages.error(request, "Erreur : vérifiez les informations saisies.")
|
||
return redirect('gestion_projet:activites-projet')
|
||
|
||
@login_required
|
||
def liste_activites_projet(request):
|
||
"""Vue pour retourner la liste des activités d'un projet spécifique"""
|
||
employe = Employe.objects.get(user=request.user)
|
||
projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
|
||
if projet:
|
||
activites = ActiviteProjet.objects.filter(projet_id=projet.projet.id_projet).order_by('-date_debut')
|
||
else:
|
||
activites = []
|
||
data = []
|
||
for a in activites:
|
||
data.append({
|
||
"id": a.id,
|
||
"titre": a.titre,
|
||
"date_debut": a.date_debut,
|
||
"date_fin": a.date_fin,
|
||
"statut": a.statut,
|
||
"budget_prevu": a.budget_prevu,
|
||
"budget_depense": a.budget_depense,
|
||
"motif_changement_budget": a.motif_changement_budget,
|
||
"besoin_ressource_materielle": a.besoin_ressource_materielle,
|
||
"description": a.description,
|
||
})
|
||
return JsonResponse(data, safe=False)
|
||
|
||
@login_required
|
||
def liste_livrables_activite(request, activite_id):
|
||
"""Vue pour retourner la liste des livrables attendus d'une activité spécifique"""
|
||
livrables = LivrablesLivres.objects.filter(activite__id=activite_id)
|
||
data = []
|
||
for livrable in livrables:
|
||
print(livrable.fichier.url)
|
||
data.append({
|
||
"titre": livrable.nom,
|
||
"lien": livrable.fichier.url if livrable.fichier else "",
|
||
})
|
||
return JsonResponse(data, safe=False)
|
||
|
||
@login_required
|
||
def mises_a_jour_depense_activite(request):
|
||
"""Vue pour retourner la liste des activités d'un projet spécifique avec leurs dépenses mises à jour"""
|
||
if request.method == "POST":
|
||
activite_id = request.POST.get("id_activite")
|
||
budget_depense = request.POST.get("budget_depense")
|
||
motif = request.POST.get("motif", "").strip()
|
||
try:
|
||
activite = ActiviteProjet.objects.get(id=activite_id)
|
||
activite.budget_depense = Decimal(budget_depense)
|
||
if Decimal(budget_depense) != activite.budget_prevu:
|
||
activite.motif_changement_budget = motif
|
||
else:
|
||
activite.motif_changement_budget = ""
|
||
activite.save()
|
||
messages.success(request, f"Dépenses mises à jour pour l’activité '{activite.titre}'.")
|
||
except (ActiviteProjet.DoesNotExist, InvalidOperation):
|
||
messages.error(request, "Erreur lors de la mise à jour des dépenses.")
|
||
return redirect("gestion_projet:activites-projet")
|
||
|
||
@login_required
|
||
def ajouter_livrables_projet(request):
|
||
"""Vue pour ajouter un livrable à une activité de projet spécifique via un formulaire"""
|
||
|
||
if request.method == "POST":
|
||
form = LivrablesLivresForm(request.POST, request.FILES)
|
||
if form.is_valid():
|
||
form.save()
|
||
messages.success(request, "Livrable ajouté avec succès !")
|
||
else:
|
||
messages.error(request, "Erreur : vérifiez les informations saisies.")
|
||
return redirect('gestion_projet:activites-projet')
|
||
|
||
@login_required
|
||
def ajouter_document_projet(request):
|
||
"""Ajoute un document à un projet"""
|
||
employe = Employe.objects.get(user=request.user)
|
||
projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
|
||
if request.method == "POST":
|
||
form = DocumentProjetForm(request.POST, request.FILES)
|
||
if form.is_valid():
|
||
document = form.save(commit=False)
|
||
document.projet = projet.projet
|
||
document.save()
|
||
messages.success(request, "Document ajouté avec succès !")
|
||
else:
|
||
messages.error(request, "Erreur : le document n’a pas pu être enregistré.")
|
||
return redirect('gestion_projet:activites-projet')
|
||
|
||
def liste_documents_projet(request):
|
||
employe = Employe.objects.get(user=request.user)
|
||
projet = Affectation.objects.filter(employe=employe, role='chef_projet', date_fin_daffectation__gte=timezone.now().date()).select_related('projet').first()
|
||
if projet:
|
||
documents = DocumentProjet.objects.filter(projet__id_projet=projet.projet.id_projet)
|
||
else:
|
||
documents = []
|
||
data = []
|
||
for d in documents:
|
||
data.append({
|
||
"nom_document": d.nom_document,
|
||
"numero": d.numero,
|
||
"date_validite": d.date_validite,
|
||
"lien_document": d.fichier.url if d.fichier else "",
|
||
})
|
||
return JsonResponse(data, safe=False)
|
||
|
||
def modifier_activite_projet(request, id):
|
||
"""Vue pour modifier une activité de projet spécifique via un formulaire pré-rempli"""
|
||
try:
|
||
activite = ActiviteProjet.objects.get(id=id)
|
||
except ActiviteProjet.DoesNotExist:
|
||
messages.error(request, "L'activité spécifiée n’existe pas.")
|
||
return redirect('activites-projet')
|
||
if request.method == 'POST':
|
||
form = ActiviteProjetForm(request.POST, instance=activite)
|
||
if form.is_valid():
|
||
activite.besoin_ressource_materielle = bool(request.POST.get("besoin_ressource_materielle"))
|
||
form.save()
|
||
messages.success(request, f"Activité « {activite.titre} » modifiée avec succès.")
|
||
else:
|
||
messages.error(request, "Erreur lors de la modification de l'activité.")
|
||
return redirect('activites-projet')
|
||
|
||
form = ActiviteProjetForm(instance=activite)
|
||
return render(
|
||
request,
|
||
'gestion_projet/activite.html', {
|
||
'form': form,
|
||
'activite': activite,
|
||
}
|
||
)
|
||
|
||
def annuler_activite_projet(request):
|
||
"""Vue pour annuler une activité de projet spécifique après confirmation de l'utilisateur"""
|
||
print(request.POST)
|
||
if request.method != "POST":
|
||
messages.error(request, "Requête invalide.")
|
||
return redirect('gestion_projet:activites-projet')
|
||
try:
|
||
activite = ActiviteProjet.objects.get(id=request.POST.get('id_activite'))
|
||
except ActiviteProjet.DoesNotExist:
|
||
messages.error(request, "L'activité spécifiée n’existe pas.")
|
||
return redirect('gestion_projet:activites-projet')
|
||
if request.method == "POST":
|
||
activite.annuler = True
|
||
activite.motif_annulation = request.POST.get("motif_annulation", "").strip()
|
||
activite.save()
|
||
messages.success(request, f"L'activité '{activite.titre}' a été annulée avec succès.")
|
||
return redirect('gestion_projet:activites-projet') |