Ajout de la prise en charge des photos de profil

This commit is contained in:
2026-04-30 13:28:57 +02:00
committed by Soriba SYLLA
parent 7ee14e7b3f
commit 0047b1f91c
276 changed files with 45898 additions and 1 deletions

View File

6
gestion_projet/admin.py Normal file
View File

@@ -0,0 +1,6 @@
from django.contrib import admin
from .models import DomaineDeRecherche
@admin.register(DomaineDeRecherche)
class DomaineDeRecherche(admin.ModelAdmin):
list_display = ('nom',)

176
gestion_projet/forms.py Normal file
View File

@@ -0,0 +1,176 @@
from django import forms
from gestion_projet.models import Projet
from .models import (
ActiviteProjet,
Bailleur,
DocumentProjet,
FinancementProjet,
LivrablesLivres,
DomaineDeRecherche
)
class ProjetForm(forms.ModelForm):
"""Formulaire de création et de modification d'un projet, avec validation des dates et personnalisation des champs."""
class Meta:
model = Projet
fields = (
'id_projet',
'nom_projet',
'date_debut',
'date_fin',
'numero_convention',
'domaine_recherche',
'type_projet',
'budget',
'budget_RH',
'description'
)
# domaine_recherche = forms.ModelMultipleChoiceField(
# queryset=DomaineDeRecherche.objects.all(),
# to_field_name="nom",
# required=False
# )
widgets = {
'id_projet': forms.TextInput(attrs={'class': "form-control"}),
'nom_projet': forms.TextInput(attrs={'class': "form-control"}),
'numero_convention': forms.TextInput(attrs={'class': "form-control"}),
'domaine_recherche': forms.SelectMultiple(attrs={'class': "form-control"}),
'type_projet': forms.Select(attrs={'class': "form-select"}),
'budget': forms.NumberInput(attrs={'class': "form-control"}),
'budget_RH': forms.NumberInput(attrs={'class': "form-control"}),
'description': forms.Textarea(attrs={'class': "form-control"}),
'date_debut': forms.DateInput(attrs={'type': 'date', 'class': "form-control"}),
'date_fin': forms.DateInput(attrs={'type': 'date', 'class': "form-control"}),
}
class BailleurForm(forms.ModelForm):
"""
Formulaire de création et de modification d'un bailleur,
avec validation des champs et personnalisation des labels.
"""
class Meta:
model = Bailleur
fields = ('nom_organisme', 'contact', 'email', 'pays')
widgets = {
'nom_organisme':forms.TextInput(attrs={
'class':'form-control',
}),
'contact':forms.TextInput(attrs={
'class':'form-control',
}),
'email':forms.TextInput(attrs={
'class':'form-control',
}),
'pays':forms.TextInput(attrs={
'class':'form-control',
}),
}
class DocumentProjetForm(forms.ModelForm):
"""
Formulaire pour ajouter ou modifier un document associé à un projet,
avec validation des champs et personnalisation des labels.
"""
class Meta:
model = DocumentProjet
fields = [
'nom_document',
'numero',
'date_validite',
'fichier',
'description'
]
widgets = {
'nom_document': forms.Select(attrs={'class': 'form-select'}),
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
'numero': forms.TextInput(attrs={'class': 'form-control'}),
'date_validite': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
'fichier': forms.ClearableFileInput(attrs={'class': 'form-control'}),
}
class ActiviteProjetForm(forms.ModelForm):
"""Formulaire pour créer ou modifier une activité de projet, avec validation des champs et personnalisation des widgets."""
class Meta:
model = ActiviteProjet
fields = (
'titre',
'date_debut',
'date_fin',
'besoin_ressource_materielle',
'budget_prevu',
'description',
)
widgets = {
'titre':forms.TextInput(attrs={
'class':'form-control',
'placeholder':'Titre de lactivité'
}),
'description':forms.Textarea(attrs={
'class':'form-control',
'rows':3,
'placeholder':'Description de lactivité'
}),
'date_debut':forms.DateInput(attrs={
'class':'form-control',
'type':'date'
}),
'date_fin':forms.DateInput(attrs={
'class':'form-control',
'type':'date'
}),
'besoin_ressource_materielle': forms.Textarea(attrs={
'class':'form-control',
'rows':3,
'placeholder':'Besoin de ressources matérielles'
}),
'budget_prevu': forms.NumberInput(attrs={
'class':'form-control',
'placeholder':'Budget prévu'
}),
}
class FinancementProjetFrom(forms.ModelForm):
"""Formulaire pour créer ou modifier le financement relatif à un projet."""
class Meta:
model = FinancementProjet
fields = (
'projet',
'bailleur',
'pourcentage',
)
widgets = {
'projet':forms.Select(attrs={
'class':'form-select',
}),
'bailleur':forms.Select(attrs={
'class':'form-select',
}),
'pourcentage':forms.NumberInput(attrs={
'class':'form-control',
}),
}
class LivrablesLivresForm(forms.ModelForm):
"""Formulaire pour créer ou modifier un livrable livré dans le cadre d'une activité de projet."""
class Meta:
model = LivrablesLivres
fields = (
'activite',
'nom',
'fichier',
)
widgets = {
'activite': forms.Select(attrs={
'class':'form-select',
}),
'nom': forms.TextInput(attrs={
'class':'form-control',
'placeholder':'Nom du livrable'
}),
'fichier': forms.ClearableFileInput(attrs={
'class':'form-control',
}),
}

View File

