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