diff --git a/Back-End/DocuSeal/views.py b/Back-End/DocuSeal/views.py index dea59a4..0d46723 100644 --- a/Back-End/DocuSeal/views.py +++ b/Back-End/DocuSeal/views.py @@ -58,15 +58,15 @@ def clone_template(request): email = request.data.get('email') # Vérifier les données requises - if not document_id or not email : - return Response({'error': 'template ID, email are required'}, status=status.HTTP_400_BAD_REQUEST) + if not document_id : + return Response({'error': 'template ID is required'}, status=status.HTTP_400_BAD_REQUEST) # URL de l'API de DocuSeal pour cloner le template clone_url = f'https://docuseal.com/api/templates/{document_id}/clone' # Faire la requête pour cloner le template try: - response = requests.post(clone_url, json={'submitters': [{'email': email}]}, headers={ + response = requests.post(clone_url, headers={ 'Content-Type': 'application/json', 'X-Auth-Token': settings.DOCUSEAL_JWT['API_KEY'] }) @@ -75,7 +75,27 @@ def clone_template(request): return Response({'error': 'Failed to clone template'}, status=response.status_code) data = response.json() - return Response(data, status=status.HTTP_200_OK) + + # URL de l'API de DocuSeal pour créer une submission + submission_url = f'https://docuseal.com/api/submissions' + + # Faire la requête pour cloner le template + try: + clone_id = data['id'] + response = requests.post(submission_url, json={'template_id':clone_id, 'send_email': False, 'submitters': [{'email': email}]}, headers={ + 'Content-Type': 'application/json', + 'X-Auth-Token': settings.DOCUSEAL_JWT['API_KEY'] + }) + + if response.status_code != status.HTTP_200_OK: + return Response({'error': 'Failed to create submission'}, status=response.status_code) + + data = response.json() + data[0]['template_id'] = clone_id + return Response(data[0], status=status.HTTP_200_OK) + + except requests.RequestException as e: + return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) except requests.RequestException as e: return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) diff --git a/Back-End/Subscriptions/models.py b/Back-End/Subscriptions/models.py index a943ead..f889116 100644 --- a/Back-End/Subscriptions/models.py +++ b/Back-End/Subscriptions/models.py @@ -180,26 +180,6 @@ class RegistrationTemplateMaster(models.Model): def __str__(self): return f'{self.group.name} - {self.template_id}' -class RegistrationTemplate(models.Model): - master = models.ForeignKey(RegistrationTemplateMaster, on_delete=models.CASCADE, related_name='templates') - template_id = models.IntegerField(primary_key=True) - name = models.CharField(max_length=255, default="") - registration_form = models.ForeignKey('RegistrationForm', on_delete=models.CASCADE, related_name='templates') - - def __str__(self): - return self.name - - @staticmethod - def get_files_from_rf(register_form_id): - """ - Récupère tous les fichiers liés à un dossier d’inscription donné. - """ - registration_files = RegistrationTemplate.objects.filter(register_form_id=register_form_id).order_by('template__order') - filenames = [] - for reg_file in registration_files: - filenames.append(reg_file.file.path) - return filenames - class RegistrationForm(models.Model): class RegistrationFormStatus(models.IntegerChoices): RF_ABSENT = 0, _('Pas de dossier d\'inscription') @@ -238,3 +218,24 @@ class RegistrationForm(models.Model): def __str__(self): return "RF_" + self.student.last_name + "_" + self.student.first_name + +class RegistrationTemplate(models.Model): + master = models.ForeignKey(RegistrationTemplateMaster, on_delete=models.CASCADE, related_name='templates') + template_id = models.IntegerField(primary_key=True) + slug = models.CharField(max_length=255, default="") + name = models.CharField(max_length=255, default="") + registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='templates') + + def __str__(self): + return self.name + + @staticmethod + def get_files_from_rf(register_form_id): + """ + Récupère tous les fichiers liés à un dossier d’inscription donné. + """ + registration_files = RegistrationTemplate.objects.filter(register_form_id=register_form_id).order_by('template__order') + filenames = [] + for reg_file in registration_files: + filenames.append(reg_file.file.path) + return filenames \ No newline at end of file diff --git a/Back-End/Subscriptions/serializers.py b/Back-End/Subscriptions/serializers.py index 90d8517..aef6148 100644 --- a/Back-End/Subscriptions/serializers.py +++ b/Back-End/Subscriptions/serializers.py @@ -28,10 +28,18 @@ class GuardianSimpleSerializer(serializers.ModelSerializer): class RegistrationFormSimpleSerializer(serializers.ModelSerializer): guardians = GuardianSimpleSerializer(many=True, source='student.guardians') + last_name = serializers.SerializerMethodField() + first_name = serializers.SerializerMethodField() class Meta: model = RegistrationForm - fields = ['student_id', 'guardians'] + fields = ['student_id', 'last_name', 'first_name', 'guardians'] + + def get_last_name(self, obj): + return obj.student.last_name + + def get_first_name(self, obj): + return obj.student.first_name class RegistrationFileGroupSerializer(serializers.ModelSerializer): registration_forms = serializers.SerializerMethodField() diff --git a/Back-End/Subscriptions/urls.py b/Back-End/Subscriptions/urls.py index 41dc824..e8e705b 100644 --- a/Back-End/Subscriptions/urls.py +++ b/Back-End/Subscriptions/urls.py @@ -9,13 +9,14 @@ from .views import StudentView, GuardianView, ChildrenListView, StudentListView # Files from .views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView from .views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group -from .views import registration_file_views +from .views import registration_file_views, get_templates_by_rf urlpatterns = [ re_path(r'^registerForms/(?P[0-9]+)/archive$', archive, name="archive"), re_path(r'^registerForms/(?P[0-9]+)/resend$', resend, name="resend"), re_path(r'^registerForms/(?P[0-9]+)/send$', send, name="send"), re_path(r'^registerForms/(?P[0-9]+)$', RegisterFormWithIdView.as_view(), name="registerForm"), + re_path(r'^registerForms/(?P[0-9]+)/templates$', get_templates_by_rf, name="get_templates_by_rf"), re_path(r'^registerForms$', RegisterFormView.as_view(), name="registerForms"), # Page INSCRIPTION - Liste des élèves diff --git a/Back-End/Subscriptions/views/__init__.py b/Back-End/Subscriptions/views/__init__.py index bd5e65f..7fbf22e 100644 --- a/Back-End/Subscriptions/views/__init__.py +++ b/Back-End/Subscriptions/views/__init__.py @@ -1,4 +1,4 @@ -from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive +from .register_form_views import RegisterFormView, RegisterFormWithIdView, send, resend, archive, get_templates_by_rf from .registration_file_views import RegistrationTemplateMasterView, RegistrationTemplateMasterSimpleView, RegistrationTemplateView, RegistrationTemplateSimpleView from .registration_file_group_views import RegistrationFileGroupView, RegistrationFileGroupSimpleView, get_registration_files_by_group from .student_views import StudentView, StudentListView, ChildrenListView @@ -17,6 +17,7 @@ __all__ = [ 'RegistrationFileGroupView', 'RegistrationFileGroupSimpleView', 'get_registration_files_by_group', + 'get_templates_by_rf', 'StudentView', 'StudentListView', 'ChildrenListView', diff --git a/Back-End/Subscriptions/views/register_form_views.py b/Back-End/Subscriptions/views/register_form_views.py index efe08bf..1b11bec 100644 --- a/Back-End/Subscriptions/views/register_form_views.py +++ b/Back-End/Subscriptions/views/register_form_views.py @@ -383,3 +383,23 @@ def resend(request,id): return JsonResponse({"message": f"Le dossier a été renvoyé à l'adresse {email}"}, safe=False) return JsonResponse({"errorMessage":errorMessage}, safe=False, status=status.HTTP_400_BAD_REQUEST) return JsonResponse({"errorMessage":'Dossier d\'inscription non trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) + +@swagger_auto_schema( + method='get', + responses={200: openapi.Response('Success', schema=openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + 'message': openapi.Schema(type=openapi.TYPE_STRING) + } + ))}, + operation_description="Récupère les fichiers à signer d'un dossier d'inscription donné", + operation_summary="Récupérer les fichiers à signer d'un dossier d'inscription donné" +) +@api_view(['GET']) +def get_templates_by_rf(request, id): + try: + templates = RegistrationTemplate.objects.filter(registration_form=id) + templates_data = list(templates.values()) + return JsonResponse(templates_data, safe=False) + except RegistrationFileGroup.DoesNotExist: + return JsonResponse({'error': 'Le groupe de fichiers n\'a pas été trouvé'}, status=404) \ No newline at end of file diff --git a/Back-End/Subscriptions/views/registration_file_group_views.py b/Back-End/Subscriptions/views/registration_file_group_views.py index a74af89..dc016fc 100644 --- a/Back-End/Subscriptions/views/registration_file_group_views.py +++ b/Back-End/Subscriptions/views/registration_file_group_views.py @@ -118,8 +118,8 @@ class RegistrationFileGroupSimpleView(APIView): def get_registration_files_by_group(request, id): try: group = RegistrationFileGroup.objects.get(id=id) - templates = RegistrationTemplateMaster.objects.filter(groups=group) - templates_data = list(templates.values()) + templateMasters = RegistrationTemplateMaster.objects.filter(groups=group) + templates_data = list(templateMasters.values()) return JsonResponse(templates_data, safe=False) except RegistrationFileGroup.DoesNotExist: return JsonResponse({'error': 'Le groupe de fichiers n\'a pas été trouvé'}, status=404) \ No newline at end of file diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index 796b3e0..5f73ae8 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -391,6 +391,7 @@ useEffect(()=>{ // Sauvegarde des templates clonés dans la base de données const cloneData = { name: `clone_${clonedDocument.id}`, + slug: clonedDocument.slug, template_id: clonedDocument.id, master: templateMaster.template_id, registration_form: data.student.id @@ -468,7 +469,8 @@ useEffect(()=>{ // Sauvegarde des templates clonés dans la base de données const cloneData = { name: `clone_${clonedDocument.id}`, - template_id: clonedDocument.id, + slug: clonedDocument.slug, + template_id: clonedDocument.template_id, master: templateMaster.template_id, registration_form: data.student.id }; diff --git a/Front-End/src/app/actions/subscriptionAction.js b/Front-End/src/app/actions/subscriptionAction.js index 6bd1bbd..bcda2bc 100644 --- a/Front-End/src/app/actions/subscriptionAction.js +++ b/Front-End/src/app/actions/subscriptionAction.js @@ -138,4 +138,17 @@ export async function getRegisterFormFileTemplate(fileId) { throw new Error('Failed to fetch file template'); } return response.json(); +} + +export const fetchTemplatesFromRegistrationFiles = async (id) => { + const response = await fetch(`${BE_SUBSCRIPTION_REGISTERFORMS_URL}/${id}/templates`, { + credentials: 'include', + headers: { + 'Accept': 'application/json', + } + }); + if (!response.ok) { + throw new Error('Erreur lors de la récupération des fichiers associés au groupe'); + } + return response.json(); } \ No newline at end of file diff --git a/Front-End/src/components/Inscription/InscriptionFormShared.js b/Front-End/src/components/Inscription/InscriptionFormShared.js index f32d094..067f655 100644 --- a/Front-End/src/components/Inscription/InscriptionFormShared.js +++ b/Front-End/src/components/Inscription/InscriptionFormShared.js @@ -7,7 +7,7 @@ import Loader from '@/components/Loader'; import Button from '@/components/Button'; import DjangoCSRFToken from '@/components/DjangoCSRFToken'; import Table from '@/components/Table'; -import { fetchRegistrationTemplateMaster, createRegistrationTemplates, fetchRegisterForm, deleteRegistrationTemplates } from '@/app/actions/subscriptionAction'; +import { fetchRegistrationTemplateMaster, createRegistrationTemplates, fetchRegisterForm, deleteRegistrationTemplates, fetchTemplatesFromRegistrationFiles } from '@/app/actions/subscriptionAction'; import { fetchRegistrationFileFromGroup } from '@/app/actions/registerFileGroupAction'; import { Download, Upload, Trash2, Eye } from 'lucide-react'; import { BASE_URL } from '@/utils/Url'; @@ -18,6 +18,7 @@ import logger from '@/utils/logger'; import StudentInfoForm from '@/components/Inscription/StudentInfoForm'; import FilesToSign from '@/components/Inscription/FilesToSign'; import FilesToUpload from '@/components/Inscription/FilesToUpload'; +import { DocusealForm } from '@docuseal/react'; /** * Composant de formulaire d'inscription partagé @@ -83,7 +84,6 @@ export default function InscriptionFormShared({ }); setGuardians(data?.student?.guardians || []); setUploadedFiles(data.registration_files || []); - setFileGroup(data.fileGroup || null); }); setIsLoading(false); @@ -91,12 +91,10 @@ export default function InscriptionFormShared({ }, [studentId]); useEffect(() => { - if(fileGroup){ - fetchRegistrationFileFromGroup(fileGroup).then((data) => { - setFileTemplates(data); - }); - } - }, [fileGroup]); + fetchTemplatesFromRegistrationFiles(studentId).then((data) => { + setFileTemplates(data); + }) + }, []); // Fonctions de gestion du formulaire et des fichiers const updateFormField = (field, value) => { @@ -190,12 +188,7 @@ export default function InscriptionFormShared({ setCurrentPage(currentPage - 1); }; - const requiredFileTemplates = fileTemplates.filter(template => template.is_required); - - // Ajout des logs pour débogage - console.log('BASE_URL:', BASE_URL); - console.log('requiredFileTemplates:', requiredFileTemplates); - console.log('currentPage:', currentPage); + const requiredFileTemplates = fileTemplates; // Configuration des colonnes pour le tableau des fichiers const columns = [ @@ -275,15 +268,18 @@ export default function InscriptionFormShared({ {currentPage > 1 && currentPage <= requiredFileTemplates.length + 1 && (