@@ -0,0 +1,107 @@
# Generated by Django 5.2.13 on 2026-04-17 12:03
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ActiviteProjet',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('titre', models.CharField(max_length=200, verbose_name="Titre de l'activité")),
('description', models.TextField(blank=True, null=True, verbose_name="Description de l'activité")),
('date_debut', models.DateField(verbose_name='Date de début')),
('date_fin', models.DateField(verbose_name='Date de fin')),
('annuler', models.BooleanField(default=False, verbose_name="Annuler l'activité")),
('motif_annulation', models.TextField(blank=True, null=True, verbose_name="Motif d'annulation")),
('motif_changement_budget', models.TextField(blank=True, null=True, verbose_name='Motif de changement de budget')),
('budget_prevu', models.DecimalField(decimal_places=2, default=0, max_digits=15, verbose_name='Budget prévu')),
('budget_depense', models.DecimalField(decimal_places=2, default=0, max_digits=15, verbose_name='Budget dépensé')),
('besoin_ressource_materielle', models.TextField(verbose_name='Besoin de ressources matérielles')),
],
),
migrations.CreateModel(
name='Bailleur',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nom_organisme', models.CharField(max_length=200, unique=True)),
('contact', models.CharField(blank=True, max_length=100, null=True)),
('email', models.EmailField(blank=True, max_length=254, null=True)),
('pays', models.CharField(blank=True, max_length=100, null=True)),
],
),
migrations.CreateModel(
name='DomaineDeRecherche',
fields=[
('nom', models.CharField(choices=[('sciences_sociales', 'Sciences sociales'), ('naturelles', 'Naturelles'), ('humaines', 'Humaines'), ('veterinaires', 'Vétérinaires')], max_length=100, primary_key=True, serialize=False, verbose_name='Domaine de recherche')),
],
options={
'verbose_name': 'Domaine de recherche',
'verbose_name_plural': 'Domaines de recherche',
},
),
migrations.CreateModel(
name='LivrablesLivres',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('nom', models.CharField(max_length=255, verbose_name='Nom du livrable')),
('fichier', models.FileField(blank=True, null=True, upload_to='fichier_livrables/')),
('activite', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.activiteprojet')),
],
),
migrations.CreateModel(
name='Projet',
fields=[
('id_projet', models.CharField(blank=True, max_length=100, primary_key=True, serialize=False, unique=True, verbose_name='ID du projet')),
('nom_projet', models.CharField(max_length=200, verbose_name='Nom du projet')),
('date_debut', models.DateField(verbose_name='Date de début')),
('date_fin', models.DateField(verbose_name='Date de fin')),
('numero_convention', models.CharField(max_length=100, verbose_name='Numéro de convention')),
('description', models.TextField(verbose_name='Description')),
('type_projet', models.CharField(choices=[('laboratoire', 'Laboratoire'), ('épidémiologie', 'Épidémiologie'), ('sciences sociales', 'Sciences sociales'), ('cliniques', 'Cliniques'), ('autre', 'Autre')], default='épidémiologie', max_length=100, verbose_name='Type de projet')),
('budget', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Budget')),
('budget_RH', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Budget RH')),
('created_at', models.DateTimeField(auto_now_add=True)),
('bailleur', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='gestion_projet.bailleur', verbose_name='Bailleur de fonds')),
('domaine_recherche', models.ManyToManyField(to='gestion_projet.domainederecherche')),
],
),
migrations.CreateModel(
name='DocumentProjet',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date_ajout', models.DateTimeField(auto_now_add=True, verbose_name="Date d'ajout")),
('nom_document', models.CharField(choices=[('protocole', 'Protocole détude'), ('ethique', "Approbation du comité d'éthique"), ('autorisation', 'Autorisation (DNLP)'), ('rapport_technique', 'Rapport technique'), ('rapport_financier', 'Rapport financier'), ('rapport_avancement', "Rapport d'avancement"), ('convention', 'Convention'), ('rapport_final', 'Rapport final'), ('autre', 'Autre')], max_length=100, verbose_name='Type de document')),
('description', models.TextField(blank=True, verbose_name='Description')),
('numero', models.CharField(blank=True, max_length=100, null=True, verbose_name='Numéro du document')),
('date_validite', models.DateField(blank=True, null=True, verbose_name='Date de validité')),
('fichier', models.FileField(upload_to='documents_projets/', verbose_name='Fichier à télécharger')),
('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='gestion_projet.projet', verbose_name='Projet')),
],
),
migrations.AddField(
model_name='activiteprojet',
name='projet',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet', verbose_name='Projet'),
),
migrations.CreateModel(
name='FinancementProjet',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('pourcentage', models.DecimalField(decimal_places=2, max_digits=5)),
('bailleur', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.bailleur')),
('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet')),
],
options={
'unique_together': {('projet', 'bailleur')},
},
),
]

View File

309
gestion_projet/models.py Normal file
View File

