12 Commits

Author SHA1 Message Date
Fatimac2804
1da9d4ca5f Affichage des livrables sur les details de l'activité
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
SGL/sirh/pipeline/head This commit looks good
2026-09-02 17:17:23 +02:00
Fatimac2804
e81c29a586 Affichage des détails de la réservation de salle
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
SGL/sirh/pipeline/head This commit looks good
2026-09-02 14:43:27 +02:00
Fatimac2804
d28bf85556 Affichage de la liste des departements pour la modification de l'employer
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
Organisation/sirh/pipeline/pr-main This commit looks good
SGL/sirh/pipeline/head This commit looks good
2026-09-02 14:33:51 +02:00
Fatimac2804
3b587d2a1a Affichage des detait de la reservation de salle
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-02 13:46:07 +02:00
Fatimac2804
e3f0befff6 Correction sur l'afficha de la liste de departement pour la modification des informations de l'employer
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-09-02 12:48:35 +02:00
Soriba SYLLA
55e5a2819c Ajout du contexte processor
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-08-31 09:48:21 +00:00
Soriba SYLLA
7e28b25497 Merge branch 'staging' 2026-08-23 15:14:24 +00:00
f9ca539670 Supprimer gestion_salle/migrations/0001_initial.py
All checks were successful
Organisation/sirh/pipeline/head This commit looks good
2026-07-14 09:17:13 +00:00
7b4778252f Supprimer gestion_projet/migrations/0001_initial.py
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-07-14 09:17:02 +00:00
2c086187e7 Supprimer gestion_employe/migrations/0001_initial.py
Some checks are pending
Organisation/sirh/pipeline/head Build queued...
2026-07-14 09:16:51 +00:00
5aff49600e Supprimer gestion_conge/migrations/0001_initial.py
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-07-14 09:16:28 +00:00
8c6e59e15a Correction enregistrement profil
Some checks failed
Organisation/sirh/pipeline/head There was a failure building this commit
2026-07-14 09:03:28 +00:00
47 changed files with 312 additions and 127 deletions

View File

@@ -27,7 +27,7 @@ SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=True, cast=bool)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',')
CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]).split(",")
#CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]).split(",")
# Application definition
@@ -66,6 +66,7 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'contexte_universel.contexte_universel',
],
},
},
@@ -144,9 +145,9 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# LOGIN_URL = '/client/'
LOGIN_URL = 'simple-sso-login'
SSO_PRIVATE_KEY = 'D2brFY3LnNDLQBBnvYPXAn185Wi9EpPvnHsUosjEZ9vZMyEtXqkcVfFWkVbN0xpG'
SSO_PUBLIC_KEY = 'm9JDP3uGPmGG9ULqPw2BY3ryBNMTGRsuukgIOhk7lIN9lT0wlr2zhoRZiNZ28UCM'
SSO_SERVER = 'http://test-authentification.cerfig.org:89/server/'
SSO_PRIVATE_KEY = config('SSO_PRIVATE_KEY')
SSO_PUBLIC_KEY = config('SSO_PUBLIC_KEY')
SSO_SERVER = config('SSO_SERVER')
#SSO_LOGOUT_URL = 'http://test-authentification.cerfig.org:89/deconnexion'
# Configuration de l'email

View File

@@ -10,19 +10,25 @@ class SIRHClient(Client):
def build_user(self, user_data):
"""Crée le User et son Profile avec les infos reçues du serveur."""
user_data_copy = user_data.copy()
user_data.pop("departement")
user_data.pop("chef_departement")
user_data.pop("groups")
print("Un petit test", user_data_copy)
user_data.pop("departement", None)
user_data.pop("chef_departement", None)
user_data.pop("groups", None)
user = super().build_user(user_data)
departement = Departement.objects.get_or_create(
nom=user_data_copy.get("departement", ""))[0]
departement = None
if "departement" in user_data_copy:
departement = Departement.objects.get_or_create(
nom=user_data_copy.get("departement", ""))[0]
Employe.objects.update_or_create(
user = user,
defaults = {
"departement": departement,
"chef": user_data_copy.get("chef_departement", ""),
"chef": user_data_copy.get("chef_departement", None),
}
)
@@ -30,4 +36,4 @@ class SIRHClient(Client):
group, _ = Group.objects.get_or_create(name=group_name)
user.groups.add(group)
return user
return user

