diff --git a/SIRH/settings.py b/SIRH/settings.py index a6d62f9..df3bb06 100644 --- a/SIRH/settings.py +++ b/SIRH/settings.py @@ -24,9 +24,10 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = config('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = config('DEBUG', default=False, cast=bool) +DEBUG = config('DEBUG', default=True, cast=bool) -ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',') +ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[]).split(',') +CSRF_TRUSTED_ORIGINS = config("CSRF_TRUSTED_ORIGINS", default=[]).split(",") # Application definition @@ -40,12 +41,9 @@ INSTALLED_APPS = [ 'gestion_employe', 'gestion_conge', 'gestion_projet', - 'gestion_salle', - 'simple_sso.sso_server' + 'gestion_salle' ] -LOGIN_URL = 'login' - MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -80,24 +78,16 @@ WSGI_APPLICATION = 'SIRH.wsgi.application' # https://docs.djangoproject.com/en/5.2/ref/settings/#databases -if config('ENVIRONMENT') == 'local': - DATABASES = { +DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), - } - } -else: - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': config('DATABASE_NAME'), - 'USER': config('DATABASE_USER'), - 'PASSWORD': config('DATABASE_PASSWORD'), - 'HOST': config('DATABASE_HOST'), - 'PORT': config('DATABASE_PORT'), - } + 'ENGINE': 'django.db.backends.mysql', + 'NAME': config("DB_NAME"), + 'USER': config("DB_USER"), + 'PASSWORD': config("DB_PASSWORD"), + 'HOST': config("DB_HOST"), + 'PORT': config("DB_PORT", cast=int), } +} # Password validation # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators @@ -150,12 +140,21 @@ MEDIA_ROOT = BASE_DIR / "media" DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +# Connexion sso +# 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_LOGOUT_URL = 'http://test-authentification.cerfig.org:89/deconnexion' + # Configuration de l'email EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' EMAIL_HOST = 'ssl0.ovh.net' EMAIL_PORT = 465 EMAIL_USE_SSL = True EMAIL_USE_TLS = False -EMAIL_HOST_USER = 'support.it@cerfig.org' -EMAIL_HOST_PASSWORD = 'Cerfig2025' +EMAIL_HOST_USER = config("EMAIL_HOST_USER") +EMAIL_HOST_PASSWORD = config("EMAIL_HOST_PASSWORD") DEFAULT_FROM_EMAIL = EMAIL_HOST_USER diff --git a/SIRH/sso.py b/SIRH/sso.py new file mode 100644 index 0000000..52e4660 --- /dev/null +++ b/SIRH/sso.py @@ -0,0 +1,33 @@ +# SGL/sso.py +from gestion_employe.models import Employe, Departement +from simple_sso.sso_client.client import Client +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group + +User = get_user_model() + +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") + + user = super().build_user(user_data) + 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", ""), + } + ) + + for group_name in user_data_copy.get("groups", []): + group, _ = Group.objects.get_or_create(name=group_name) + user.groups.add(group) + + return user \ No newline at end of file diff --git a/SIRH/templates/SIRH/BASE.html b/SIRH/templates/SIRH/BASE.html index 5a0f392..faa8f2f 100644 --- a/SIRH/templates/SIRH/BASE.html +++ b/SIRH/templates/SIRH/BASE.html @@ -15,8 +15,9 @@
+ {% include 'parts/menu_application.html' %} {% include 'parts/menu_principal.html' %} -
+
{% block 'contenu' %} {% endblock %}
diff --git a/SIRH/templates/SIRH/login.html b/SIRH/templates/SIRH/login.html index 801af39..d8dac08 100644 --- a/SIRH/templates/SIRH/login.html +++ b/SIRH/templates/SIRH/login.html @@ -24,15 +24,16 @@ {% endfor %} {% endif %} {% csrf_token %} +
- - + +
- - - + + +
diff --git a/SIRH/templates/SIRH/parts/menu_application.html b/SIRH/templates/SIRH/parts/menu_application.html new file mode 100644 index 0000000..46b6ab4 --- /dev/null +++ b/SIRH/templates/SIRH/parts/menu_application.html @@ -0,0 +1,8 @@ + diff --git a/SIRH/templates/SIRH/parts/menu_principal.html b/SIRH/templates/SIRH/parts/menu_principal.html index 41bee0e..ddf1e44 100644 --- a/SIRH/templates/SIRH/parts/menu_principal.html +++ b/SIRH/templates/SIRH/parts/menu_principal.html @@ -13,35 +13,36 @@ {% endif %}
- {{ user.username }} + {{ user.first_name }} {{ user.last_name }}
- + Mon profil {% if user|has_group:"ressource_humaine" or user|has_group:"direction" %} - + Tableau de bord {% endif %} {% if user|has_group:"ressource_humaine" or user|has_group:"direction" %} - + Gestion des Projets {% endif %} - + Gestion des congés {% if user|is_chef_projet %} - + Suivi des Activités {% endif %} - + Réservation - + {% comment %} {% url 'deconnexion' %} {% endcomment %} + Déconnexion -
\ No newline at end of file + diff --git a/SIRH/urls.py b/SIRH/urls.py index ba5be4b..06a4567 100644 --- a/SIRH/urls.py +++ b/SIRH/urls.py @@ -16,17 +16,30 @@ Including another URLconf """ from django.contrib import admin from django.urls import include, path -from django.conf.urls.static import static +# from django.conf.urls.static import static from django.conf import settings -# from simple_sso.sso_server.server import Server -from . import views +from .sso import SIRHClient -# server_sso = Server() +client_sirh = SIRHClient( + settings.SSO_SERVER, + settings.SSO_PUBLIC_KEY, + settings.SSO_PRIVATE_KEY +) -urlpatterns = [ +urlpatterns = [ + # path('login/', + # views.login_view, + # name='login' + # ), + # path( + # 'deconnexion/', + # views.deconnexion_view, + # name='deconnexion' + # ), path( '', +<<<<<<< HEAD views.login_view, name='index' ), @@ -42,6 +55,8 @@ urlpatterns = [ ), path( 'gestion-employe/', +======= +>>>>>>> staging include("gestion_employe.urls") ), path( @@ -59,9 +74,7 @@ urlpatterns = [ path( 'admin/', admin.site.urls - ), - # path( - # 'sso', - # include(server_sso.get_urls()) - # ) -] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + ), + path("client/", include(client_sirh.get_urls())), +] +# + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file diff --git a/SIRH/views.py b/SIRH/views.py index 77a057d..e69de29 100644 --- a/SIRH/views.py +++ b/SIRH/views.py @@ -1,37 +0,0 @@ -from django.contrib.auth import authenticate, login, logout -from django.shortcuts import render, redirect -from django.contrib import messages - -def login_view(request): - """ - Gère la connexion des utilisateurs avec redirection selon le rôle et - vérification de l'acceptation de la politique d'utilisation. - """ - if request.method == 'POST': - email = request.POST.get('mail') - password = request.POST.get('mot_de_passe') - - if not (email and password): - messages.error(request, "Veuillez remplir tous les champs.") - return render(request, 'login.html') - - user = authenticate(request, username=email, password=password) - - if user is None: - messages.error(request, "Nom d’utilisateur ou mot de passe incorrect.") - return render(request, 'login.html') - - if not user.is_active: - messages.error(request, "Compte inactif. Contactez l'administrateur.") - return render(request, 'login.html') - - login(request, user) - - return redirect("gestion_conges:conge") - - return render(request, 'login.html') - -def deconnexion_view(request): - """Gère la déconnexion de l'utilisateur.""" - logout(request) - return redirect('login') \ No newline at end of file diff --git a/fonction_utilitaire/fonctions_utilitaire.py b/fonction_utilitaire/fonctions_utilitaire.py index 3b53db8..2d2e3ba 100644 --- a/fonction_utilitaire/fonctions_utilitaire.py +++ b/fonction_utilitaire/fonctions_utilitaire.py @@ -7,10 +7,14 @@ QUOTA_CONGE_ANNUEL = 30 NOMBRE_PAGINATION = 8 DEBUT_RAPPEL = 60 DUREE_FIN_CONTRAT = 90 +<<<<<<< HEAD try: EMAIL_ASSISTANTE_DE_DIRECTION = list(Employe.objects.filter(fonction="assistant_direction").values_list('user__email', flat=True)) except: pass +======= +# EMAIL_ASSISTANTE_DE_DIRECTION = list(Employe.objects.filter(fonction="assistant_direction").values_list('user__email', flat=True)) +>>>>>>> staging def solde_conge(employe): """Fonction de calcul du solde de congé restant l'employé""" diff --git a/gestion_conge/migrations/0001_initial.py b/gestion_conge/migrations/0001_initial.py new file mode 100644 index 0000000..e498b12 --- /dev/null +++ b/gestion_conge/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 5.2.13 on 2026-06-16 11:06 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('gestion_employe', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Conge', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_debut', models.DateField(verbose_name='Date de Début')), + ('date_fin', models.DateField(verbose_name='Date de Fin')), + ('type', models.CharField(choices=[('conge_annuel', 'Conge Annuel')], max_length=100, verbose_name='Type de Congé')), + ('date_demande', models.DateField(auto_now_add=True, verbose_name='Date de Demande')), + ('validation_hierarchique', models.BooleanField(default=None, null=True)), + ('validation_direction', models.BooleanField(default=None, null=True)), + ('motif_refus', models.TextField(blank=True, null=True)), + ('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='employe', to='gestion_employe.employe')), + ], + ), + ] diff --git a/gestion_conge/static/gestion_conge/js/index.js b/gestion_conge/static/gestion_conge/js/index.js index 1d156cf..5308791 100644 --- a/gestion_conge/static/gestion_conge/js/index.js +++ b/gestion_conge/static/gestion_conge/js/index.js @@ -3,7 +3,7 @@ const $ = (element) => document.getElementById(element); const url_liste_conge_attente = $("liste-demande-conges").dataset.url const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { - layout : "fitColumns", + // layout : "fitColumns", columns: [ {"title": "Nom et Prénom", "field": "prenom_nom"}, {"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{ @@ -15,7 +15,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { outputFormat:"dd/MM/yy", }}, {"title": "Type de congé", "field": "type"}, - {"title": "Date de la demande", "field": "date_demande"}, + {"title": "Date de la demande", "field": "date_demande", responsive: 1}, {"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{ allowEmpty : true , }}, @@ -24,7 +24,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { }}, ], pagination: true, - paginationSize: 5 + paginationSize: 5, }) const bouton_demande_conges = $("bouton-demande-conge"); diff --git a/gestion_conge/templates/gestion_conge/index.html b/gestion_conge/templates/gestion_conge/index.html index 17d1ff6..30a2595 100644 --- a/gestion_conge/templates/gestion_conge/index.html +++ b/gestion_conge/templates/gestion_conge/index.html @@ -7,6 +7,7 @@
{{message}}
{% endfor %} {% endif %} +
@@ -32,11 +33,17 @@
{{ nombre_conges_valide }}
-
+
+ +
+

Liste des demandes de congé

-
+
+ +
+
diff --git a/gestion_employe/migrations/0001_initial.py b/gestion_employe/migrations/0001_initial.py new file mode 100644 index 0000000..3c27a92 --- /dev/null +++ b/gestion_employe/migrations/0001_initial.py @@ -0,0 +1,88 @@ +# Generated by Django 5.2.13 on 2026-06-16 11:06 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('gestion_projet', '0001_initial'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Departement', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nom', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='Employe', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('matricule', models.CharField(blank=True, max_length=10, null=True, unique=True)), + ('fonction', models.CharField(blank=True, choices=[('directeur', 'Directeur'), ('assistant_direction', 'Assistante de direction'), ('assistant_technique_recherche', 'Assistant technique de recherche'), ('comptable', 'Comptable'), ('raf', 'RAF'), ('data_manager', 'Data Manager'), ('logisticien', 'Logisticien'), ('post_doctorant', 'Post-Doctorant'), ('qualiticien', 'Qualiticien'), ('technicien_surface', 'Technicien de surface'), ('chauffeur', 'Chauffeur')], max_length=50, null=True)), + ('date_embauche', models.DateField(blank=True, null=True)), + ('adresse', models.CharField(blank=True, max_length=100, null=True)), + ('telephone', models.CharField(blank=True, max_length=15, null=True)), + ('sexe', models.CharField(blank=True, choices=[('m', 'Masculin'), ('f', 'Féminin')], max_length=1, null=True)), + ('date_naissance', models.DateField(blank=True, null=True)), + ('CV', models.FileField(blank=True, null=True, upload_to='cv/')), + ('diplome', models.FileField(blank=True, null=True, upload_to='diplomes/')), + ('rib', models.FileField(blank=True, null=True, upload_to='rib/')), + ('photo', models.ImageField(blank=True, null=True, upload_to='photos/')), + ('casier_judiciaire', models.FileField(blank=True, null=True, upload_to='casier/')), + ('chef', models.BooleanField(default=False, verbose_name='Cet utilisateur est-il chef de ce département ?')), + ('departement', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='gestion_employe.departement')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Contrat', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('numero_contrat', models.CharField(max_length=100, unique=True)), + ('type_contrat', models.CharField(choices=[('contrat_duree_determinee', 'Contrat à Durée Déterminée'), ('contrat_duree_indeterminee', 'Contrat à Durée Indéterminée'), ('contrat_prestation', 'Contrat de Prestation de Service'), ('contrat_stage', 'Contrat de Stage'), ('convention_bourse_entretien', "Convention de bourse d'entretien")], max_length=50)), + ('date_debut', models.DateField()), + ('date_fin', models.DateField(blank=True, null=True)), + ('salaire_mensuel', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('statut', models.CharField(choices=[('actif', 'Actif'), ('termine', 'Terminé'), ('suspendu', 'Suspendu'), ('rupture_contrat', 'Rupture de Contrat')], max_length=50)), + ('fichier_contrat', models.FileField(blank=True, null=True, upload_to='contrats/')), + ('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_employe.employe')), + ], + ), + migrations.CreateModel( + name='Formation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('titre', models.CharField(max_length=255, verbose_name='Nom du certificat')), + ('organisme', models.CharField(max_length=255, verbose_name="Nom de l'organisme")), + ('description', models.TextField(blank=True, null=True, verbose_name='Description de la formation')), + ('date_obtention', models.DateField(blank=True, null=True, verbose_name="Date d'obtention")), + ('date_fin', models.DateField(blank=True, null=True, verbose_name='Date de fin de validité')), + ('certificat', models.FileField(blank=True, null=True, upload_to='documents/formations/', verbose_name='Certificat (PDF/Image)')), + ('employe', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='employe_formation', to='gestion_employe.employe')), + ], + ), + migrations.CreateModel( + name='Affectation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date_affectation', models.DateField()), + ('date_fin_daffectation', models.DateField(blank=True, null=True)), + ('role', models.CharField(choices=[('chef_projet', 'Chef de projet'), ('doctorant', 'Doctorant'), ('mastorant', 'Mastorant'), ('consultant', 'Consultant'), ('stagiaire', 'Stagiaire'), ('laborantin', 'Laborantin'), ('medecin', 'Médecin'), ('autre', 'Autre')], default='Membre', max_length=20)), + ('pourcentage_temps_affectation', models.DecimalField(decimal_places=2, max_digits=5)), + ('projet', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_projet.projet')), + ('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_employe.employe')), + ], + options={ + 'unique_together': {('projet', 'employe')}, + }, + ), + ] diff --git a/gestion_employe/static/gestion_employe/js/index.js b/gestion_employe/static/gestion_employe/js/index.js index 95eb1d8..ba451c1 100644 --- a/gestion_employe/static/gestion_employe/js/index.js +++ b/gestion_employe/static/gestion_employe/js/index.js @@ -12,6 +12,7 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", { // ajaxURL: url_liste_employe, pagination: true, paginationSize: 10, + responsiveLayout : "hide" }) fetch(url_liste_employe) diff --git a/gestion_projet/migrations/0001_initial.py b/gestion_projet/migrations/0001_initial.py new file mode 100644 index 0000000..350818e --- /dev/null +++ b/gestion_projet/migrations/0001_initial.py @@ -0,0 +1,107 @@ +# Generated by Django 5.2.13 on 2026-06-16 11:06 + +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')}, + }, + ), + ] diff --git a/gestion_salle/migrations/0001_initial.py b/gestion_salle/migrations/0001_initial.py new file mode 100644 index 0000000..ceff4f8 --- /dev/null +++ b/gestion_salle/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# Generated by Django 5.2.13 on 2026-06-16 11:06 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('gestion_employe', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Reservation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('salle', models.CharField(choices=[('formation', 'Salle de formation'), ('reunion', 'Salle de réunion'), ('lien_zoom', 'Lien Zoom')], max_length=100)), + ('date_demande', models.DateTimeField(auto_now_add=True)), + ('date_debut', models.DateField()), + ('date_fin', models.DateField(blank=True, null=True)), + ('heure_debut', models.TimeField()), + ('heure_fin', models.TimeField()), + ('besoin_zoom', models.BooleanField(default=False, verbose_name="Besoin d'un lien Zoom ?")), + ('besoin_ordi', models.BooleanField(default=False, verbose_name="Besoin d'un ordinateur ?")), + ('lien_zoom', models.URLField(blank=True, null=True, verbose_name='Lien Zoom')), + ('motif_reservation', models.TextField()), + ('statut', models.CharField(choices=[('en_attente', 'En attente'), ('validee', 'Validée'), ('refusee', 'Refusée'), ('annulee', 'Annulée')], default='en_attente', max_length=25)), + ('employe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gestion_employe.employe')), + ], + ), + ] diff --git a/requirements.txt b/requirements.txt index a0fb268..b4b76bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ numpy==2.2.6 pandas==2.3.3 pillow==12.2.0 python-dateutil==2.9.0.post0 +python-decouple==3.8 pytz==2026.1.post1 requests==2.33.1 six==1.17.0 @@ -17,5 +18,5 @@ sqlparse==0.5.5 typing_extensions==4.15.0 tzdata==2026.2 urllib3==2.6.3 -python-decouple -gunicorn +webservices==0.7 +webservices.naudit==0.7.2 diff --git a/staticfiles/gestion_conge/js/index.js b/staticfiles/gestion_conge/js/index.js index 1d156cf..5308791 100644 --- a/staticfiles/gestion_conge/js/index.js +++ b/staticfiles/gestion_conge/js/index.js @@ -3,7 +3,7 @@ const $ = (element) => document.getElementById(element); const url_liste_conge_attente = $("liste-demande-conges").dataset.url const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { - layout : "fitColumns", + // layout : "fitColumns", columns: [ {"title": "Nom et Prénom", "field": "prenom_nom"}, {"title": "Date de début", "field": "date_debut", formatter:"datetime", formatterParams:{ @@ -15,7 +15,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { outputFormat:"dd/MM/yy", }}, {"title": "Type de congé", "field": "type"}, - {"title": "Date de la demande", "field": "date_demande"}, + {"title": "Date de la demande", "field": "date_demande", responsive: 1}, {"title": "Validation par supérieur hiérarchique", "field": "validation_hierarchique", formatter:"tickCross", formatterParams :{ allowEmpty : true , }}, @@ -24,7 +24,7 @@ const tableau_liste_demande_conge = new Tabulator("#liste-demande-conges", { }}, ], pagination: true, - paginationSize: 5 + paginationSize: 5, }) const bouton_demande_conges = $("bouton-demande-conge"); diff --git a/staticfiles/gestion_employe/js/index.js b/staticfiles/gestion_employe/js/index.js index 95eb1d8..ba451c1 100644 --- a/staticfiles/gestion_employe/js/index.js +++ b/staticfiles/gestion_employe/js/index.js @@ -12,6 +12,7 @@ const tableau_liste_employe = new Tabulator("#tableau_liste_employe", { // ajaxURL: url_liste_employe, pagination: true, paginationSize: 10, + responsiveLayout : "hide" }) fetch(url_liste_employe) diff --git a/staticfiles/gestion_employe/js/mon_profil.js b/staticfiles/gestion_employe/js/mon_profil.js index 64c77f9..77178b4 100644 --- a/staticfiles/gestion_employe/js/mon_profil.js +++ b/staticfiles/gestion_employe/js/mon_profil.js @@ -16,27 +16,31 @@ const tableau_certificat = new Tabulator("#tableau-certificat", { ], ajaxURL: url_certificat, }) - const enregistrerProfil = $("enregistrerProfil"); + enregistrerProfil.addEventListener("click", (e) => { + const url = $("information-personnelles").dataset.url; const csrftoken = document.querySelector("[name='csrfmiddlewaretoken']").value; + + 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); + fetch(url, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': csrftoken + "X-CSRFToken": csrftoken }, - body: JSON.stringify({ - "nom": $("nom").value, - "prenom": $("prenom").value, - "email": $("email").value, - "telephone": $("telephone").value, - "adresse": $("adresse").value, - "sexe": $("sexe").value, - "date_naissance": $("date_naissance").value, - }) + body: formData }) .then(response => response.json()) .then(data => alert(data.message)) -}) \ No newline at end of file + .catch(error => console.error("Erreur:", error)); +}); \ No newline at end of file diff --git a/staticfiles/gestion_projet/js/enregistrement_bailleur.js b/staticfiles/gestion_projet/js/enregistrement_bailleur.js index afe12ea..c09f502 100644 --- a/staticfiles/gestion_projet/js/enregistrement_bailleur.js +++ b/staticfiles/gestion_projet/js/enregistrement_bailleur.js @@ -1,4 +1,5 @@ const btnEnregistrerBailleur = document.getElementById('btnEnregistrerBailleur'); +let table; btnEnregistrerBailleur.addEventListener('click', function() { const form = document.getElementById('formBailleur'); @@ -20,4 +21,31 @@ btnEnregistrerBailleur.addEventListener('click', function() { alert('Ce bailleur existe déjà dans la base de données.'); } }); -}); \ No newline at end of file +}); + +document.addEventListener("DOMContentLoaded", function () { + + table = new Tabulator("#table-bailleurs", { + ajaxURL: "/gestion-projet/bailleurs/", + layout: "fitColumns", + pagination: "local", + paginationSize: 5, + + columns: [ + {title: "#", formatter: "rownum", width: 60}, + {title: "Organisme", field: "nom_organisme"}, + {title: "Contact", field: "contact"}, + {title: "Email", field: "email"}, + {title: "Pays", field: "pays"}, + ], + rowDblClick: function(e, row) { + const data = row.getData(); + + if (confirm(`Voulez-vous vraiment supprimer ${data.nom_organisme} ?`)) { + supprimerBailleur(data.id); + } + } + }); + +}); + diff --git a/staticfiles/gestion_salle/js/index.js b/staticfiles/gestion_salle/js/index.js index 4f69d09..6950c0e 100644 --- a/staticfiles/gestion_salle/js/index.js +++ b/staticfiles/gestion_salle/js/index.js @@ -15,7 +15,6 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), { .then(response => response.json()) .then(data => { currentReservationId = data.id_reservation; - console.log(data); $("id_reservation_detail").value = data.id_reservation; $("id_reservation_refus").value = data.id_reservation; $("id_reservation_zoom").value = data.id_reservation; @@ -30,11 +29,10 @@ const calendrier = Schedule(document.getElementById('planning-reservation'), { $("besoin_ordinateur").checked=data.besoin_ordinateur; $("lien_zoom").value=data.lien_zoom; - if(data.statut !== "annulee"){ - $("motif_refus_container").className = "d-none"; - }else{ - $("motif_refus").value=data.motif_refus; + if (data.besoin_zoom === false){ + $("lien_zoom_container").className = "d-none"; } + }) } }); @@ -80,6 +78,11 @@ $("bouton-annuler").addEventListener("click", (e) => { const csrf = document.querySelector("[name=csrfmiddlewaretoken]").value; const url_annuler = $("formulaire-details").dataset.urlannuler; + console.log("URL d'annulation :"); + console.log($("formulaire-details")) + + e.preventDefault(); + fetch( url_annuler, { @@ -130,6 +133,9 @@ if($("refuserReservation")){ const url = e.currentTarget.dataset.lienrefus; const idRes = $("id_reservation_detail").value; + console.log(idRes); + e.preventDefault(); + fetch(url, { method: "POST", headers: { @@ -161,16 +167,11 @@ const tableau_reservation_attente = new Tabulator("#tableau-reservation-attente" tableau_reservation_attente.on("rowClick", (row, rowData) => { const data = rowData.getData(); - console.log(data); - if(data.besoin_zoom === false){ $("lien_zoom_container").className = 'd-none'; } - if(data.statut !== "refusee"){ - $("motif_refus_container").className = 'd-none'; - } - + const id_user = $("current-user-id").dataset.userid; $("id_reservation_detail").value = data.id; $("id_reservation_refus").value = data.id; $("id_reservation_zoom").value = data.id; @@ -178,16 +179,20 @@ tableau_reservation_attente.on("rowClick", (row, rowData) => { $("employe").value=data.employe; $("salle").value=data.salle; $("statut-reservation").innerHTML=data.statut; - $("date_evenement").value=data.date_debut; + $("date_debut").value = data.date_debut; + $("date_fin").value = data.date_fin; $("heure_debut").value=data.heure_debut; $("heure_fin").value=data.heure_fin; $("motif_reservation").value=data.motif_reservation; $("besoin_zoom").checked=data.besoin_zoom; $("besoin_ordinateur").checked=data.besoin_ordi; $("lien_zoom").value=data.lien_zoom; - $("motif_refus").value=data.motif_refus; + + if (id_user != data.employe_id){ + $("bouton-annuler").className = "d-none"; + } const modal = new bootstrap.Modal($("modalDetailReservation")); bootstrap.Modal.getOrCreateInstance($("modalReservationAttente")).hide(); modal.show(); -}) +}) \ No newline at end of file