@@ -0,0 +1,309 @@
from django.db import models
from datetime import date
from django.utils import timezone
class Bailleur(models.Model):
"""Modèle représentant un bailleur de fonds pour les projets de recherche."""
nom_organisme = models.CharField(
max_length=200,
unique=True
)
contact = models.CharField(
max_length=100,
blank=True,
null=True
)
email = models.EmailField(
blank=True,
null=True
)
pays = models.CharField(
max_length=100,
blank=True,
null=True
)
def __str__(self):
return self.nom_organisme
class DomaineDeRecherche(models.Model):
"""Modèle représentant les domaines de recherche"""
DOMAINE_RECHERCHE = [
('sciences_sociales', 'Sciences sociales'),
('naturelles', 'Naturelles'),
('humaines', 'Humaines'),
('veterinaires', 'Vétérinaires')
]
nom = models.CharField(
max_length=100,
verbose_name="Domaine de recherche",
choices=DOMAINE_RECHERCHE,
primary_key=True
)
class Meta:
verbose_name = 'Domaine de recherche'
verbose_name_plural = 'Domaines de recherche'
def __str__(self):
return self.nom
class Projet(models.Model):
"""Modèle représentant un projet de recherche avec ses caractéristiques et son bailleur associé."""
TYPE_PROJET = [
('laboratoire', 'Laboratoire'),
('épidémiologie', 'Épidémiologie'),
('sciences sociales', 'Sciences sociales'),
('cliniques', 'Cliniques'),
('autre', 'Autre'),
]
id_projet = models.CharField(
max_length=100,
blank=True,
unique=True,
primary_key=True,
verbose_name="ID du projet"
)
nom_projet = models.CharField(
max_length=200,
verbose_name="Nom du projet"
)
date_debut = models.DateField(
verbose_name="Date de début"
)
date_fin = models.DateField(
verbose_name="Date de fin"
)
numero_convention = models.CharField(
max_length=100,
verbose_name="Numéro de convention"
)
description = models.TextField(
verbose_name="Description"
)
type_projet = models.CharField(
max_length=100,
choices=TYPE_PROJET,
default='épidémiologie',
verbose_name="Type de projet"
)
domaine_recherche = models.ManyToManyField(DomaineDeRecherche)
budget=models.DecimalField(
max_digits=12,
decimal_places=2,
verbose_name="Budget"
)
budget_RH = models.DecimalField(
max_digits=12,
decimal_places=2,
verbose_name="Budget RH"
)
created_at = models.DateTimeField(auto_now_add=True)
bailleur = models.ForeignKey(
Bailleur,
on_delete=models.SET_NULL,
null=True,
blank=True,
verbose_name="Bailleur de fonds"
)
@property
def statut(self):
if self.date_fin < date.today():
return "Terminé"
return "En cours"
@property
def avancement(self):
aujourd_hui = date.today()
if (self.date_debut and self.date_fin) and (self.date_debut < self.date_fin):
duree_projet = (self.date_fin - self.date_debut).days
temps_ecoule = (aujourd_hui - self.date_debut).days
if duree_projet > 0:
return round((temps_ecoule / duree_projet) * 100, 2)
return 0
def __str__(self):
return f"{self.nom_projet}"
class FinancementProjet(models.Model):
"""
Modèle représentant le financement d'un projet par un bailleur,
avec le pourcentage de contribution.
"""
projet = models.ForeignKey(
Projet,
on_delete=models.CASCADE
)
bailleur = models.ForeignKey(
Bailleur,
on_delete=models.CASCADE
)
pourcentage = models.DecimalField(
max_digits = 5,
decimal_places=2
)
class Meta:
unique_together = ('projet', 'bailleur')
def __str__(self):
return f"{self.bailleur.nom} - {self.projet.nom_projet} ({self.pourcentage}%)"
class DocumentProjet(models.Model):
"""Modèle représentant un document associé à un projet, avec des métadonnées et un fichier attaché."""
NOM_DOCUMENT_CHOICES = [
('protocole', 'Protocole détude'),
('ethique', "Approbation du comité d'éthique"),
('autorisation', 'Autorisation (DNLP)'),
('rapport_technique', 'Rapport technique'),
('rapport_financier', 'Rapport financier'),
('rapport_avancement', "Rapport d'avancement"),
('convention', 'Convention'),
('rapport_final', 'Rapport final'),
('autre', 'Autre'),
]
projet = models.ForeignKey(
Projet,
on_delete=models.CASCADE,
related_name='documents',
verbose_name="Projet"
)
date_ajout = models.DateTimeField(
auto_now_add=True,
verbose_name="Date d'ajout"
)
nom_document = models.CharField(
max_length = 100,
choices = NOM_DOCUMENT_CHOICES,
verbose_name="Type de document"
)
description = models.TextField(
blank = True,
verbose_name = "Description"
)
numero = models.CharField(
max_length = 100,
blank = True,
null = True,
verbose_name = "Numéro du document"
)
date_validite = models.DateField(
blank = True,
null = True,
verbose_name = "Date de validité"
)
fichier = models.FileField(
upload_to = 'documents_projets/',
verbose_name = "Fichier à télécharger"
)
def __str__(self):
return f"{self.nom_document} ({self.projet})"
class ActiviteProjet(models.Model):
"""Modèle représentant le planning d'un projet, avec des activités associées et un statut."""
projet = models.ForeignKey(
Projet,
on_delete = models.CASCADE,
verbose_name = "Projet"
)
titre = models.CharField(
max_length = 200,
verbose_name = "Titre de l'activité"
)
description = models.TextField(
blank = True,
null = True,
verbose_name = "Description de l'activité"
)
date_debut = models.DateField(verbose_name="Date de début")
date_fin = models.DateField(verbose_name="Date de fin")
annuler = models.BooleanField(
default = False,
verbose_name = "Annuler l'activité"
)
motif_annulation = models.TextField(
blank = True,
null = True,
verbose_name = "Motif d'annulation"
)
motif_changement_budget = models.TextField(
blank = True,
null = True,
verbose_name = "Motif de changement de budget"
)
budget_prevu = models.DecimalField(
max_digits = 15,
decimal_places = 2,
default = 0,
verbose_name = "Budget prévu"
)
budget_depense = models.DecimalField(
max_digits = 15,
decimal_places = 2,
default = 0,
verbose_name = "Budget dépensé"
)
besoin_ressource_materielle = models.TextField(
verbose_name="Besoin de ressources matérielles"
)
@property
def statut(self):
today = timezone.now().date()
if not self.annuler:
if self.date_fin < today:
return 'Terminé'
elif self.date_debut > today:
return 'À venir'
else:
return 'En cours'
else:
return 'Annulé'
def __str__(self):
return f"{self.titre} ({self.projet.nom_projet})"
# class LivrableAttendu(models.Model):
# """
# Modèle représentant un livrable attendu pour une activité de projet,
# avec des critères de validation.
# """
# activite = models.ForeignKey(
# ActiviteProjet,
# on_delete = models.CASCADE,
# related_name = "livrables_attendus"
# )
# nom = models.CharField(max_length=255)
# def __str__(self):
# return f"{self.nom} (Activité: {self.activite.titre})"
class LivrablesLivres(models.Model):
"""Modèle représentant un livrable livré pour une activité de projet."""
activite = models.ForeignKey(
ActiviteProjet,
on_delete = models.CASCADE
)
# nom = models.ForeignKey(
# LivrableAttendu,
# on_delete = models.CASCADE
# )
nom = models.CharField(
max_length=255,
verbose_name="Nom du livrable"
)
fichier = models.FileField(
upload_to = 'fichier_livrables/',
blank = True,
null = True
)
def __str__(self):
return self.nom

