From e3879f516b81b7e4b784049668b2507f12e8155f Mon Sep 17 00:00:00 2001 From: N3WT DE COMPET Date: Sun, 2 Mar 2025 12:35:53 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Gestion=20des=20documents=20n=C3=A9cess?= =?UTF-8?q?itant=20des=20signatures=20=C3=A9lectroniques=20et=20ceux=20ne?= =?UTF-8?q?=20n=C3=A9cessitant=20pas=20les=20signatures=20=C3=A9lectroniqu?= =?UTF-8?q?es=20[#22]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Back-End/DocuSeal/views.py | 46 +++++++++++-------- Back-End/Subscriptions/models.py | 15 +++--- Back-End/Subscriptions/serializers.py | 2 + .../views/registration_file_views.py | 12 ++--- .../app/[locale]/admin/subscriptions/page.js | 12 ++--- .../app/actions/registerFileGroupAction.js | 5 +- .../Inscription/InscriptionFormShared.js | 25 ++++++++-- .../components/Structure/Files/FileUpload.js | 17 ++++--- .../Structure/Files/FilesGroupsManagement.js | 32 ++++++------- .../src/pages/api/docuseal/cloneTemplate.js | 6 ++- 10 files changed, 103 insertions(+), 69 deletions(-) diff --git a/Back-End/DocuSeal/views.py b/Back-End/DocuSeal/views.py index a0c35cb..40f6b50 100644 --- a/Back-End/DocuSeal/views.py +++ b/Back-End/DocuSeal/views.py @@ -19,7 +19,7 @@ def generate_jwt_token(request): # Récupérer les données de la requête user_email = request.data.get('user_email') documents_urls = request.data.get('documents_urls', []) - template_id = request.data.get('template_id') # Récupérer le template_id + id = request.data.get('id') # Récupérer le id # Vérifier les données requises if not user_email: @@ -34,7 +34,7 @@ def generate_jwt_token(request): payload = { 'user_email': user_email, 'documents_urls': documents_urls, - 'template_id': template_id, # Ajouter le template_id au payload + 'template_id': id, # Ajouter le id au payload 'exp': datetime.datetime.utcnow() + expiration_delta # Temps d'expiration du token } @@ -54,6 +54,8 @@ def clone_template(request): # Récupérer les données de la requête document_id = request.data.get('templateId') email = request.data.get('email') + is_required = request.data.get('is_required') + print(f'test is required = {is_required}') # Vérifier les données requises if not document_id : @@ -74,26 +76,32 @@ def clone_template(request): data = response.json() - # URL de l'API de DocuSeal pour créer une submission - submission_url = f'https://docuseal.com/api/submissions' + if is_required: + print(f'REQUIRED -> création dune submission') + # 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'] - }) + # 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) + 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) + data = response.json() + data[0]['id'] = clone_id + print(f'DATA RESPONSE : {data[0]}') + 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) + else : + print(f'NOT REQUIRED -> on ne crée pas de submission') + return Response(data, status=status.HTTP_200_OK) 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 4eddb33..89efee9 100644 --- a/Back-End/Subscriptions/models.py +++ b/Back-End/Subscriptions/models.py @@ -173,12 +173,13 @@ def registration_file_path(instance, filename): return f'registration_files/dossier_rf_{instance.student_id}/{filename}' class RegistrationTemplateMaster(models.Model): - groups = models.ManyToManyField(RegistrationFileGroup, related_name='template_masters') - template_id = models.IntegerField(primary_key=True) + groups = models.ManyToManyField(RegistrationFileGroup, related_name='template_masters', blank=True) + id = models.IntegerField(primary_key=True) name = models.CharField(max_length=255, default="") + is_required = models.BooleanField(default=False) def __str__(self): - return f'{self.group.name} - {self.template_id}' + return f'{self.group.name} - {self.id}' class RegistrationForm(models.Model): class RegistrationFormStatus(models.IntegerChoices): @@ -220,14 +221,14 @@ class RegistrationForm(models.Model): return "RF_" + self.student.last_name + "_" + self.student.first_name def registration_file_upload_to(instance, filename): - return f"registration_files/dossier_rf_{instance.register_form.pk}/{filename}" + return f"registration_files/dossier_rf_{instance.registration_form.pk}/{filename}" class RegistrationTemplate(models.Model): - master = models.ForeignKey(RegistrationTemplateMaster, on_delete=models.CASCADE, related_name='templates') - template_id = models.IntegerField(primary_key=True) + master = models.ForeignKey(RegistrationTemplateMaster, on_delete=models.CASCADE, related_name='templates', blank=True) + 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') + registration_form = models.ForeignKey(RegistrationForm, on_delete=models.CASCADE, related_name='templates', blank=True) file = models.FileField(null=True,blank=True, upload_to=registration_file_upload_to) def __str__(self): diff --git a/Back-End/Subscriptions/serializers.py b/Back-End/Subscriptions/serializers.py index aef6148..bec6584 100644 --- a/Back-End/Subscriptions/serializers.py +++ b/Back-End/Subscriptions/serializers.py @@ -12,11 +12,13 @@ import pytz from datetime import datetime class RegistrationTemplateMasterSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) class Meta: model = RegistrationTemplateMaster fields = '__all__' class RegistrationTemplateSerializer(serializers.ModelSerializer): + id = serializers.IntegerField(required=False) class Meta: model = RegistrationTemplate fields = '__all__' diff --git a/Back-End/Subscriptions/views/registration_file_views.py b/Back-End/Subscriptions/views/registration_file_views.py index 9175fca..7df225a 100644 --- a/Back-End/Subscriptions/views/registration_file_views.py +++ b/Back-End/Subscriptions/views/registration_file_views.py @@ -44,7 +44,7 @@ class RegistrationTemplateMasterSimpleView(APIView): } ) def get(self, request, id): - master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='template_id', _value=id) + master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id) if master is None: return JsonResponse({"errorMessage":'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) serializer = RegistrationTemplateMasterSerializer(master) @@ -60,7 +60,7 @@ class RegistrationTemplateMasterSimpleView(APIView): } ) def put(self, request, id): - master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='template_id', _value=id) + master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id) if master is None: return JsonResponse({'erreur': 'Le master de template n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) serializer = RegistrationTemplateMasterSerializer(master, data=request.data) @@ -77,7 +77,7 @@ class RegistrationTemplateMasterSimpleView(APIView): } ) def delete(self, request, id): - master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='template_id', _value=id) + master = bdd.getObject(_objectName=RegistrationTemplateMaster, _columnName='id', _value=id) if master is not None: master.delete() return JsonResponse({'message': 'La suppression du master de template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT) @@ -118,7 +118,7 @@ class RegistrationTemplateSimpleView(APIView): } ) def get(self, request, id): - template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='template_id', _value=id) + template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id) if template is None: return JsonResponse({"errorMessage":'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) serializer = RegistrationTemplateSerializer(template) @@ -134,7 +134,7 @@ class RegistrationTemplateSimpleView(APIView): } ) def put(self, request, id): - template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='template_id', _value=id) + template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id) if template is None: return JsonResponse({'erreur': 'Le template d\'inscription n\'a pas été trouvé'}, safe=False, status=status.HTTP_404_NOT_FOUND) serializer = RegistrationTemplateSerializer(template, data=request.data) @@ -151,7 +151,7 @@ class RegistrationTemplateSimpleView(APIView): } ) def delete(self, request, id): - template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='template_id', _value=id) + template = bdd.getObject(_objectName=RegistrationTemplate, _columnName='id', _value=id) if template is not None: template.delete() return JsonResponse({'message': 'La suppression du template a été effectuée avec succès'}, safe=False, status=status.HTTP_204_NO_CONTENT) diff --git a/Front-End/src/app/[locale]/admin/subscriptions/page.js b/Front-End/src/app/[locale]/admin/subscriptions/page.js index 5f73ae8..5e68335 100644 --- a/Front-End/src/app/[locale]/admin/subscriptions/page.js +++ b/Front-End/src/app/[locale]/admin/subscriptions/page.js @@ -386,14 +386,14 @@ useEffect(()=>{ // Cloner les templates pour chaque templateMaster du fileGroup const masters = templateMasters.filter(file => file.groups.includes(selectedFileGroup)); const clonePromises = masters.map((templateMaster, index) => { - return cloneTemplate(templateMaster.template_id, guardianEmail) + return cloneTemplate(templateMaster.id, guardianEmail, templateMaster.is_required) .then(clonedDocument => { // 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, + id: clonedDocument.id, + master: templateMaster.id, registration_form: data.student.id }; @@ -464,14 +464,14 @@ useEffect(()=>{ // Cloner les templates pour chaque templateMaster du fileGroup const masters = templateMasters.filter(file => file.groups.includes(selectedFileGroup)); const clonePromises = masters.map((templateMaster, index) => { - return cloneTemplate(templateMaster.template_id, updatedData.guardianEmail) + return cloneTemplate(templateMaster.id, updatedData.guardianEmail, templateMaster.is_required) .then(clonedDocument => { // Sauvegarde des templates clonés dans la base de données const cloneData = { name: `clone_${clonedDocument.id}`, slug: clonedDocument.slug, - template_id: clonedDocument.template_id, - master: templateMaster.template_id, + id: clonedDocument.id, + master: templateMaster.id, registration_form: data.student.id }; diff --git a/Front-End/src/app/actions/registerFileGroupAction.js b/Front-End/src/app/actions/registerFileGroupAction.js index c051596..1c74d2d 100644 --- a/Front-End/src/app/actions/registerFileGroupAction.js +++ b/Front-End/src/app/actions/registerFileGroupAction.js @@ -197,7 +197,7 @@ export const editRegistrationTemplateMaster = (fileId, data, csrfToken) => { .then(requestResponseHandler) } -export const cloneTemplate = (templateId, email) => { +export const cloneTemplate = (templateId, email, is_required) => { return fetch(`${FE_API_DOCUSEAL_CLONE_URL}`, { method: 'POST', headers: { @@ -205,7 +205,8 @@ export const cloneTemplate = (templateId, email) => { }, body: JSON.stringify({ templateId, - email + email, + is_required }) }) .then(requestResponseHandler) diff --git a/Front-End/src/components/Inscription/InscriptionFormShared.js b/Front-End/src/components/Inscription/InscriptionFormShared.js index 830885c..1aa1c46 100644 --- a/Front-End/src/components/Inscription/InscriptionFormShared.js +++ b/Front-End/src/components/Inscription/InscriptionFormShared.js @@ -8,7 +8,13 @@ import Button from '@/components/Button'; import DjangoCSRFToken from '@/components/DjangoCSRFToken'; import Table from '@/components/Table'; import { fetchRegisterForm, fetchTemplatesFromRegistrationFiles } from '@/app/actions/subscriptionAction'; -import { fetchRegistrationFileFromGroup, fetchRegistrationTemplateMaster, downloadTemplate, createRegistrationTemplates, deleteRegistrationTemplates } from '@/app/actions/registerFileGroupAction'; +import { fetchRegistrationFileFromGroup, + fetchRegistrationTemplateMaster, + downloadTemplate, + createRegistrationTemplates, + editRegistrationTemplates, + deleteRegistrationTemplates + } from '@/app/actions/registerFileGroupAction'; import { Download, Upload, Trash2, Eye } from 'lucide-react'; import { BASE_URL } from '@/utils/Url'; import DraggableFileUpload from '@/components/DraggableFileUpload'; @@ -277,10 +283,21 @@ export default function InscriptionFormShared({ withDownloadButton={false} onComplete={() => { downloadTemplate(requiredFileTemplates[currentPage - 2].slug) - .then((data) => { - logger.debug("PDF URL : ", data) + .then((data) => fetch(data)) + .then((response) => response.blob()) + .then((blob) => { + const file = new File([blob], `${requiredFileTemplates[currentPage - 2].name}.pdf`, { type: blob.type }); + const updateData = new FormData(); + updateData.append('file', file); + + return editRegistrationTemplates(requiredFileTemplates[currentPage - 2].id, updateData, csrfToken); }) - .catch((error) => console.error(error)); + .then((data) => { + logger.debug("EDIT TEMPLATE : ", data); + }) + .catch((error) => { + logger.error("error editing template : ", error); + }); }} > diff --git a/Front-End/src/components/Structure/Files/FileUpload.js b/Front-End/src/components/Structure/Files/FileUpload.js index 339ee5b..8c02ebc 100644 --- a/Front-End/src/components/Structure/Files/FileUpload.js +++ b/Front-End/src/components/Structure/Files/FileUpload.js @@ -33,7 +33,7 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl const body = fileToEdit ? JSON.stringify({ user_email: 'n3wt.school@gmail.com', - template_id: fileToEdit.template_id + id: fileToEdit.id }) : JSON.stringify({ user_email: 'n3wt.school@gmail.com' @@ -75,6 +75,7 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl const handleLoad = (detail) => { const templateId = detail?.id; + logger.debug('loading template id : ', detail) setTemplateMaster(detail); } @@ -88,30 +89,34 @@ export default function FileUpload({ handleCreateTemplateMaster, handleEditTempl setUploadedFileName(detail.name); } - const handleSubmit = () => { + const handleSubmit = (data) => { + const is_required = (data.fields.length > 0) if (fileToEdit) { logger.debug('Modification du template master:', templateMaster?.id); handleEditTemplateMaster({ name: uploadedFileName, group_ids: selectedGroups.map(group => group.id), - template_id: templateMaster?.id + id: templateMaster?.id, + is_required: is_required }); } else { logger.debug('Création du template master:', templateMaster?.id); handleCreateTemplateMaster({ name: uploadedFileName, group_ids: selectedGroups.map(group => group.id), - template_id: templateMaster?.id + id: templateMaster?.id, + is_required: is_required }); guardianDetails.forEach((guardian, index) => { - cloneTemplate(templateMaster?.id, guardian.email) + logger.debug('creation du clone avec required : ', is_required) + cloneTemplate(templateMaster?.id, guardian.email, is_required) .then(clonedDocument => { // Sauvegarde des templates clonés dans la base de données const data = { name: `${uploadedFileName}_${guardian.first_name}_${guardian.last_name}`, slug: clonedDocument.slug, - template_id: clonedDocument.template_id, + id: clonedDocument.id, master: templateMaster?.id, registration_form: guardian.registration_form }; diff --git a/Front-End/src/components/Structure/Files/FilesGroupsManagement.js b/Front-End/src/components/Structure/Files/FilesGroupsManagement.js index 7274210..3b07826 100644 --- a/Front-End/src/components/Structure/Files/FilesGroupsManagement.js +++ b/Front-End/src/components/Structure/Files/FilesGroupsManagement.js @@ -50,10 +50,6 @@ export default function FilesGroupsManagement({ csrfToken }) { ]).then(([filesTemplateMasters, groupsData, filesTemplates]) => { setGroups(groupsData); setTemplates(filesTemplates); - // Sélectionner automatiquement le premier groupe s'il existe - if (groupsData.length > 0) { - setSelectedGroup(groupsData[0].id.toString()); - } // Transformer chaque fichier pour inclure les informations complètes du groupe const transformedFiles = filesTemplateMasters.map(file => transformFileData(file, groupsData)); setTemplateMasters(transformedFiles); @@ -67,11 +63,11 @@ export default function FilesGroupsManagement({ csrfToken }) { const deleteTemplateMaster = (templateMaster) => { // Supprimer les clones associés via l'API DocuSeal const removeClonesPromises = templates - .filter(template => template.master === templateMaster.template_id) - .map(template => removeTemplate(template.template_id)); + .filter(template => template.master === templateMaster.id) + .map(template => removeTemplate(template.id)); // Ajouter la suppression du master à la liste des promesses - removeClonesPromises.push(removeTemplate(templateMaster.template_id)); + removeClonesPromises.push(removeTemplate(templateMaster.id)); // Attendre que toutes les suppressions dans DocuSeal soient terminées Promise.all(removeClonesPromises) @@ -81,10 +77,10 @@ export default function FilesGroupsManagement({ csrfToken }) { logger.debug('Master et clones supprimés avec succès de DocuSeal.'); // Supprimer le template master de la base de données - deleteRegistrationTemplateMaster(templateMaster.template_id, csrfToken) + deleteRegistrationTemplateMaster(templateMaster.id, csrfToken) .then(response => { if (response.ok) { - setTemplateMasters(templateMasters.filter(fichier => fichier.template_id !== templateMaster.template_id)); + setTemplateMasters(templateMasters.filter(fichier => fichier.id !== templateMaster.id)); alert('Fichier supprimé avec succès.'); } else { alert('Erreur lors de la suppression du fichier dans la base de données.'); @@ -133,11 +129,12 @@ export default function FilesGroupsManagement({ csrfToken }) { setIsModalOpen(true); }; - const handleCreateTemplateMaster = ({name, group_ids, template_id}) => { + const handleCreateTemplateMaster = ({name, group_ids, id, is_required}) => { const data = { name: name, - template_id: template_id, - groups: group_ids + id: id, + groups: group_ids, + is_required: is_required }; logger.debug(data); @@ -153,20 +150,21 @@ export default function FilesGroupsManagement({ csrfToken }) { }); }; - const handleEditTemplateMaster = ({name, group_ids, template_id}) => { + const handleEditTemplateMaster = ({name, group_ids, id, is_required}) => { const data = { name: name, - template_id: template_id, - groups: group_ids + id: id, + groups: group_ids, + is_required: is_required }; logger.debug(data); - editRegistrationTemplateMaster(template_id, data, csrfToken) + editRegistrationTemplateMaster(id, data, csrfToken) .then(data => { // Transformer le fichier mis à jour avec les informations du groupe const transformedFile = transformFileData(data, groups); setTemplateMasters(prevFichiers => - prevFichiers.map(f => f.template_id === template_id ? transformedFile : f) + prevFichiers.map(f => f.id === id ? transformedFile : f) ); setIsModalOpen(false); }) diff --git a/Front-End/src/pages/api/docuseal/cloneTemplate.js b/Front-End/src/pages/api/docuseal/cloneTemplate.js index 3594a9c..cbc621a 100644 --- a/Front-End/src/pages/api/docuseal/cloneTemplate.js +++ b/Front-End/src/pages/api/docuseal/cloneTemplate.js @@ -2,7 +2,8 @@ import { BE_DOCUSEAL_CLONE_TEMPLATE } from '@/utils/Url'; export default function handler(req, res) { if (req.method === 'POST') { - const { templateId, email } = req.body; + const { templateId, email, is_required } = req.body; + console.log('coucou : ', req.body) fetch(BE_DOCUSEAL_CLONE_TEMPLATE, { method: 'POST', @@ -12,7 +13,8 @@ export default function handler(req, res) { }, body: JSON.stringify({ templateId, - email + email, + is_required }) }) .then(response => {