Premiere version SIRH
This commit is contained in:
0
gestion_projet/__init__.py
Normal file
0
gestion_projet/__init__.py
Normal file
BIN
gestion_projet/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
gestion_projet/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
gestion_projet/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
gestion_projet/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/admin.cpython-310.pyc
Normal file
BIN
gestion_projet/__pycache__/admin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/admin.cpython-313.pyc
Normal file
BIN
gestion_projet/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/admin.cpython-314.pyc
Normal file
BIN
gestion_projet/__pycache__/admin.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/apps.cpython-310.pyc
Normal file
BIN
gestion_projet/__pycache__/apps.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/apps.cpython-313.pyc
Normal file
BIN
gestion_projet/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/apps.cpython-314.pyc
Normal file
BIN
gestion_projet/__pycache__/apps.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/forms.cpython-310.pyc
Normal file
BIN
gestion_projet/__pycache__/forms.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/forms.cpython-313.pyc
Normal file
BIN
gestion_projet/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/forms.cpython-314.pyc
Normal file
BIN
gestion_projet/__pycache__/forms.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/models.cpython-310.pyc
Normal file
BIN
gestion_projet/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/models.cpython-313.pyc
Normal file
BIN
gestion_projet/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/models.cpython-314.pyc
Normal file
BIN
gestion_projet/__pycache__/models.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/urls.cpython-310.pyc
Normal file
BIN
gestion_projet/__pycache__/urls.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/urls.cpython-313.pyc
Normal file
BIN
gestion_projet/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/urls.cpython-314.pyc
Normal file
BIN
gestion_projet/__pycache__/urls.cpython-314.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/views.cpython-310.pyc
Normal file
BIN
gestion_projet/__pycache__/views.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/views.cpython-313.pyc
Normal file
BIN
gestion_projet/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/__pycache__/views.cpython-314.pyc
Normal file
BIN
gestion_projet/__pycache__/views.cpython-314.pyc
Normal file
Binary file not shown.
6
gestion_projet/admin.py
Normal file
6
gestion_projet/admin.py
Normal 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
176
gestion_projet/forms.py
Normal 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 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',
|
||||
}),
|
||||
}
|
||||
107
gestion_projet/migrations/0001_initial.py
Normal file
107
gestion_projet/migrations/0001_initial.py
Normal 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')},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
gestion_projet/migrations/__init__.py
Normal file
0
gestion_projet/migrations/__init__.py
Normal 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.
Binary file not shown.
BIN
gestion_projet/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
gestion_projet/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
gestion_projet/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
gestion_projet/migrations/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
gestion_projet/migrations/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
309
gestion_projet/models.py
Normal file
309
gestion_projet/models.py
Normal 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
|
||||
19
gestion_projet/static/gestion_projet/js/creation_projet.js
Normal file
19
gestion_projet/static/gestion_projet/js/creation_projet.js
Normal 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 !");
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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.');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
64
gestion_projet/static/gestion_projet/js/index.js
Normal file
64
gestion_projet/static/gestion_projet/js/index.js
Normal 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"));
|
||||
// })
|
||||
@@ -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,
|
||||
});
|
||||
59
gestion_projet/static/gestion_projet/js/suivi-activites.js
Normal file
59
gestion_projet/static/gestion_projet/js/suivi-activites.js
Normal 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();
|
||||
})
|
||||
@@ -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 %}
|
||||
32
gestion_projet/templates/gestion_projet/creation_projet.html
Normal file
32
gestion_projet/templates/gestion_projet/creation_projet.html
Normal 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 %}
|
||||
61
gestion_projet/templates/gestion_projet/index.html
Normal file
61
gestion_projet/templates/gestion_projet/index.html
Normal 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 %}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
64
gestion_projet/templates/gestion_projet/suivi_activite.html
Normal file
64
gestion_projet/templates/gestion_projet/suivi_activite.html
Normal 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
3
gestion_projet/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
122
gestion_projet/urls.py
Normal file
122
gestion_projet/urls.py
Normal 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
485
gestion_projet/views.py
Normal 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é 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