View File

@@ -0,0 +1,19 @@
const boutonEnregistrerProjet = $("btnEnregistrerProjet");
boutonEnregistrerProjet.addEventListener("click", function() {
const formulaire = $("formCreationProjet");
const formData = new FormData(formulaire);
fetch(formulaire.action, {
method: "POST",
body: formData,
headers: {
"X-CSRFToken": formData.get("csrfmiddlewaretoken")
}
})
.then(response => {
if (response.ok) {
window.location.reload();
alert("Projet enregistré avec succès !");
}
});
});

View File

@@ -0,0 +1,23 @@
const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur');
btnEnregistrerBailleur.addEventListener('click', function() {
const form = document.getElementById('formBailleur');
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': formData.get('csrfmiddlewaretoken')
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Bailleur enregistré avec succès !');
window.location.reload();
} else {
alert('Ce bailleur existe déjà dans la base de données.');
}
});
});

View File

@@ -0,0 +1,22 @@
const btn_enregistrer_financement = document.getElementById('btn_enregistrer_financement');
btn_enregistrer_financement.addEventListener('click', function() {
const form = document.getElementById('form_financement');
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': formData.get('csrfmiddlewaretoken')
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert(data.message);
window.location.reload();
}else {
alert(data.message);
}
});
});

View File

@@ -0,0 +1,64 @@
const $ = (element) => document.getElementById(element)
const url_liste_projet = $("tableau-liste-projet").dataset.url;
const tableau_liste_projet = new Tabulator("#tableau-liste-projet", {
fitColumns: true,
responsiveLayout : true,
columns: [
{title: "Projet", field: "nom_projet"},
{title: "Source de financement", field: "source_financement"},
{title: "Budget Total", field: "budget"},
{title: "Budget RH", field: "budget_RH"},
{title: "Avancement", field: "avancement", formatter: "progress"},
{title: "Statut", field: "statut"},
],
ajaxURL: url_liste_projet
})
const employes_affectes_projet = new Tabulator("#employes_affectes_projet", {
columns: [
{title: "Employé", field: "employe"},
{title: "Pourcentage d'affectation", field: "pourcentage_affectation"},
],
placeholder: "Aucun employé affecté pour ce projet",
})
const bailleurs_projet = new Tabulator("#bailleurs_projet", {
columns: [
{title: "Bailleur", field: "bailleur"},
{title: "Pourcentage de financement", field: "pourcentage_financement"},
],
placeholder: "Aucun bailleur attribué pour ce projet",
})
tableau_liste_projet.on("rowClick", function (row, rowData) {
const data = rowData.getData();
const modal = new bootstrap.Modal($("modalDetailProjet"));
$("detail_id_projet").value = data.id_projet;
$("detail_nom_projet").value = data.nom_projet;
$("detail_date_debut").value = data.date_debut;
$("detail_date_fin").value = data.date_fin;
$("detail_numero_convention").value = data.numero_convention;
$("detail_type_projet").value = data.type_projet;
Array.from($("detail_domaine_recherche").options).forEach(option => {
if (data.domaine_recherche.includes(option.value)) {
option.selected = true;
} else {
option.selected = false;
}
});
$("detail_budget").value = data.budget;
$("detail_budget_rh").value = data.budget_RH;
$("detail_description").value = data.description;
$("detail_statut").value = data.statut;
employes_affectes_projet.setData(`projet/liste-employes-par-projet/${$("detail_id_projet").value}`);
bailleurs_projet.setData(`projet/bailleurs/${data.id_projet}/`);
modal.show();
})
// $('detail-projet-form').addEventListener('submit', (e) => {
// e.preventDefault();
// new FormData($("detail-projet-form"));
// })

View File