View File

@@ -1,8 +1,8 @@
<div class="col-1 bg-danger border d-flex flex-column vh-100 pt-5 sticky-top">
<a href="http://test-sirh.cerfig.org:89/" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<a href="{{ SIRH_LIEN }}" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-people-fill fs-1 d-block"></i>SIRH
</a>
<a href="http://test-sgl.cerfig.org:89/" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<a href="{{ SGL_LIEN }}" class="text-white text-center fw-bold text-decoration-none mb-4" style="font-size:1.4em">
<i class="bi bi-building-gear fs-1 d-block"></i> SGL
</a>
</div>

View File

@@ -42,7 +42,7 @@
<i class="bi bi-calendar-check"></i> Réservation
</a>
{% comment %} {% url 'deconnexion' %} {% endcomment %}
<a href="http://test-authentification.cerfig.org:89/deconnexion" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<a href="{% url 'deconnexion' %}" class="text-white fw-bold text-decoration-none mb-4" style="font-size: 1.2em;">
<i class="bi bi-box-arrow-right"></i> Déconnexion
</a>
</div>

View File

@@ -18,7 +18,7 @@ from django.contrib import admin
from django.urls import include, path
# from django.conf.urls.static import static
from django.conf import settings
from . import views
from .sso import SIRHClient
client_sirh = SIRHClient(
@@ -28,15 +28,15 @@ client_sirh = SIRHClient(
)
urlpatterns = [
# path('login/',
# views.login_view,
# name='login'
# ),
# path(
# 'deconnexion/',
# views.deconnexion_view,
# name='deconnexion'
# ),
path('login/',
views.connexion,
name='login'
),
path(
'deconnexion/',
views.deconnexion,
name='deconnexion'
),
path(
'',
include("gestion_employe.urls")
@@ -59,4 +59,4 @@ urlpatterns = [
),
path("client/", include(client_sirh.get_urls())),
]
# + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@@ -0,0 +1,16 @@
from django.contrib.auth import logout
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.conf import settings
def connexion(request):
"""Redirige vers le serveur SSO pour l'authentification."""
return redirect(reverse_lazy("simple-sso-login"))
def deconnexion(request):
"""Gérez la déconnexion des utilisateurs."""
logout(request)
#return redirect(settings.SSO_LOGOUT_URL)
return redirect("http://authentification.cerfig.org/login")

7
contexte_universel.py Normal file
View File

@@ -0,0 +1,7 @@
from decouple import config
def contexte_universel(request):
return {
"SIRH_LIEN": config("SIRH_LIEN"),
"SGL_LIEN": config("SGL_LIEN")
}

View File

@@ -7,7 +7,11 @@ QUOTA_CONGE_ANNUEL = 30
NOMBRE_PAGINATION = 8
DEBUT_RAPPEL = 60
DUREE_FIN_CONTRAT = 90
# EMAIL_ASSISTANTE_DE_DIRECTION = list(Employe.objects.filter(fonction="assistant_direction").values_list('user__email', flat=True))
try:
EMAIL_ASSISTANTE_DE_DIRECTION = list(Employe.objects.filter(fonction="assistant_direction").values_list('user__email', flat=True))
except:
pass
def solde_conge(employe):
"""Fonction de calcul du solde de congé restant l'employé"""

View File

@@ -18,29 +18,34 @@ const tableau_certificat = new Tabulator("#tableau-certificat", {
})
const enregistrerProfil = $("enregistrerProfil");
enregistrerProfil.addEventListener("click", (e) => {
// enregistrerProfil.addEventListener("click", (e) => {
const url = $("information-personnelles").dataset.url;
const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
// const url = $("information-personnelles").dataset.url;
// const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value;
const formData = new FormData();
// const formData = new FormData();
formData.append("nom", $("nom").value);
formData.append("prenom", $("prenom").value);
formData.append("email", $("email").value);
formData.append("telephone", $("telephone").value);
formData.append("adresse", $("adresse").value);
formData.append("sexe", $("sexe").value);
formData.append("date_naissance", $("date_naissance").value);
// formData.append("nom", $("nom").value);
// formData.append("prenom", $("prenom").value);
// formData.append("email", $("email").value);
// formData.append("telephone", $("telephone").value);
// formData.append("adresse", $("adresse").value);
// formData.append("sexe", $("sexe").value);
// formData.append("date_naissance", $("date_naissance").value);
fetch(url, {
method: "POST",
headers: {
"X-CSRFToken": csrftoken
},
body: formData
})
.then(response => response.json())
.then(data => alert(data.message))
.catch(error => console.error("Erreur:", error));
});
// console.log("---------------------------------------------")
// console.log("Données du formulaire:", url);
// console.log("Données du formulaire:", formData);
// fetch(url, {
// method: "POST",
// headers: {
// "X-CSRFToken": csrftoken
// },
// body: formData
// })
// .then(response => response.json())
// .then(data => alert(data.message))
// .catch(error => console.error("Erreur:", error));
// });

View File

@@ -38,7 +38,7 @@
</h2>
<div id="collapseProfil" class="accordion-collapse" aria-labelledby="headingContrats" data-bs-parent="#accordionInformationEmploye">
<div class="accordion-body">
<div class="row" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}">
<form class="row" action = "{% url 'gestion_employe:modifier-employe' %}" method = "POST" enctype = "multipart/form-data" id="information-personnelles" data-url="{% url 'gestion_employe:modifier-employe' %}">
{% csrf_token %}
<div class="col">
<div class="form-group mb-2">
@@ -50,19 +50,19 @@
</div>
<div class="form-group mb-2">
<label>Matricule :</label>
<input type="text" class="form-control" id="matricule" value="{{ employe.matricule|default:'' }}" readonly>
<input type="text" class="form-control" id="matricule" name = 'matricule' value="{{ employe.matricule|default:'' }}" readonly>
</div>
<div class="form-group mb-2">
<label>Nom :</label>
<input type="text" class="form-control" id="nom" value="{{ employe.user.last_name }}">
<input type="text" class="form-control" id="nom" name = 'nom' value="{{ employe.user.last_name }}">
</div>
<div class="form-group mb-2">
<label>Prénom :</label>
<input type="text" class="form-control" id="prenom" value="{{ employe.user.first_name }}">
<input type="text" class="form-control" id="prenom" name = 'prenom' value="{{ employe.user.first_name }}">
</div>
<div class="form-group mb-2">
<label>Sexe :</label>
<select id='sexe' class="form-select">
<select id='sexe' name = 'sexe' class="form-select">
<option value='h' {% if employe.sexe == 'h' %}selected{% endif %}>Homme</option>
<option value='f' {% if employe.sexe == 'f' %}selected{% endif %}>Femme</option>
</select>
@@ -72,27 +72,27 @@
<div class="col">
<div class="form-group mb-2">
<label>Date de naissance :</label>
<input type="date" class="form-control" id="date_naissance" value="{{ employe.date_naissance|date:'Y-m-d' }}">
<input type="date" class="form-control" id="date_naissance" name = 'date_naissance' value="{{ employe.date_naissance|date:'Y-m-d' }}">
</div>
<div class="form-group mb-2">
<label>Département :</label>
<input type="text" class="form-control" id="departement" value="{{ employe.departement.nom|default:'' }}" readonly>
<input type="text" class="form-control" id="departement" name = 'departement' value="{{ employe.departement.nom|default:'' }}" readonly>
</div>
<div class="form-group mb-2">
<label>Email :</label>
<input type="email" class="form-control" id="email" value="{{ employe.user.email|default:'' }}">
<input type="email" class="form-control" id="email" name = 'email' value="{{ employe.user.email|default:'' }}">
</div>
<div class="form-group mb-2">
<label>Téléphone :</label>
<input type="text" class="form-control" id="telephone" value="{{ employe.telephone|default:'' }}">
<input type="text" class="form-control" id="telephone" name = 'telephone' value="{{ employe.telephone|default:'' }}">
</div>
<div class="form-group mb-2">
<label>Adresse :</label>
<input type="text" class="form-control" id="adresse" value="{{ employe.adresse|default:'' }}">
<input type="text" class="form-control" id="adresse" name = 'adresse' value="{{ employe.adresse|default:'' }}">
</div>
</div>
</div>
<button type="submit" class="btn btn-success d-block m-auto" id="enregistrerProfil">Enregistrer</button>
<button type="submit" class="btn btn-success d-block m-auto" id="enregistrerProfil">Enregistrer</button>
</form>
</div>
</div>
</div>

View File

@@ -20,6 +20,9 @@ from fonction_utilitaire import fonctions_utilitaire
@login_required
def index(request):
"""Vue d'index"""
if not request.user.groups.filter(name="ressource_humaine").exists():
return redirect('gestion_employe:mon-profil')
employes = Employe.objects.all().order_by('-user__date_joined')
nombre_employes = Employe.objects.count()
nombre_cps = Contrat.objects.filter(type_contrat='contrat_prestation').count()
@@ -94,7 +97,7 @@ def affecter_employe_projet(request):
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}%)."
f"Les pourcentages d'affectation de l'employé {employe.user.first_name} {employe.user.last_name} dépasse 100% sur les différents projets ({total_affectation + temps_nouveau}%)."
)
return redirect('gestion_employe:index')
@@ -191,25 +194,26 @@ def modifier_employer(request):
except Employe.DoesNotExist:
return JsonResponse({"message": "Employé non trouvé."})
if request.method == "POST":
data = json.loads(request.body)
user = User.objects.get(username=request.user)
user.last_name = data['nom']
user.first_name = data['prenom']
user.email = data['email']
employe.telephone = data['telephone']
employe.adresse = data['adresse']
employe.sexe = data['sexe']
user.last_name = request.POST['nom']
user.first_name = request.POST['prenom']
user.email = request.POST['email']
employe.telephone = request.POST['telephone']
employe.adresse = request.POST['adresse']
employe.sexe = request.POST['sexe']
if request.FILES.get("photo"):
employe.photo = request.FILES["photo"]
if data['date_naissance']:
difference = relativedelta(timezone.now().date(), datetime.strptime(data['date_naissance'], "%Y-%m-%d").date())
if request.POST['date_naissance']:
difference = relativedelta(timezone.now().date(), datetime.strptime(request.POST['date_naissance'], "%Y-%m-%d").date())
if difference.years >= 18:
employe.date_naissance = data['date_naissance']
employe.date_naissance = request.POST['date_naissance']
else:
return JsonResponse({"message": "Veuillez entrez une date de naissance correcte."})
messages.error(request, "Veuillez entrez une date de naissance correcte.")
employe.save()
user.save()
return JsonResponse({"message": "Profil mis à jour avec succès."})
messages.success(request, "Profil mis à jour avec succès.")
return redirect("gestion_employe:mon-profil")
def enregistrement_document(request):
"""Vue pour permettre à un utilisateur de télécharger et enregistrer des documents liés à son profil"""

