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 } )
2026-04-30 14:16:42 +02:00
@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 )
2026-04-30 13:28:57 +02:00
@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 )
2026-04-30 14:16:42 +02:00
2026-04-30 13:28:57 +02:00
@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 ' )