@@ -0,0 +1,20 @@
const urlListeDocument = document.getElementById('listeDocuments').dataset.urllistedocument;
const table_liste_documents = new Tabulator(document.getElementById('listeDocuments'), {
layout: "fitColumns",
placeholder: "Aucun document trouvé",
columns: [
{ title: "Nom du Document", field: "nom_document" },
{ title: "Numéro", field: "numero" },
{ title: "Date de Validité", field: "date_validite", formatter: "datetime", formatterParams: {
inputFormat: "yyyy-MM-dd",
outputFormat: "dd/MM/yyyy"
}
},
{ title: "Lien vers le Document", field: "lien_document", formatter:"link", formatterParams:{
target:"_blank",
}
},
],
ajaxURL: urlListeDocument,
});

View File

@@ -0,0 +1,59 @@
const $ = (element) => document.getElementById(element)
const url_liste_activite = $("tableau-liste-activite").dataset.urllisteactivite
const tableau_liste_activite = new Tabulator("#tableau-liste-activite", {
columns: [
{title: "Activité", field: "titre"},
{title: "Date début", field: "date_debut"},
{title: "Date fin", field: "date_fin"},
{title: "Budget prévu", field: "budget_prevu"},
{title: "Budget dépensé", field: "budget_depense"},
{title: "Motif de changement de budget", field: "motif_changement_budget"},
{title: "Statut", field: "statut"},
],
ajaxURL: url_liste_activite,
})
tableau_liste_activite.on("rowClick", function (row, rowData) {
const data = rowData.getData();
$("idDetailActivite").value = data.id;
$("titreDetailActivite").value = data.titre;
$("descriptionDetailActivite").value = data.description;
$("date_debutDetailActivite").value = data.date_debut;
$("date_finDetailActivite").value = data.date_fin;
$("statutDetailActivite").value = data.statut;
$("budget_prevuDetailActivite").value = data.budget_prevu;
$("besoin_ressources_materiellesDetailActivite").value = data.besoin_ressource_materielle;
const modal = new bootstrap.Modal($("modalDetailActivite"));
modal.show();
fetch(`liste-des-livrables/${data.id}/`)
.then(response => response.json())
.then(livrables => {
tableau_liste_livrable.setData(livrables);
})
})
const tableau_liste_livrable = new Tabulator("#listeLivrables", {
columns: [
{title: "Livrable", field: "titre"},
{title: "Lien du livrable", field: "lien", formatter: "link", formatterParams: {blank: true}},
],
placeholder: "Aucun livrable trouvé",
})
$("btnMiseAJourDepense").addEventListener("click", function() {
const modal = new bootstrap.Modal($("modalDepenseActivite"));
bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
const idActivite = $("idDetailActivite").value;
const budgetPrevu = $("budget_prevuDetailActivite").value;
$("id_activite_depense").value = idActivite;
$("budget_prevu").value = budgetPrevu;
modal.show();
})
$("btnAnnulerActivite").addEventListener("click", function(event) {
new bootstrap.Modal($("modalAnnulerActivite")).show();
$("id_activite_annulation").value = $("idDetailActivite").value;
bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
})

View File

@@ -0,0 +1,37 @@
{% extends "BASE.html" %}
{% load static %}
{% block 'titre_page' %} Gestion des projets {% endblock %}
{% block 'contenu' %}
<button class="btn btn-primary mb-3">
<i class="bi bi-caret-left-fill"></i> Retour
</button>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<div class="row d-flex justify-content-center">
<div class="col-8 card bordered rounded py-4 px-2 ">
<h5>Ajout du financement au projet (Nom du projet ici)</h5>
<hr>
<form method="POST" action="{% url 'gestion_projet:creation-projet' %}">
{% csrf_token %}
{{ formulaire_creation_projet.as_p }}
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-danger">Annuler</button>
<a href="{% url 'gestion_projet:ajouter_financement' %}" class="btn btn-primary ms-2">Enregistrer et ajouter un financement</a>
</div>
</form>
</div>
</div>
{% endblock %}
{% block 'modal' %}
{% include "gestion_projet/parts/modalAjoutProjet.html" %}
{% endblock %}
{% block 'js' %}
<script type="text/javascript" src="{% static 'gestion_projet/js/index.js' %}"></script>
{% endblock %}

View File

@@ -0,0 +1,32 @@
{% extends "BASE.html" %}
{% load static %}
{% block 'titre_page' %} Gestion des projets {% endblock %}
{% block 'contenu' %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% comment %} <h3>Enregistrement d'un nouveau projet</h3> {% endcomment %}
<div class="row d-flex justify-content-center">
<div class="col-8 card bordered rounded py-4 px-2 ">
<h5>Enregistrement d'un nouveau projet</h5>
<hr>
<form method="POST" action="{% url 'gestion_projet:creation-projet' %}">
{% csrf_token %}
{{ formulaire_creation_projet.as_p }}
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-danger">Annuler</button>
<button class="btn btn-primary ms-2">Enregistrer</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block 'modal' %}
{% endblock %}
{% block 'js' %}
{% endblock %}

View File