View File

@@ -15,6 +15,11 @@ const tableau_liste_activite = new Tabulator("#tableau-liste-activite", {
})
tableau_liste_activite.on("rowClick", function (row, rowData) {
const data = rowData.getData();
console.log('=== ACTIVITÉ SÉLECTIONNÉE ===');
console.log('ID:', data.id);
console.log('Budget prévu:', data.budget_prevu);
console.log('Budget dépensé:', data.budget_depense);
$("idDetailActivite").value = data.id;
$("titreDetailActivite").value = data.titre;
$("descriptionDetailActivite").value = data.description;
@@ -22,14 +27,79 @@ tableau_liste_activite.on("rowClick", function (row, rowData) {
$("date_finDetailActivite").value = data.date_fin;
$("statutDetailActivite").value = data.statut;
$("budget_prevuDetailActivite").value = data.budget_prevu;
$("budget_depenseDetailActivite").value = data.budget_depense || 0;
// Vérifier si l'activité est annulée
const isAnnulee = data.statut && data.statut.toLowerCase().includes('annul');
console.log('Activité annulée?', isAnnulee);
// Masquer/Afficher les boutons d'action selon le statut
const btnAnnuler = $("btnAnnulerActivite");
const btnMiseAJour = $("btnMiseAJourDepense");
const btnAjouterLivrable = $("btnOuvrirAjouterLivrable");
if (isAnnulee) {
// Activité annulée : masquer les boutons
if (btnAnnuler) btnAnnuler.style.display = "none";
if (btnMiseAJour) btnMiseAJour.style.display = "none";
if (btnAjouterLivrable) btnAjouterLivrable.style.display = "none";
console.log('✓ Boutons masqués - activité annulée');
} else {
// Activité active : afficher les boutons
if (btnAnnuler) btnAnnuler.style.display = "block";
if (btnMiseAJour) btnMiseAJour.style.display = "block";
if (btnAjouterLivrable) btnAjouterLivrable.style.display = "block";
console.log('✓ Boutons affichés - activité active');
}
// Vérifier si les budgets sont différents
const budgetPrevu = parseFloat(data.budget_prevu) || 0;
const budgetDepense = parseFloat(data.budget_depense) || 0;
const isDifferent = budgetDepense !== budgetPrevu;
console.log('Budget prévu:', budgetPrevu);
console.log('Budget dépensé:', budgetDepense);
console.log('Sont-ils différents?', isDifferent);
// Masquer/Afficher le conteneur du motif
const motifContainer = $("motif_container");
if (motifContainer) {
if (isDifferent) {
motifContainer.style.display = "block";
$("motif_changement_budgetDetailActivite").value = data.motif_changement_budget || "";
console.log('✓ Motif AFFICHÉ - budgets différents');
} else {
motifContainer.style.display = "none";
$("motif_changement_budgetDetailActivite").value = "";
console.log('✓ Motif MASQUÉ - budgets identiques');
}
} else {
console.error('❌ Élément motif_container introuvable!');
}
$("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())
const url_livrables = `/gestion-projet/activite/liste-des-livrables/${data.id}/`;
console.log('URL des livrables:', url_livrables);
fetch(url_livrables)
.then(response => {
console.log('Réponse status:', response.status);
return response.json();
})
.then(livrables => {
console.log('Livrables reçus:', livrables);
console.log('Nombre de livrables:', livrables.length);
tableau_liste_livrable.setData(livrables);
$("nombreLivrables").textContent = livrables.length;
console.log('Tableau mis à jour avec', livrables.length, 'livrables');
})
.catch(error => {
console.error(' Erreur lors du chargement des livrables:', error);
tableau_liste_livrable.setData([]);
$("nombreLivrables").textContent = 0;
})
})
@@ -40,6 +110,7 @@ const tableau_liste_livrable = new Tabulator("#listeLivrables", {
],
placeholder: "Aucun livrable trouvé",
})
console.log('✓ Tableau des livrables initialisé');
$("btnMiseAJourDepense").addEventListener("click", function() {
const modal = new bootstrap.Modal($("modalDepenseActivite"));
@@ -56,4 +127,5 @@ $("btnAnnulerActivite").addEventListener("click", function(event) {
new bootstrap.Modal($("modalAnnulerActivite")).show();
$("id_activite_annulation").value = $("idDetailActivite").value;
bootstrap.Modal.getOrCreateInstance($("modalDetailActivite")).hide();
})
})

View File

@@ -1,58 +1,111 @@
<div class="modal fade" id="modalDetailActivite" tabindex="-1">
<div class="modal-dialog">
<div class="modal fade" id="modalDetailActivite" tabindex="-1"
aria-labelledby="modalDetailActiviteLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<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 class="modal-header bg-orange-dark text-black">
<h5 class="modal-title" id="modalDetailActiviteLabel">
<i class="bi bi-clipboard-check me-2"></i>
Détails de l'activité
</h5>
<button type="button"class="btn-close btn-close-white" data-bs-dismiss="modal"
aria-label="Fermer">
</button>
</div>
<div class="modal-body">
<div class="modal-body" data-url-livrables="{% url 'gestion_projet:liste-livrables-activite' activite_id=0 %}">
<input type="hidden" id="idDetailActivite">
<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 class="col-12">
<label for="titreDetailActivite" class="form-label fw-bold">
Titre :
</label>
<input type="text"class="form-control" id="titreDetailActivite"readonly>
</div>
<div class="col-12">
<label for="descriptionDetailActivite" class="form-label fw-bold">
Description :
</label>
<textarea class="form-control"id="descriptionDetailActivite" rows="3" readonly></textarea>
</div>
<div class="col-md-6">
<label for="date_debutDetailActivite"class="form-label fw-bold">
Date de début :
</label>
<input type="date"class="form-control"id="date_debutDetailActivite"readonly>
</div>
<div class="col-md-6">
<label for="date_finDetailActivite"class="form-label fw-bold">
Date de fin :
</label>
<input type="date"class="form-control"id="date_finDetailActivite"readonly>
</div>
<div class="col-md-6">
<label for="statutDetailActivite" class="form-label fw-bold">
Statut :
</label>
<input type="text"class="form-control"id="statutDetailActivite"readonly>
</div>
<div class="col-md-6">
<label for="budget_prevuDetailActivite"class="form-label fw-bold">
Budget prévu :
</label>
<input type="number"class="form-control" id="budget_prevuDetailActivite"readonly>
</div>
<div class="col-md-6">
<label for="budget_depenseDetailActivite"class="form-label fw-bold">
Montant dépensé (GNF) :
</label>
<input type="number"class="form-control" id="budget_depenseDetailActivite"readonly>
</div>
<div class="col-12" id="motif_container">
<label for="motif_changement_budgetDetailActivite"class="form-label fw-bold">
Motif de différence :
</label>
<textarea class="form-control"id="motif_changement_budgetDetailActivite"rows="2"readonly></textarea>
</div>
<div class="col-12">
<label for="besoin_ressources_materiellesDetailActivite"class="form-label fw-bold">
Besoin de ressources matérielles :
</label>
<textarea class="form-control"id="besoin_ressources_materiellesDetailActivite"rows="3"readonly></textarea>
</div>
<div class="col-12 mt-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0 fw-bold">
<i class="bi bi-box-seam me-2"></i>
Liste des livrables
</h5>
<span class="badge bg-primary" id="nombreLivrables">
0
</span>
</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">
</div>
<div class="col-12 mt-3 text-center">
<button type="button"class="btn btn-danger"id="btnAnnulerActivite">
<i class="bi bi-x-circle me-2"></i>
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 type="button"class="btn btn-success"id="btnMiseAJourDepense">
<i class="bi bi-cash-stack me-2"></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 type="button"class="btn btn-primary"id="btnOuvrirAjouterLivrable"data-bs-toggle="modal"
data-bs-target="#modalAjouterLivrable">
<i class="bi bi-plus-circle me-2"></i>
Ajouter un livrable
</button>
<button type="button" class="btn btn-secondary"data-bs-dismiss="modal">
Fermer
</button>
</div>
</div>
</div>
</div>
</div>

View File

@@ -381,15 +381,32 @@ def liste_activites_projet(request):
@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)
try:
livrables = LivrablesLivres.objects.filter(activite__id=activite_id)
data = []
for livrable in livrables:
lien = ""
if livrable.fichier:
try:
lien = livrable.fichier.url
except Exception as e:
print(f"[DEBUG] Erreur accès URL fichier {livrable.nom}: {e}")
lien = ""
data.append({
"titre": livrable.nom,
"lien": lien,
})
print(f"[DEBUG] ✓ Livrables activité {activite_id}: {len(data)} trouvés")
return JsonResponse(data, safe=False)
except Exception as e:
print(f"[DEBUG] Erreur liste_livrables_activite: {e}")
import traceback
traceback.print_exc()
return JsonResponse({"error": f"Erreur serveur: {str(e)}"}, status=500)
@login_required
def mises_a_jour_depense_activite(request):