{requiredFileTemplates[currentPage - 2].name}

- +
)} diff --git a/Front-End/src/components/Structure/Files/FileUpload.js b/Front-End/src/components/Structure/Files/FileUpload.js index edff3d6..339ee5b 100644 --- a/Front-End/src/components/Structure/Files/FileUpload.js +++ b/Front-End/src/components/Structure/Files/FileUpload.js @@ -16,8 +16,7 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl const [templateMaster, setTemplateMaster] = useState(null); const [uploadedFileName, setUploadedFileName] = useState(''); const [selectedGroups, setSelectedGroups] = useState([]); - const [guardianEmails, setGuardianEmails] = useState([]); - const [registrationFormIds, setRegistrationFormIds] = useState([]); + const [guardianDetails, setGuardianDetails] = useState([]); const csrfToken = useCsrfToken(); @@ -61,25 +60,22 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl const handleGroupChange = (selectedGroups) => { setSelectedGroups(selectedGroups); - const emails = selectedGroups.flatMap(group => group.registration_forms.flatMap(form => form.guardians.map(guardian => guardian.email))); - setGuardianEmails(emails); // Mettre à jour la variable d'état avec les emails des guardians - - const registrationFormIds = selectedGroups.flatMap(group => group.registration_forms.map(form => form.student_id)); - setRegistrationFormIds(registrationFormIds); // Mettre à jour la variable d'état avec les IDs des dossiers d'inscription - - logger.debug('Emails des Guardians associés aux groupes sélectionnés:', emails); - logger.debug('IDs des dossiers d\'inscription associés aux groupes sélectionnés:', registrationFormIds); + const details = selectedGroups.flatMap(group => + group.registration_forms.flatMap(form => + form.guardians.map(guardian => ({ + email: guardian.email, + last_name: form.last_name, + first_name: form.first_name, + registration_form: form.student_id + })) + ) + ); + setGuardianDetails(details); // Mettre à jour la variable d'état avec les détails des guardians }; const handleLoad = (detail) => { const templateId = detail?.id; setTemplateMaster(detail); - if (fileToEdit) { - logger.debug('Editing master ID :', templateId); - } - else { - logger.debug('Opening master ID :', templateId); - } } const handleUpload = (detail) => { @@ -87,6 +83,11 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl setUploadedFileName(detail.name); }; + const handleChange = (detail) => { + logger.debug(detail) + setUploadedFileName(detail.name); + } + const handleSubmit = () => { if (fileToEdit) { logger.debug('Modification du template master:', templateMaster?.id); @@ -95,8 +96,7 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl group_ids: selectedGroups.map(group => group.id), template_id: templateMaster?.id }); - } - else { + } else { logger.debug('Création du template master:', templateMaster?.id); handleCreateTemplateMaster({ name: uploadedFileName, @@ -104,18 +104,17 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl template_id: templateMaster?.id }); - guardianEmails.forEach((email, index) => { - cloneTemplate(templateMaster?.id, email) + guardianDetails.forEach((guardian, index) => { + cloneTemplate(templateMaster?.id, guardian.email) .then(clonedDocument => { - // Sauvegarde des templates clonés dans la base de données const data = { - name: `clone_${clonedDocument.id}`, - template_id: clonedDocument.id, + name: `${uploadedFileName}_${guardian.first_name}_${guardian.last_name}`, + slug: clonedDocument.slug, + template_id: clonedDocument.template_id, master: templateMaster?.id, - registration_form: registrationFormIds[index] + registration_form: guardian.registration_form }; - createRegistrationTemplates(data, csrfToken) .then(response => { logger.debug('Template enregistré avec succès:', response); @@ -125,16 +124,14 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl logger.error('Erreur lors de l\'enregistrement du template:', error); }); - // Logique pour envoyer chaque template au submitter - logger.debug('Sending template to:', email); + logger.debug('Sending template to:', guardian.email); }) .catch(error => { logger.error('Error during cloning or sending:', error); }); }); } - }; return ( @@ -164,6 +161,7 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl language={'fr'} onLoad={handleLoad} onUpload={handleUpload} + onChange={handleChange} onSave={handleSubmit} className="h-full overflow-auto" // Ajouter overflow-auto pour permettre le défilement style={{ maxHeight: '70vh' }} // Limiter la hauteur maximale du composant diff --git a/Front-End/src/components/Structure/Files/FilesGroupsManagement.js b/Front-End/src/components/Structure/Files/FilesGroupsManagement.js index 7977445..7274210 100644 --- a/Front-End/src/components/Structure/Files/FilesGroupsManagement.js +++ b/Front-End/src/components/Structure/Files/FilesGroupsManagement.js @@ -166,8 +166,9 @@ export default function FilesGroupsManagement({ csrfToken }) { // Transformer le fichier mis à jour avec les informations du groupe const transformedFile = transformFileData(data, groups); setTemplateMasters(prevFichiers => - prevFichiers.map(f => f.id === template_id ? transformedFile : f) + prevFichiers.map(f => f.template_id === template_id ? transformedFile : f) ); + setIsModalOpen(false); }) .catch(error => { console.error('Error editing file:', error);