@@ -0,0 +1,61 @@
{% extends "BASE.html" %}
{% load static %}
{% load tags_personnaliser %}
{% block 'titre_page' %} Gestion des projets {% endblock %}
{% block 'contenu' %}
<h3>Gestion des projets</h3>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<div class="row">
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Projets en cours</span>
<h3>{{projet_en_cours}}</h3>
</div>
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4 mx-2">
<span class="fs-5">Budget Total (GNF)</span>
<h3>{{budget_total}}</h3>
</div>
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Personnel sous projet</span>
<h3>{{nombre_personnel}}</h3>
</div>
</div>
<div class="row mt-4">
<div class="col d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-4" >La liste des projets </h5>
{% if user|has_group:"ressource_humaine" %}
<div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalProjet">
<i class="bi bi-plus-circle"></i> Ajouter un projet
</button>
<button class="btn btn-info" data-bs-toggle="modal" data-bs-target="#modalBailleur">
<i class="bi bi-person"></i> Ajouter un bailleur
</button>
</div>
{% endif %}
</div>
<div class="row">
<div class="col">
<div id="tableau-liste-projet" data-url="{% url 'gestion_projet:liste-projet' %}"></div>
</div>
</div>
</div>
{% endblock %}
{% block 'modal' %}
{% include "gestion_projet/parts/modalAjoutProjet.html" %}
{% include "gestion_projet/parts/modalFinancement.html" %}
{% include "gestion_projet/parts/creation_bailleur.html" %}
{% include "gestion_projet/parts/modalDetailProjet.html" %}
{% endblock %}
{% block 'js' %}
<script type="text/javascript" src="{% static 'gestion_projet/js/index.js' %}"></script>
<script type="text/javascript" src="{% static 'gestion_projet/js/creation_projet.js' %}"></script>
<script type="text/javascript" src="{% static 'gestion_projet/js/enregistrement_bailleur.js' %}"></script>
<script type="text/javascript" src="{% static 'gestion_projet/js/enregistrement_financement.js' %}"></script>
{% endblock %}

View File

@@ -0,0 +1,20 @@
<div class="modal fade" id="modalBailleur" tabindex="-1" aria-labelledby="modalBailleurLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalBailleurLabel">Ajouter un Bailleur</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<form id="formBailleur" method="POST" action="{% url 'gestion_projet:creation-bailleur' %}">
{% csrf_token %}
{{ form_ajout_bailleur.as_p }}
</form>
</div>
<div class="modal-footer">
<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>
</div>
</div>
</div>

View File

