diff --git a/Back-End/Auth/migrations/0001_initial.py b/Back-End/Auth/migrations/0001_initial.py
index 38f75fe..4d83049 100644
--- a/Back-End/Auth/migrations/0001_initial.py
+++ b/Back-End/Auth/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1.3 on 2026-04-05 08:05
+# Generated by Django 5.1.3 on 2026-04-05 14:05
import django.contrib.auth.models
import django.contrib.auth.validators
diff --git a/Back-End/Common/migrations/0001_initial.py b/Back-End/Common/migrations/0001_initial.py
index 6070e9f..2c148f5 100644
--- a/Back-End/Common/migrations/0001_initial.py
+++ b/Back-End/Common/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1.3 on 2026-04-05 08:05
+# Generated by Django 5.1.3 on 2026-04-05 14:04
import django.db.models.deletion
from django.db import migrations, models
diff --git a/Back-End/Establishment/migrations/0001_initial.py b/Back-End/Establishment/migrations/0001_initial.py
index e171153..953239e 100644
--- a/Back-End/Establishment/migrations/0001_initial.py
+++ b/Back-End/Establishment/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1.3 on 2026-04-05 08:05
+# Generated by Django 5.1.3 on 2026-04-05 14:04
import Establishment.models
import django.contrib.postgres.fields
diff --git a/Back-End/GestionMessagerie/migrations/0001_initial.py b/Back-End/GestionMessagerie/migrations/0001_initial.py
index 8d92423..899add7 100644
--- a/Back-End/GestionMessagerie/migrations/0001_initial.py
+++ b/Back-End/GestionMessagerie/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1.3 on 2026-04-05 08:05
+# Generated by Django 5.1.3 on 2026-04-05 14:05
import django.db.models.deletion
import django.utils.timezone
diff --git a/Back-End/GestionNotification/migrations/0001_initial.py b/Back-End/GestionNotification/migrations/0001_initial.py
index 8198a27..768f5f1 100644
--- a/Back-End/GestionNotification/migrations/0001_initial.py
+++ b/Back-End/GestionNotification/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1.3 on 2026-04-05 08:05
+# Generated by Django 5.1.3 on 2026-04-05 14:05
import django.db.models.deletion
from django.conf import settings
diff --git a/Back-End/Planning/migrations/0001_initial.py b/Back-End/Planning/migrations/0001_initial.py
index 7b95e54..2e6fac1 100644
--- a/Back-End/Planning/migrations/0001_initial.py
+++ b/Back-End/Planning/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1.3 on 2026-04-05 08:05
+# Generated by Django 5.1.3 on 2026-04-05 14:05
import django.db.models.deletion
from django.db import migrations, models
diff --git a/Back-End/School/migrations/0001_initial.py b/Back-End/School/migrations/0001_initial.py
index 5b5aa28..5449b07 100644
--- a/Back-End/School/migrations/0001_initial.py
+++ b/Back-End/School/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1.3 on 2026-04-05 08:05
+# Generated by Django 5.1.3 on 2026-04-05 14:05
import django.contrib.postgres.fields
import django.db.models.deletion
@@ -124,7 +124,6 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=100)),
('updated_date', models.DateTimeField(auto_now=True)),
('color_code', models.CharField(default='#FFFFFF', max_length=7)),
- ('school_year', models.CharField(blank=True, max_length=9)),
('establishment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='specialities', to='Establishment.establishment')),
],
),
@@ -154,7 +153,6 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last_name', models.CharField(max_length=100)),
('first_name', models.CharField(max_length=100)),
- ('school_year', models.CharField(blank=True, max_length=9)),
('updated_date', models.DateTimeField(auto_now=True)),
('profile_role', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='teacher_profile', to='Auth.profilerole')),
('specialities', models.ManyToManyField(blank=True, to='School.speciality')),
diff --git a/Back-End/Settings/migrations/0001_initial.py b/Back-End/Settings/migrations/0001_initial.py
index dfb4083..02fd817 100644
--- a/Back-End/Settings/migrations/0001_initial.py
+++ b/Back-End/Settings/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1.3 on 2026-04-05 08:05
+# Generated by Django 5.1.3 on 2026-04-05 14:04
import django.db.models.deletion
from django.db import migrations, models
diff --git a/Back-End/Subscriptions/migrations/0001_initial.py b/Back-End/Subscriptions/migrations/0001_initial.py
index 5e846df..659161b 100644
--- a/Back-End/Subscriptions/migrations/0001_initial.py
+++ b/Back-End/Subscriptions/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.1.3 on 2026-04-05 08:05
+# Generated by Django 5.1.3 on 2026-04-05 14:04
import Subscriptions.models
import django.db.models.deletion
@@ -54,6 +54,8 @@ class Migration(migrations.Migration):
('file', models.FileField(blank=True, null=True, upload_to=Subscriptions.models.registration_school_file_upload_to)),
('formTemplateData', models.JSONField(blank=True, default=list, null=True)),
('isValidated', models.BooleanField(blank=True, default=None, null=True)),
+ ('electronic_signature', models.TextField(blank=True, help_text='Signature électronique encodée en base64', null=True)),
+ ('electronic_signature_date', models.DateTimeField(blank=True, help_text='Date de la signature électronique', null=True)),
],
),
migrations.CreateModel(
@@ -169,6 +171,7 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(default='', max_length=255)),
('is_required', models.BooleanField(default=False)),
+ ('requires_electronic_signature', models.BooleanField(default=False, help_text='Si activé, le parent devra signer électroniquement ce document')),
('formMasterData', models.JSONField(blank=True, default=list, null=True)),
('file', models.FileField(blank=True, help_text='Fichier du formulaire existant (PDF, DOC, etc.)', null=True, upload_to=Subscriptions.models.registration_school_file_master_upload_to)),
('establishment', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='school_file_masters', to='Establishment.establishment')),
diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py
index 749b2c6..91a4413 100644
--- a/Back-End/Subscriptions/models.py
+++ b/Back-End/Subscriptions/models.py
@@ -384,6 +384,7 @@ class RegistrationSchoolFileMaster(models.Model):
groups = models.ManyToManyField(RegistrationFileGroup, related_name='school_file_masters', blank=True)
name = models.CharField(max_length=255, default="")
is_required = models.BooleanField(default=False)
+ requires_electronic_signature = models.BooleanField(default=False, help_text="Si activé, le parent devra signer électroniquement ce document")
formMasterData = models.JSONField(default=list, blank=True, null=True)
file = models.FileField(
upload_to=registration_school_file_master_upload_to,
@@ -558,6 +559,9 @@ class RegistrationSchoolFileTemplate(models.Model):
formTemplateData = models.JSONField(default=list, blank=True, null=True)
# Tri-etat: None=en attente, True=valide, False=refuse
isValidated = models.BooleanField(null=True, blank=True, default=None)
+ # Signature électronique (base64 SVG ou PNG)
+ electronic_signature = models.TextField(null=True, blank=True, help_text="Signature électronique encodée en base64")
+ electronic_signature_date = models.DateTimeField(null=True, blank=True, help_text="Date de la signature électronique")
def __str__(self):
return self.name
diff --git a/Back-End/Subscriptions/serializers.py b/Back-End/Subscriptions/serializers.py
index fec83fc..8748e39 100644
--- a/Back-End/Subscriptions/serializers.py
+++ b/Back-End/Subscriptions/serializers.py
@@ -57,6 +57,8 @@ class RegistrationSchoolFileTemplateSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
file_url = serializers.SerializerMethodField()
master_file_url = serializers.SerializerMethodField()
+ requires_electronic_signature = serializers.SerializerMethodField()
+ is_electronically_signed = serializers.SerializerMethodField()
class Meta:
model = RegistrationSchoolFileTemplate
@@ -72,6 +74,27 @@ class RegistrationSchoolFileTemplateSerializer(serializers.ModelSerializer):
return obj.master.file.url
return None
+ def get_requires_electronic_signature(self, obj):
+ # Retourne si le document nécessite une signature électronique
+ if obj.master:
+ return obj.master.requires_electronic_signature
+ return False
+
+ def get_is_electronically_signed(self, obj):
+ # Retourne True si le document a été signé électroniquement
+ return bool(obj.electronic_signature)
+
+ def update(self, instance, validated_data):
+ # Auto-remplir la date de signature si electronic_signature est fournie
+ from django.utils import timezone
+ if 'electronic_signature' in validated_data and validated_data['electronic_signature']:
+ # Nouvelle signature ou re-signature : enregistrer la date
+ validated_data['electronic_signature_date'] = timezone.now()
+ # Si le document était refusé, le repasser en attente de validation
+ if instance.isValidated == False:
+ validated_data['isValidated'] = None
+ return super().update(instance, validated_data)
+
class RegistrationParentFileTemplateSerializer(serializers.ModelSerializer):
id = serializers.IntegerField(required=False)
file_url = serializers.SerializerMethodField()
diff --git a/Back-End/Subscriptions/templates/pdfs/dynamic_form.html b/Back-End/Subscriptions/templates/pdfs/dynamic_form.html
new file mode 100644
index 0000000..206b289
--- /dev/null
+++ b/Back-End/Subscriptions/templates/pdfs/dynamic_form.html
@@ -0,0 +1,245 @@
+
+
+
+
+ {{ pdf_title }}
+
+
+
+
+
{{ pdf_title }}
+
+ {% for field in fields %}
+ {% if field.kind == 'heading' %}
+ {% if field.level == 'heading1' %}
+
{{ field.text }}
+ {% elif field.level == 'heading2' %}
+
{{ field.text }}
+ {% elif field.level == 'heading3' %}
+
{{ field.text }}
+ {% elif field.level == 'heading4' %}
+
{{ field.text }}
+ {% elif field.level == 'heading5' %}
+
{{ field.text }}
+ {% else %}
+
{{ field.text }}
+ {% endif %}
+ {% elif field.kind == 'paragraph' %}
+
{{ field.text }}
+ {% elif field.kind == 'file' %}
+
+
{{ field.label }}
+
+ {% if field.has_preview %}
+ {% for preview in field.preview_pages %}
+
+
+
+ {% endfor %}
+ {% if field.total_pages > 1 %}
+
Aperçu de la première page sur {{ field.total_pages }} pages
+ {% endif %}
+ {% else %}
+
Aucun document source fourni
+ {% endif %}
+
+
+ {% elif field.kind == 'phone' %}
+
+
{{ field.label }}
+
+
+ {{ field.prefix }}
+
+ {% if field.value %}
+ {{ field.value }}
+ {% else %}
+
+ {% endif %}
+
+
+
+
+ {% elif field.kind == 'signature' %}
+
+
{{ field.label }}
+
+ {% if field.signature_src %}
+
+ {% else %}
+
+ {% endif %}
+
+
+ {% else %}
+
+
{{ field.label }}
+
+ {% if field.value %}
+ {{ field.value }}
+ {% else %}
+
+ {% endif %}
+
+ {% if field.field_type %}
+
Type: {{ field.field_type }}
+ {% endif %}
+
+ {% endif %}
+ {% endfor %}
+
+
+
\ No newline at end of file
diff --git a/Back-End/__version__.py b/Back-End/__version__.py
index 27fdca4..81f0fde 100644
--- a/Back-End/__version__.py
+++ b/Back-End/__version__.py
@@ -1 +1 @@
-__version__ = "0.0.3"
+__version__ = "0.0.4"
diff --git a/Front-End/package.json b/Front-End/package.json
index 5756fb9..e0c19e4 100644
--- a/Front-End/package.json
+++ b/Front-End/package.json
@@ -1,6 +1,6 @@
{
"name": "n3wt-school-front-end",
- "version": "0.0.3",
+ "version": "0.0.4",
"private": true,
"scripts": {
"dev": "next dev",
diff --git a/Front-End/src/components/Inscription/DynamicFormsList.js b/Front-End/src/components/Inscription/DynamicFormsList.js
index f9a0639..701f5fa 100644
--- a/Front-End/src/components/Inscription/DynamicFormsList.js
+++ b/Front-End/src/components/Inscription/DynamicFormsList.js
@@ -2,6 +2,8 @@
import React, { useState, useEffect } from 'react';
import FormRenderer from '@/components/Form/FormRenderer';
import FileUpload from '@/components/Form/FileUpload';
+import SignatureField from '@/components/Form/SignatureField';
+import Button from '@/components/Form/Button';
import {
CheckCircle,
Hourglass,
@@ -9,6 +11,7 @@ import {
Download,
Upload,
XCircle,
+ PenTool,
} from 'lucide-react';
import logger from '@/utils/logger';
import { getSecureFileUrl } from '@/utils/fileUrl';
@@ -20,6 +23,7 @@ import { getSecureFileUrl } from '@/utils/fileUrl';
* @param {Function} onFormSubmit - Callback appelé quand un formulaire est soumis
* @param {Boolean} enable - Si les formulaires sont modifiables
* @param {Function} onFileUpload - Callback appelé quand un fichier est sélectionné
+ * @param {Function} onSignatureSubmit - Callback appelé quand une signature est soumise
*/
export default function DynamicFormsList({
schoolFileTemplates,
@@ -27,7 +31,8 @@ export default function DynamicFormsList({
onFormSubmit,
enable = true,
onValidationChange,
- onFileUpload, // nouvelle prop pour gérer l'upload (à passer depuis le parent)
+ onFileUpload,
+ onSignatureSubmit,
}) {
const [currentTemplateIndex, setCurrentTemplateIndex] = useState(0);
const [formsData, setFormsData] = useState({});
@@ -82,6 +87,19 @@ export default function DynamicFormsList({
// vérifier si un fichier a déjà été uploadé sur le template
const template = schoolFileTemplates.find((tpl) => tpl.id === templateId);
if (template && template.file && !isDynamicForm(template)) {
+ // Si le document est refusé, on ne le considère pas comme complété
+ if (template.isValidated === false) {
+ return false;
+ }
+ return true;
+ }
+
+ // Vérifier si le document a été signé électroniquement ET non refusé
+ if (
+ template &&
+ template.is_electronically_signed &&
+ template.isValidated !== false
+ ) {
return true;
}
@@ -249,6 +267,30 @@ export default function DynamicFormsList({
}
};
+ // Handler pour soumettre la signature électronique
+ const handleSignature = async (templateId, signatureData) => {
+ if (!signatureData || !templateId) return;
+ try {
+ if (onSignatureSubmit) {
+ await onSignatureSubmit(templateId, signatureData);
+ setFormsData((prev) => ({
+ ...prev,
+ [templateId]: { ...prev[templateId], signed: true },
+ }));
+ setFormsValidation((prev) => ({
+ ...prev,
+ [templateId]: true,
+ }));
+ // Passer au formulaire suivant si disponible
+ if (currentTemplateIndex < schoolFileTemplates.length - 1) {
+ setCurrentTemplateIndex(currentTemplateIndex + 1);
+ }
+ }
+ } catch (error) {
+ logger.error('Erreur lors de la soumission de la signature :', error);
+ }
+ };
+
if (!schoolFileTemplates || schoolFileTemplates.length === 0) {
return (
@@ -325,7 +367,8 @@ export default function DynamicFormsList({
? 'text-secondary font-semibold'
: textClass;
canEdit = false;
- } else if (isValidated === false) {
+ } else if (isValidated === false && !isCompletedLocally) {
+ // Refusé uniquement si pas re-complété localement
statusLabel = 'Refusé';
statusColor = 'red';
icon =
;
@@ -337,30 +380,28 @@ export default function DynamicFormsList({
? 'text-red-900 font-semibold'
: 'text-red-700';
canEdit = true;
+ } else if (isCompletedLocally) {
+ statusLabel = 'Complété';
+ statusColor = 'orange';
+ icon =
;
+ bgClass = isActive ? 'bg-orange-200' : 'bg-orange-50';
+ borderClass = isActive
+ ? 'border border-orange-300'
+ : 'border border-orange-200';
+ textClass = isActive
+ ? 'text-orange-900 font-semibold'
+ : 'text-orange-700';
+ canEdit = true;
} else {
- if (isCompletedLocally) {
- statusLabel = 'Complété';
- statusColor = 'orange';
- icon =
;
- bgClass = isActive ? 'bg-orange-200' : 'bg-orange-50';
- borderClass = isActive
- ? 'border border-orange-300'
- : 'border border-orange-200';
- textClass = isActive
- ? 'text-orange-900 font-semibold'
- : 'text-orange-700';
- canEdit = true;
- } else {
- statusLabel = 'À compléter';
- statusColor = 'gray';
- icon =
;
- bgClass = isActive ? 'bg-gray-200' : '';
- borderClass = isActive ? 'border border-gray-300' : '';
- textClass = isActive
- ? 'text-gray-900 font-semibold'
- : 'text-gray-600';
- canEdit = true;
- }
+ statusLabel = 'À compléter';
+ statusColor = 'gray';
+ icon =
;
+ bgClass = isActive ? 'bg-gray-200' : '';
+ borderClass = isActive ? 'border border-gray-300' : '';
+ textClass = isActive
+ ? 'text-gray-900 font-semibold'
+ : 'text-gray-600';
+ canEdit = true;
}
return (
@@ -417,14 +458,14 @@ export default function DynamicFormsList({
Validé
- ) : currentTemplate.isValidated === false ? (
-
- Refusé
-
) : hasLocalCompletion(currentTemplate.id) ? (
Complété
+ ) : currentTemplate.isValidated === false ? (
+
+ Refusé
+
) : (
En attente
@@ -507,39 +548,137 @@ export default function DynamicFormsList({
/>
)}
- {/* Cas non validé : bouton télécharger + upload */}
+ {/* Cas non validé */}
{currentTemplate.isValidated !== true && (
- {/* Bouton télécharger le document source (fichier maître) */}
- {(currentTemplate.master_file_url ||
- currentTemplate.file) && (
-
+ {/* Affichage du document à signer */}
+ {(currentTemplate.master_file_url ||
+ currentTemplate.file) && (
+
)}
- className="flex items-center gap-2 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"
- download
- >
-
- Télécharger le document
-
- )}
- {/* Composant d'upload */}
- {enable && (
-
- handleUpload(file, currentTemplate)
- }
- existingFile={
- currentTemplate.file_url || currentTemplate.file
- }
- required
- enable={true}
- />
+ {/* Afficher si déjà signé et non refusé */}
+ {currentTemplate.is_electronically_signed &&
+ currentTemplate.isValidated !== false ? (
+
+
+
+
+ Document signé électroniquement
+
+ {currentTemplate.electronic_signature_date && (
+
+ Signé le{' '}
+ {new Date(
+ currentTemplate.electronic_signature_date
+ ).toLocaleDateString('fr-FR', {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ })}
+
+ )}
+
+
+ ) : (
+ /* Signature électronique si pas encore signé ou refusé */
+ enable && (
+
+ {/* Message si document refusé */}
+ {currentTemplate.isValidated === false && (
+
+
+
+ Ce document a été refusé. Veuillez le
+ signer à nouveau.
+
+
+ )}
+
{
+ setFormsData((prev) => ({
+ ...prev,
+ [currentTemplate.id]: {
+ ...(prev[currentTemplate.id] || {}),
+ signature: signatureData,
+ },
+ }));
+ }}
+ disabled={false}
+ />
+
+ handleSignature(
+ currentTemplate.id,
+ formsData[currentTemplate.id]?.signature
+ )
+ }
+ disabled={
+ !formsData[currentTemplate.id]?.signature
+ }
+ className="mt-4 w-full"
+ icon={ }
+ />
+
+ )
+ )}
+ >
+ ) : (
+ <>
+ {/* Document classique : télécharger + upload */}
+ {(currentTemplate.master_file_url ||
+ currentTemplate.file) && (
+
+
+ Télécharger le document
+
+ )}
+
+ {/* Composant d'upload */}
+ {enable && (
+
+ handleUpload(file, currentTemplate)
+ }
+ existingFile={
+ currentTemplate.file_url || currentTemplate.file
+ }
+ required
+ enable={true}
+ />
+ )}
+ >
)}
)}
diff --git a/Front-End/src/components/Inscription/InscriptionFormShared.js b/Front-End/src/components/Inscription/InscriptionFormShared.js
index 26e73de..27f0027 100644
--- a/Front-End/src/components/Inscription/InscriptionFormShared.js
+++ b/Front-End/src/components/Inscription/InscriptionFormShared.js
@@ -639,6 +639,50 @@ export default function InscriptionFormShared({
});
};
+ const handleSignatureSubmit = (templateId, signatureData) => {
+ if (!templateId || !signatureData) {
+ logger.error('Données manquantes pour la signature.');
+ return Promise.reject(
+ new Error('Données manquantes pour la signature.')
+ );
+ }
+
+ const updateData = new FormData();
+ updateData.append('electronic_signature', signatureData);
+
+ return editRegistrationSchoolFileTemplates(templateId, updateData, csrfToken)
+ .then((response) => {
+ logger.debug('Signature électronique enregistrée avec succès :', response);
+
+ // Mettre à jour schoolFileTemplates avec la signature
+ setSchoolFileTemplates((prevTemplates) =>
+ prevTemplates.map((template) =>
+ template.id === templateId
+ ? {
+ ...template,
+ electronic_signature: response.data.electronic_signature,
+ electronic_signature_date: response.data.electronic_signature_date,
+ is_electronically_signed: true,
+ // Repasser en attente si le document était refusé
+ isValidated:
+ response.data.isValidated !== undefined
+ ? response.data.isValidated
+ : template.isValidated === false
+ ? null
+ : template.isValidated,
+ }
+ : template
+ )
+ );
+
+ return response;
+ })
+ .catch((error) => {
+ logger.error('Erreur lors de l\'enregistrement de la signature :', error);
+ throw error;
+ });
+ };
+
const handleDeleteFile = (templateId) => {
const fileToDelete = uploadedFiles.find(
(file) => parseInt(file.id) === templateId && file.fileName
@@ -970,6 +1014,7 @@ export default function InscriptionFormShared({
onValidationChange={handleDynamicFormsValidationChange}
enable={enable}
onFileUpload={handleSchoolFileUpload}
+ onSignatureSubmit={handleSignatureSubmit}
/>
)}
diff --git a/Front-End/src/components/Inscription/ValidateSubscription.js b/Front-End/src/components/Inscription/ValidateSubscription.js
index b32d43e..d242150 100644
--- a/Front-End/src/components/Inscription/ValidateSubscription.js
+++ b/Front-End/src/components/Inscription/ValidateSubscription.js
@@ -9,7 +9,7 @@ import {
} from '@/app/actions/subscriptionAction';
import { getSecureFileUrl } from '@/utils/fileUrl';
import logger from '@/utils/logger';
-import { School, FileText } from 'lucide-react';
+import { School, FileText, PenTool } from 'lucide-react';
import SectionHeader from '@/components/SectionHeader';
import Button from '@/components/Form/Button';
@@ -161,6 +161,10 @@ export default function ValidateSubscription({
name: template.name || 'Document scolaire',
file: template.file,
description: template.description,
+ is_electronically_signed: template.is_electronically_signed,
+ electronic_signature: template.electronic_signature,
+ electronic_signature_date: template.electronic_signature_date,
+ requires_electronic_signature: template.requires_electronic_signature,
})),
...parentFileTemplates.map((template) => ({
name: template.master_name || 'Document parent',
@@ -213,9 +217,19 @@ export default function ValidateSubscription({
{currentTemplateIndex < allTemplates.length && (
-
- {allTemplates[currentTemplateIndex].name || 'Document sans nom'}
-
+
+
+ {allTemplates[currentTemplateIndex].name ||
+ 'Document sans nom'}
+
+ {/* Badge signature électronique */}
+ {allTemplates[currentTemplateIndex].is_electronically_signed && (
+
+
+ Signé électroniquement
+
+ )}
+
+ {/* Affichage de la signature électronique */}
+ {allTemplates[currentTemplateIndex].is_electronically_signed &&
+ allTemplates[currentTemplateIndex].electronic_signature && (
+
+
+
+
+ Signature électronique
+
+ {allTemplates[currentTemplateIndex]
+ .electronic_signature_date && (
+
+ - Signé le{' '}
+ {new Date(
+ allTemplates[
+ currentTemplateIndex
+ ].electronic_signature_date
+ ).toLocaleDateString('fr-FR', {
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ })}
+
+ )}
+
+
+
+ )}
)}
@@ -262,7 +315,27 @@ export default function ValidateSubscription({
)}
-
{template.name}
+
+ {template.name}
+ {/* Badge signature électronique */}
+ {template.is_electronically_signed && (
+
+
+ Signé
+
+ )}
+ {/* Alerte si signature requise mais pas signée */}
+ {template.requires_electronic_signature &&
+ !template.is_electronically_signed && (
+
+
+ À signer
+
+ )}
+
{/* 2 boutons : Validé / Refusé (sauf fiche élève) */}
{index !== 0 && (
diff --git a/Front-End/src/components/Structure/Files/FilesGroupsManagement.js b/Front-End/src/components/Structure/Files/FilesGroupsManagement.js
index 4527b0f..bd771b7 100644
--- a/Front-End/src/components/Structure/Files/FilesGroupsManagement.js
+++ b/Front-End/src/components/Structure/Files/FilesGroupsManagement.js
@@ -348,6 +348,7 @@ export default function FilesGroupsManagement({
group_ids,
formMasterData,
file,
+ requires_electronic_signature,
},
onCreated
) => {
@@ -358,6 +359,7 @@ export default function FilesGroupsManagement({
groups: group_ids,
formMasterData,
establishment: selectedEstablishmentId,
+ requires_electronic_signature: requires_electronic_signature || false,
};
dataToSend.append('data', JSON.stringify(jsonData));
if (file) {
@@ -402,6 +404,7 @@ export default function FilesGroupsManagement({
formMasterData,
id,
file,
+ requires_electronic_signature,
}) => {
// Correction : normaliser group_ids pour ne garder que les IDs (number/string)
let normalizedGroupIds = [];
@@ -417,6 +420,7 @@ export default function FilesGroupsManagement({
groups: normalizedGroupIds,
formMasterData: formMasterData,
establishment: selectedEstablishmentId,
+ requires_electronic_signature: requires_electronic_signature || false,
};
dataToSend.append('data', JSON.stringify(jsonData));
@@ -803,18 +807,12 @@ export default function FilesGroupsManagement({
à droite de la liste des documents pour ajouter :
-
-
- Formulaire personnalisé
- {' '}
- : créé dynamiquement par l'école, à remplir et/ou signer
- électroniquement par la famille.
-
Formulaire existant
{' '}
- : importez un PDF ou autre document à faire remplir.
+ : importez un PDF ou autre document à faire remplir. Vous pouvez
+ activer la signature électronique.
@@ -962,16 +960,6 @@ export default function FilesGroupsManagement({
}
items={[
- {
- type: 'item',
- label: (
-
-
- Formulaire personnalisé
-
- ),
- onClick: () => handleDocDropdownSelect('formulaire'),
- },
{
type: 'item',
label: (
@@ -1117,12 +1105,16 @@ export default function FilesGroupsManagement({
group_ids: fileToEdit.groups,
file: fileToEdit.file,
formMasterData: fileToEdit.formMasterData,
+ requires_electronic_signature:
+ fileToEdit.requires_electronic_signature || false,
});
} else {
handleCreateSchoolFileMaster({
name: fileToEdit.name,
group_ids: fileToEdit.groups,
file: fileToEdit.file,
+ requires_electronic_signature:
+ fileToEdit.requires_electronic_signature || false,
});
}
setIsFileUploadPopupOpen(false);
@@ -1199,6 +1191,22 @@ export default function FilesGroupsManagement({
required
enable
/>
+
+ setFileToEdit({
+ ...fileToEdit,
+ requires_electronic_signature:
+ !fileToEdit?.requires_electronic_signature,
+ })
+ }
+ fieldName="requires_electronic_signature"
+ itemLabelFunc={() => 'À signer électroniquement'}
+ />
+
+ setFileToEdit({
+ ...fileToEdit,
+ requires_electronic_signature:
+ !fileToEdit?.requires_electronic_signature,
+ })
+ }
+ fieldName="requires_electronic_signature"
+ itemLabelFunc={() => 'À signer électroniquement'}
+ />