Premiere version SIRH

This commit is contained in:
2026-04-27 10:17:10 +00:00
commit 9865860254
485 changed files with 46065 additions and 0 deletions

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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')