@@ -0,0 +1,16 @@
<div class="modal fade" id="modalListeDocument" tabindex="-1" aria-labelledby="modalListeDocumentLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="modalListeDocumentLabel">Liste des Documents</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div id="listeDocuments" data-urllistedocument="{% url 'gestion_projet:liste-documents-projet' %}"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div class="modal fade" id="modalAjoutActivite" tabindex="-1" aria-labelledby="modalAjoutActiviteLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalAjoutActiviteLabel">Ajouter une Activité</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form method="POST" action="{% url 'gestion_projet:ajouter-activite' %}">
{% csrf_token %}
<div class="modal-body">
{{ form_ajout_activite.as_p }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary">Ajouter</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div class="modal fade" id="modalAjoutDocument" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Ajouter un Document</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST" action="{% url 'gestion_projet:ajouter-document' %}" enctype="multipart/form-data">
{% csrf_token %}
<div class="modal-body">
{{ form_ajout_document.as_p }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-primary">Ajouter</button>
</div>
</form>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div class="modal fade" id="modalAjouterLivrable" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Ajouter un livrable - (Nom du livrable)</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="post" action="{% url 'gestion_projet:ajouter-livrable' %}" enctype="multipart/form-data">
{% csrf_token %}
{{ form_ajout_livrable.as_p }}
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-success">Enregistrer</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<!-- Modale Projet -->
<div class="modal fade" id="modalProjet" tabindex="-1" aria-labelledby="modalProjetLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title" id="modalProjetLabel">Ajouter un projet</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body p-3">
<form method="POST" id="formCreationProjet" action="{% url 'gestion_projet:creation-projet' %}">
{% csrf_token %}
{{ formulaire_creation_projet.as_p }}
</form>
</div>
<div class="modal-footer">
<button type="submit" id="btnEnregistrerProjet" class="btn btn-success">Enregistrer</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,26 @@
<div class="modal fade" id="modalAnnulerActivite" tabindex="-1" aria-labelledby="modalAnnulerActiviteLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title fw-bold" id="modalAnnulerActiviteLabel">Annuler l'activité</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<form method="POST" action="{% url 'gestion_projet:annuler-activite' %}">
{% csrf_token %}
<input type="hidden" name="id_activite" id="id_activite_annulation">
<div class="mb-3">
<label for="motif_annulation" class="form-label">Motif d'annulation</label>
<textarea class="form-control" name="motif_annulation" id="motif_annulation" rows="4" required></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-danger">
<i class="bi bi-x-circle"></i> Confirmer l'annulation
</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,58 @@
<div class="modal fade" id="modalDetailActivite" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-orange-dark">
<h5 class="modal-title">Détails de l'activité</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="">
<input type="hidden" id="idDetailActivite">
<div class="form-group mb-2">
<label>Titre :</label>
<input type="text" class="form-control" id="titreDetailActivite">
</div>
<div class="form-group mb-2">
<label>Description :</label>
<textarea class="form-control" id="descriptionDetailActivite"></textarea>
</div>
<div class="form-group mb-2">
<label>Date de début :</label>
<input type="date" class="form-control" id="date_debutDetailActivite">
</div>
<div class="form-group mb-2">
<label>Date de fin :</label>
<input type="date" class="form-control" id="date_finDetailActivite">
</div>
<div class="form-group mb-2">
<label>Statut:</label>
<input type="text" class="form-control" id="statutDetailActivite">
</div>
<div class="form-group mb-2">
<label>Budget prévu :</label>
<input type="number" class="form-control" id="budget_prevuDetailActivite">
</div>
<div class="form-group mb-2">
<label>Besoin de ressources matérielles:</label>
<textarea class="form-control" id="besoin_ressources_materiellesDetailActivite"></textarea>
</div>
<p><strong>Liste des livrables :</strong></p>
<div id="listeLivrables"></div>
<button class="btn btn-danger d-block mx-auto" id="btnAnnulerActivite">
Annuler cette activité
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-success" id="btnMiseAJourDepense">
<i class="bi bi-plus-circle"></i> Mise à jour du budget
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjouterLivrable">
<i class="bi bi-plus-circle"></i> Ajouter un livrable
</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,115 @@
{% load tags_personnaliser %}
<div class="modal fade" id="modalDetailProjet{{ activite.id }}" tabindex="-1">
<div class="modal-dialog ">
<div class="modal-content">
<div class="modal-header ">
<h5 class="modal-title">Détails du projet </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row g-3">
<div class="col" id="detail-projet-container">
<form method='post' action="{% url 'gestion_projet:mises-a-jour-projet' %}" id="detail-projet-form">
{% csrf_token %}
<div class="form-group mb-2">
<label>Id projet:</label>
<input type="text" class="form-control" id="detail_id_projet" name='id_projet' readonly>
</div>
<div class="form-group mb-2">
<label> Nom projet :</label>
<input type="text" class="form-control" id="detail_nom_projet" name='nom_projet'>
</div>
<div class="form-group mb-2">
<label>Numero de convention:</label>
<input type="text" class="form-control" id="detail_numero_convention" name="numero_convention">
</div>
<div class="form-group mb-2">
<label>Date de début :</label>
<input type="date" class="form-control" id="detail_date_debut" name='date_debut'>
</div>
<div class="form-group mb-2">
<label>Date de fin :</label>
<input type="date" class="form-control" id="detail_date_fin" name='date_fin'>
</div>
<div class="form-group mb-2">
<label> Type de projet </label>
<select class="form-select" id="detail_type_projet" name='type_projet'>
<option value="laboratoire">Laboratoire</option>
<option value="épidémiologie">Épidémiologie</option>
<option value="sciences sociales">Sciences sociales</option>
<option value="cliniques">Cliniques</option>
<option value="autre">Autre</option>
</select>
</div>
<div class="form-group mb-2">
<label> Domaine de recherche </label>
<select class="form-select" multiple id="detail_domaine_recherche" name="domaine_recherche">
<option value="sciences_sociales">Sciences sociales</option>
<option value="naturelles">Naturelles</option>
<option value="humaines">Humaines</option>
<option value="veterinaires">Vétérinaires</option>
</select>
</div>
<div class="form-group mb-2">
<label>Budget :</label>
<input type="number" class="form-control" id="detail_budget" name='budget'>
</div>
<div class="form-group mb-2">
<label>Budget RH:</label>
<input type="number" class="form-control" id="detail_budget_rh" name='budget_RH'>
</div>
<div class="form-group mb-2">
<label>Description :</label>
<textarea class="form-control" id="detail_description" name='description'></textarea>
</div>
<div class="form-check-group mb-2">
<label class="form-check-label">Statut :</label>
<input type="text" class="form-control" id="detail_statut" readonly>
</div>
<button type='submit' class="btn btn-warning">
<i class="bi bi-pencil"></i> Modifier Projet
</button>
</form>
<hr class="my-4">
<div class="accordion" id="accordionDetailProjet">
<div class="accordion-item">
<h2 class="accordion-header" id="employesAffectes">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
Employés Affectés
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="employesAffectes" data-bs-parent="#accordionDetailProjet">
<div class="accordion-body">
<div id="employes_affectes_projet"></div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="bailleursProjet">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
Liste des bailleurs
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="bailleursProjet" data-bs-parent="#accordionDetailProjet">
<div class="accordion-body">
<div id="bailleurs_projet"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
{% if user|has_group:"ressource_humaine" %}
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#modal_ajout_financement">
<i class="bi bi-plus-circle"></i> Ajouter un financement
</button>
{% endif %}
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle"></i> Fermer
</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div class="modal fade" id="modal_ajout_financement" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Ajout de financement</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button>
</div>
<div class="modal-body">
<form method="POST" id="form_financement" action="{% url 'gestion_projet:ajouter_financement' %}">
{% csrf_token %}
{{ form_ajout_financement.as_p }}
</form>
</div>
<div class="modal-footer">
<button type="submit" id="btn_enregistrer_financement" class="btn btn-success">Enregistrer</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,36 @@
<div class="modal fade" id="modalDepenseActivite" tabindex="-1" aria-labelledby="modalDepenseActiviteLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalDepenseActiviteLabel">
<i class="bi bi-cash-stack me-2"></i> Mise à jour des dépenses
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form method="POST" action="{% url 'gestion_projet:mettre-a-jour-depense' %}">
{% csrf_token %}
<input type="hidden" name="id_activite" id="id_activite_depense">
<div class="mb-2">
<label class="form-label">Budget prévu (GNF)</label>
<input type="number" class="form-control" name="budget_prevu" id="budget_prevu" value="" disabled>
</div>
<div class="mb-2">
<label for="budget_depense" class="form-label">Montant dépensé (GNF)</label>
<input type="number" name="budget_depense" id="budget_depense" class="form-control" required>
</div>
<div class="mb-2">
<label class="form-label">Motif de différence</label>
<textarea class="form-control" name="motif" rows="3"></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Annuler</button>
<button type="submit" class="btn btn-success">
<i class="bi bi-save"></i> Enregistrer
</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,64 @@
{% extends "BASE.html" %}
{% load static %}
{% block 'titre_page' %} Gestion des projets {% endblock %}
{% block 'contenu' %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-{% if message.tags == "error" %}danger{% else %}success{% endif %} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<h5 class="mb-4 fw-bold text-uppercase text-orange-dark">
<i class="bi bi-kanban-fill me-2"></i> Suivi des Activités ({{ nom_projet }} )
</h5>
<div class="row d-flex justify-content-center mb-4">
<div class="col text-white bg-success d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Budget Total (GNF)</span>
<h3>{{ budget_total }}</h3>
</div>
<div class="col text-white bg-info d-flex flex-column justify-content-center align-items-center border rounded mx-3 p-4">
<span class="fs-5">Budget RH (GNF)</span>
<h3>{{ budget_RH }}</h3>
</div>
<div class="col text-white bg-warning d-flex flex-column justify-content-center align-items-center border rounded p-4">
<span class="fs-5">Budget Dépensé (GNF)</span>
<h3>{{budget_depense}}</h3>
</div>
</div>
<div class="row">
<div class="mt-4 d-flex justify-content-between mb-3">
<h5><i class="bi bi-people"></i> La liste des Activités</h5>
<div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjoutActivite">
<i class="bi bi-plus-circle me-2"></i> Ajouter une Activité
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalAjoutDocument">
<i class="bi bi-plus-circle me-2"></i> Ajouter un document
</button>
<button class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modalListeDocument">
Documents du projet
</button>
</div>
</div>
</div>
<div class="table-responsive">
<div id="tableau-liste-activite" data-urllisteactivite="{% url 'gestion_projet:liste-activites-projet' %}"></div>
</div>
{% endblock %}
{% block 'modal' %}
{% include "gestion_projet/parts/modalAjoutActivite.html" %}
{% include "gestion_projet/parts/modalDetailActivite.html" %}
{% include "gestion_projet/parts/modalAjoutDocument.html" %}
{% include "gestion_projet/parts/modalMiseAJourDepense.html" %}
{% include "gestion_projet/parts/modalAjoutLivrable.html" %}
{% include "gestion_projet/parts/liste_document_projet.html" %}
{% include "gestion_projet/parts/modalAnnulerActivite.html" %}
{% endblock %}
{% block 'js' %}
<script type="text/javascript" src="{% static 'gestion_projet/js/suivi-activites.js' %}"></script>
<script type="text/javascript" src="{% static 'gestion_projet/js/liste_documents_projet.js' %}"></script>
{% endblock %}

3
gestion_projet/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

122
gestion_projet/urls.py Normal file
View File

@@ -0,0 +1,122 @@
from django.urls import path
from . import views
app_name = 'gestion_projet'
urlpatterns = [
path(
'',
views.index,
name='index'
),
path(
'modifier-financement/<int:financement_id>/',
views.modifier_financement_projet,
name='modifier-financement'
),
path(
'projet/creation/',
views.creation_projet,
name='creation-projet'
),
path(
'projet/modifier/<int:projet_id>/',
views.modification_projet,
name='modifier-projet'
),
path(
'liste-projet/',
views.liste_projet,
name='liste-projet'
),
path(
'projet/suppression/<int:id>/',
views.suppression_projet,
name='projet-suppression'
),
path(
'projet/ajouter-financement',
views.ajouter_financement_projet,
name='ajouter_financement'
),
path(
'creation-bailleur',
views.creation_bailleur,
name='creation-bailleur'
),
path(
'projet/affectation/<int:projet_id>',
views.affecter_employe_projet,
name='affecter-employe-projet'
),
path(
'projet/ajout-de-document/',
views.ajouter_document_projet,
name='ajouter-document'
),
path(
'projet/bailleurs/<str:projet_id>/',
views.liste_bailleurs,
name='liste-bailleurs'
),
path(
'activite/',
views.activites_projet,
name='activites-projet'
),
path(
'activite/ajouter/',
views.ajouter_activite_projet,
name='ajouter-activite'
),
path(
'activite/modifier/<int:id>/',
views.modifier_activite_projet,
name='modifier-activite'
),
path(
'activite/annuler/',
views.annuler_activite_projet,
name='annuler-activite'
),
path(
'activite/liste/',
views.liste_activites_projet,
name='liste-activites-projet'
),
# path(
# 'projet/ajout-de-document/',
# views.ajouter_document_projet,
# name='ajouter-document'
# ),
path(
'projet/liste-des-documents/',
views.liste_documents_projet,
name='liste-documents-projet'
),
path(
'activite/liste-des-livrables/<int:activite_id>/',
views.liste_livrables_activite,
name='liste-livrables-activite'
),
path(
'projet/ajout-de-livrable/',
views.ajouter_livrables_projet,
name='ajouter-livrable'
),
path(
'activite/mise-a-jour-depense/',
views.mises_a_jour_depense_activite,
name='mettre-a-jour-depense'
),
path(
'projet/liste-employes-par-projet/<str:projet_id>',
views.liste_employes_affectes,
name='liste-employes-affectes'
),
path(
'projet/mises-a-jour-projet',
views.mises_a_jour_projet,
name='mises-a-jour-projet'
)
]

485
gestion_projet/views.py Normal file
View File

@@ -0,0 +1,485 @@
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')