diff --git a/admission_utils/copy_documents.py b/admission_utils/copy_documents.py new file mode 100644 index 000000000..7bd130893 --- /dev/null +++ b/admission_utils/copy_documents.py @@ -0,0 +1,94 @@ +# ############################################################################## +# +# OSIS stands for Open Student Information System. It's an application +# designed to manage the core business of higher education institutions, +# such as universities, faculties, institutes and professional schools. +# The core business involves the administration of students, teachers, +# courses, programs and so on. +# +# Copyright (C) 2015-2025 Université catholique de Louvain (http://www.uclouvain.be) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# A copy of this license - GNU General Public License - is available +# at the root of the source code of this program. If not, +# see http://www.gnu.org/licenses/. +# +# ############################################################################## +import uuid + + +def copy_documents(objs): + """ + Create copies of the files of the specified objects and affect them to the specified objects. + :param objs: The list of objects. + """ + from osis_document.api.utils import get_several_remote_metadata, get_remote_tokens, documents_remote_duplicate + from osis_document.contrib import FileField + from osis_document.utils import generate_filename + + all_document_uuids = [] + all_document_upload_paths = {} + document_fields_by_obj_uuid = {} + + # Get all the document fields and the uuids of the documents to duplicate + for obj in objs: + document_fields_by_obj_uuid[obj.uuid] = {} + + for field in obj._meta.get_fields(): + if isinstance(field, FileField): + document_uuids = getattr(obj, field.name) + + if document_uuids: + document_fields_by_obj_uuid[obj.uuid][field.name] = field + all_document_uuids += [document_uuid for document_uuid in document_uuids if document_uuid] + + all_tokens = get_remote_tokens(all_document_uuids) + metadata_by_token = get_several_remote_metadata(tokens=list(all_tokens.values())) + + # Get the upload paths of the documents to duplicate + for obj in objs: + for field_name, field in document_fields_by_obj_uuid[obj.uuid].items(): + document_uuids = getattr(obj, field_name) + + for document_uuid in document_uuids: + if not document_uuid: + continue + + document_uuid_str = str(document_uuid) + file_name = 'file' + + if document_uuid_str in all_tokens and all_tokens[document_uuid_str] in metadata_by_token: + metadata = metadata_by_token[all_tokens[document_uuid_str]] + if metadata.get('name'): + file_name = metadata['name'] + + all_document_upload_paths[document_uuid_str] = generate_filename(obj, file_name, field.upload_to) + + # Make a copy of the documents and return the uuids of the copied documents + duplicates_documents_uuids = documents_remote_duplicate( + uuids=all_document_uuids, + with_modified_upload=True, + upload_path_by_uuid=all_document_upload_paths, + ) + + # Update the uuids of the documents with the uuids of the copied documents + for obj in objs: + for field_name in document_fields_by_obj_uuid[obj.uuid]: + setattr( + obj, + field_name, + [ + uuid.UUID(duplicates_documents_uuids[str(document_uuid)]) + for document_uuid in getattr(obj, field_name) + if duplicates_documents_uuids.get(str(document_uuid)) + ], + ) diff --git a/ddd/admission/doctorat/preparation/builder/proposition_builder.py b/ddd/admission/doctorat/preparation/builder/proposition_builder.py index bdadbf3f9..7535b559b 100644 --- a/ddd/admission/doctorat/preparation/builder/proposition_builder.py +++ b/ddd/admission/doctorat/preparation/builder/proposition_builder.py @@ -23,13 +23,11 @@ # see http://www.gnu.org/licenses/. # ############################################################################## -from abc import abstractmethod from typing import Optional, Union from admission.ddd.admission.doctorat.preparation.builder.proposition_identity_builder import PropositionIdentityBuilder from admission.ddd.admission.doctorat.preparation.commands import InitierPropositionCommand from admission.ddd.admission.doctorat.preparation.domain.model._detail_projet import projet_non_rempli -from admission.ddd.admission.doctorat.preparation.domain.model.doctorat_formation import DoctoratFormation from admission.ddd.admission.doctorat.preparation.domain.model.enums import ( ChoixCommissionProximiteCDEouCLSM, ChoixCommissionProximiteCDSS, @@ -49,7 +47,7 @@ from osis_common.ddd import interface -class IPropositionBuilder(interface.RootEntityBuilder): +class PropositionBuilder(interface.RootEntityBuilder): @classmethod def build_from_repository_dto(cls, dto_object: 'interface.DTO') -> 'Proposition': raise NotImplementedError @@ -64,7 +62,7 @@ def initier_proposition( cmd: 'InitierPropositionCommand', doctorat_translator: 'IDoctoratTranslator', proposition_repository: 'IPropositionRepository', - ) -> 'PropositionIdentity ': + ) -> 'Proposition': if cmd.pre_admission_associee: return cls.initier_nouvelle_proposition_attachee_a_pre_admission( cmd, @@ -84,7 +82,7 @@ def initier_nouvelle_proposition_non_attachee_a_pre_admission( cmd: 'InitierPropositionCommand', doctorat_translator: 'IDoctoratTranslator', proposition_repository: 'IPropositionRepository', - ) -> 'PropositionIdentity': + ) -> 'Proposition': doctorat = doctorat_translator.get(cmd.sigle_formation, cmd.annee_formation) InitierPropositionValidatorList( type_admission=cmd.type_admission, @@ -102,7 +100,8 @@ def initier_nouvelle_proposition_non_attachee_a_pre_admission( elif cmd.commission_proximite and cmd.commission_proximite in ChoixSousDomaineSciences.get_names(): commission_proximite = ChoixSousDomaineSciences[cmd.commission_proximite] reference = proposition_repository.recuperer_reference_suivante() - proposition = Proposition( + + return Proposition( entity_id=PropositionIdentityBuilder.build(), reference=reference, statut=ChoixStatutPropositionDoctorale.EN_BROUILLON, @@ -114,16 +113,73 @@ def initier_nouvelle_proposition_non_attachee_a_pre_admission( projet=projet_non_rempli, auteur_derniere_modification=cmd.matricule_candidat, ) - proposition_repository.save(proposition) - - return proposition.entity_id @classmethod - @abstractmethod def initier_nouvelle_proposition_attachee_a_pre_admission( cls, cmd: 'InitierPropositionCommand', doctorat_translator: 'IDoctoratTranslator', proposition_repository: 'IPropositionRepository', - ) -> 'PropositionIdentity': - raise NotImplementedError + ) -> 'Proposition': + pre_admission = proposition_repository.get(entity_id=PropositionIdentity(uuid=cmd.pre_admission_associee)) + + doctorat = doctorat_translator.get( + sigle=pre_admission.formation_id.sigle, annee=pre_admission.formation_id.annee + ) + + reference = proposition_repository.recuperer_reference_suivante() + + proposition = Proposition( + entity_id=PropositionIdentityBuilder.build(), + reference=reference, + statut=ChoixStatutPropositionDoctorale.EN_BROUILLON, + type_admission=ChoixTypeAdmission[cmd.type_admission], + formation_id=pre_admission.formation_id, + matricule_candidat=pre_admission.matricule_candidat, + projet=projet_non_rempli, + auteur_derniere_modification=cmd.matricule_candidat, + pre_admission_associee=pre_admission.entity_id, + curriculum=pre_admission.curriculum, + ) + + proposition.completer( + doctorat=doctorat, + justification=pre_admission.justification, + commission_proximite=pre_admission.commission_proximite.name if pre_admission.commission_proximite else '', + type_financement=pre_admission.financement.type.name if pre_admission.financement.type else '', + type_contrat_travail=pre_admission.financement.type_contrat_travail + if pre_admission.financement.type_contrat_travail + else '', + eft=pre_admission.financement.eft, + bourse_recherche=pre_admission.financement.bourse_recherche, + autre_bourse_recherche=pre_admission.financement.autre_bourse_recherche, + bourse_date_debut=pre_admission.financement.bourse_date_debut, + bourse_date_fin=pre_admission.financement.bourse_date_fin, + bourse_preuve=pre_admission.financement.bourse_preuve, + duree_prevue=pre_admission.financement.duree_prevue, + temps_consacre=pre_admission.financement.temps_consacre, + est_lie_fnrs_fria_fresh_csc=pre_admission.financement.est_lie_fnrs_fria_fresh_csc, + commentaire_financement=pre_admission.financement.commentaire, + langue_redaction_these=pre_admission.projet.langue_redaction_these, + institut_these=str(pre_admission.projet.institut_these.uuid) if pre_admission.projet.institut_these else '', + lieu_these=pre_admission.projet.lieu_these, + titre=pre_admission.projet.titre, + resume=pre_admission.projet.resume, + doctorat_deja_realise=pre_admission.experience_precedente_recherche.doctorat_deja_realise.name + if pre_admission.experience_precedente_recherche.doctorat_deja_realise + else '', + institution=pre_admission.experience_precedente_recherche.institution, + domaine_these=pre_admission.experience_precedente_recherche.domaine_these, + date_soutenance=pre_admission.experience_precedente_recherche.date_soutenance, + raison_non_soutenue=pre_admission.experience_precedente_recherche.raison_non_soutenue, + projet_doctoral_deja_commence=pre_admission.projet.deja_commence, + projet_doctoral_institution=pre_admission.projet.deja_commence_institution, + projet_doctoral_date_debut=pre_admission.projet.date_debut, + documents=pre_admission.projet.documents, + graphe_gantt=pre_admission.projet.graphe_gantt, + proposition_programme_doctoral=pre_admission.projet.proposition_programme_doctoral, + projet_formation_complementaire=pre_admission.projet.projet_formation_complementaire, + lettres_recommandation=pre_admission.projet.lettres_recommandation, + ) + + return proposition diff --git a/ddd/admission/doctorat/preparation/domain/model/proposition.py b/ddd/admission/doctorat/preparation/domain/model/proposition.py index bc483a98c..5284f52dc 100644 --- a/ddd/admission/doctorat/preparation/domain/model/proposition.py +++ b/ddd/admission/doctorat/preparation/domain/model/proposition.py @@ -173,6 +173,8 @@ class Proposition(interface.RootEntity): profil_soumis_candidat: ProfilCandidat = None + pre_admission_associee: Optional[PropositionIdentity] = None + fiche_archive_signatures_envoyees: List[str] = attr.Factory(list) comptabilite: 'Comptabilite' = comptabilite_non_remplie reponses_questions_specifiques: Dict = attr.Factory(dict) diff --git a/ddd/admission/doctorat/preparation/repository/i_groupe_de_supervision.py b/ddd/admission/doctorat/preparation/repository/i_groupe_de_supervision.py index 1207b5652..0b9f3bd82 100644 --- a/ddd/admission/doctorat/preparation/repository/i_groupe_de_supervision.py +++ b/ddd/admission/doctorat/preparation/repository/i_groupe_de_supervision.py @@ -24,9 +24,9 @@ # # ############################################################################## import abc +from abc import abstractmethod from typing import List, Optional, Union -from admission.models.enums.actor_type import ActorType from admission.ddd.admission.doctorat.preparation.domain.model._cotutelle import Cotutelle, pas_de_cotutelle from admission.ddd.admission.doctorat.preparation.domain.model.doctorat import DoctoratIdentity from admission.ddd.admission.doctorat.preparation.domain.model.groupe_de_supervision import ( @@ -34,8 +34,9 @@ GroupeDeSupervisionIdentity, SignataireIdentity, ) -from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity +from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity, Proposition from admission.ddd.admission.doctorat.preparation.dtos import CotutelleDTO, MembreCADTO, PromoteurDTO +from admission.models.enums.actor_type import ActorType from osis_common.ddd import interface from osis_common.ddd.interface import ApplicationService @@ -151,3 +152,12 @@ def get_cotutelle_dto_from_model(cls, cotutelle: Optional[Cotutelle]) -> 'Cotute convention=cotutelle and cotutelle.convention or [], autres_documents=cotutelle and cotutelle.autres_documents or [], ) + + @classmethod + @abstractmethod + def initialize_supervision_group_from_proposition( + cls, + uuid_proposition_originale: str, + nouvelle_proposition: 'Proposition', + ): + raise NotImplementedError diff --git a/ddd/admission/doctorat/preparation/repository/i_proposition.py b/ddd/admission/doctorat/preparation/repository/i_proposition.py index 5469ae59c..dff9fab8b 100644 --- a/ddd/admission/doctorat/preparation/repository/i_proposition.py +++ b/ddd/admission/doctorat/preparation/repository/i_proposition.py @@ -86,7 +86,7 @@ def delete(cls, entity_id: 'PropositionIdentity', **kwargs: ApplicationService) @classmethod @abc.abstractmethod - def save(cls, entity: 'Proposition') -> None: # type: ignore[override] + def save(cls, entity: 'Proposition', dupliquer_documents=False) -> None: # type: ignore[override] raise NotImplementedError @classmethod diff --git a/ddd/admission/doctorat/preparation/use_case/write/initier_proposition_service.py b/ddd/admission/doctorat/preparation/use_case/write/initier_proposition_service.py index 8b2806795..9c0854e63 100644 --- a/ddd/admission/doctorat/preparation/use_case/write/initier_proposition_service.py +++ b/ddd/admission/doctorat/preparation/use_case/write/initier_proposition_service.py @@ -23,11 +23,14 @@ # see http://www.gnu.org/licenses/. # ############################################################################## -from admission.ddd.admission.doctorat.preparation.builder.proposition_builder import IPropositionBuilder +from admission.ddd.admission.doctorat.preparation.builder.proposition_builder import PropositionBuilder from admission.ddd.admission.doctorat.preparation.commands import InitierPropositionCommand from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity from admission.ddd.admission.doctorat.preparation.domain.service.i_doctorat import IDoctoratTranslator from admission.ddd.admission.doctorat.preparation.domain.service.i_historique import IHistorique +from admission.ddd.admission.doctorat.preparation.repository.i_groupe_de_supervision import ( + IGroupeDeSupervisionRepository, +) from admission.ddd.admission.doctorat.preparation.repository.i_proposition import IPropositionRepository from admission.ddd.admission.domain.service.i_maximum_propositions import IMaximumPropositionsAutorisees @@ -38,19 +41,29 @@ def initier_proposition( doctorat_translator: 'IDoctoratTranslator', historique: 'IHistorique', maximum_propositions_service: 'IMaximumPropositionsAutorisees', - proposition_builder: 'IPropositionBuilder', + groupe_supervision_repository: 'IGroupeDeSupervisionRepository', ) -> 'PropositionIdentity': # GIVEN maximum_propositions_service.verifier_nombre_propositions_en_cours(cmd.matricule_candidat) # WHEN - proposition_identity = proposition_builder.initier_proposition( + proposition = PropositionBuilder().initier_proposition( cmd, doctorat_translator, proposition_repository, ) # THEN - historique.historiser_initiation(proposition_identity, cmd.matricule_candidat) + proposition_repository.save( + proposition, + dupliquer_documents=bool(cmd.pre_admission_associee), + ) + + groupe_supervision_repository.initialize_supervision_group_from_proposition( + uuid_proposition_originale=cmd.pre_admission_associee, + nouvelle_proposition=proposition, + ) + + historique.historiser_initiation(proposition.entity_id, cmd.matricule_candidat) - return proposition_identity + return proposition.entity_id diff --git a/infrastructure/admission/doctorat/preparation/builder/__init__.py b/infrastructure/admission/doctorat/preparation/builder/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/infrastructure/admission/doctorat/preparation/builder/in_memory/__init__.py b/infrastructure/admission/doctorat/preparation/builder/in_memory/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/infrastructure/admission/doctorat/preparation/builder/in_memory/proposition_builder.py b/infrastructure/admission/doctorat/preparation/builder/in_memory/proposition_builder.py deleted file mode 100644 index 9328b9def..000000000 --- a/infrastructure/admission/doctorat/preparation/builder/in_memory/proposition_builder.py +++ /dev/null @@ -1,46 +0,0 @@ -# ############################################################################## -# -# OSIS stands for Open Student Information System. It's an application -# designed to manage the core business of higher education institutions, -# such as universities, faculties, institutes and professional schools. -# The core business involves the administration of students, teachers, -# courses, programs and so on. -# -# Copyright (C) 2015-2024 Université catholique de Louvain (http://www.uclouvain.be) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# A copy of this license - GNU General Public License - is available -# at the root of the source code of this program. If not, -# see http://www.gnu.org/licenses/. -# -# ############################################################################## - -from admission.ddd.admission.doctorat.preparation.builder.proposition_builder import IPropositionBuilder -from admission.ddd.admission.doctorat.preparation.commands import InitierPropositionCommand -from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity -from admission.ddd.admission.doctorat.preparation.domain.service.i_doctorat import IDoctoratTranslator -from admission.ddd.admission.doctorat.preparation.repository.i_proposition import IPropositionRepository - - -class PropositionBuilderInMemory(IPropositionBuilder): - @classmethod - def initier_nouvelle_proposition_attachee_a_pre_admission( - cls, - cmd: 'InitierPropositionCommand', - doctorat_translator: 'IDoctoratTranslator', - proposition_repository: 'IPropositionRepository', - ) -> PropositionIdentity: - return cls.initier_nouvelle_proposition_non_attachee_a_pre_admission( - cmd=cmd, - doctorat_translator=doctorat_translator, - proposition_repository=proposition_repository, - ) diff --git a/infrastructure/admission/doctorat/preparation/builder/proposition_builder.py b/infrastructure/admission/doctorat/preparation/builder/proposition_builder.py deleted file mode 100644 index 42d8f4c05..000000000 --- a/infrastructure/admission/doctorat/preparation/builder/proposition_builder.py +++ /dev/null @@ -1,146 +0,0 @@ -# ############################################################################## -# -# OSIS stands for Open Student Information System. It's an application -# designed to manage the core business of higher education institutions, -# such as universities, faculties, institutes and professional schools. -# The core business involves the administration of students, teachers, -# courses, programs and so on. -# -# Copyright (C) 2015-2024 Université catholique de Louvain (http://www.uclouvain.be) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# A copy of this license - GNU General Public License - is available -# at the root of the source code of this program. If not, -# see http://www.gnu.org/licenses/. -# -# ############################################################################## - -from django.db import transaction -from osis_signature.enums import SignatureState -from osis_signature.models import Process, StateHistory - -from admission.ddd.admission.doctorat.preparation.builder.proposition_builder import IPropositionBuilder -from admission.ddd.admission.doctorat.preparation.builder.proposition_identity_builder import PropositionIdentityBuilder -from admission.ddd.admission.doctorat.preparation.commands import InitierPropositionCommand -from admission.ddd.admission.doctorat.preparation.domain.model.enums import ( - ChoixStatutPropositionDoctorale, -) -from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity -from admission.ddd.admission.doctorat.preparation.domain.service.i_doctorat import IDoctoratTranslator -from admission.ddd.admission.doctorat.preparation.repository.i_proposition import IPropositionRepository -from admission.models import DoctorateAdmission, SupervisionActor, Accounting -from admission.utils import copy_documents - - -class PropositionBuilder(IPropositionBuilder): - @classmethod - @transaction.atomic - def initier_nouvelle_proposition_attachee_a_pre_admission( - cls, - cmd: 'InitierPropositionCommand', - doctorat_translator: 'IDoctoratTranslator', - proposition_repository: 'IPropositionRepository', - ) -> PropositionIdentity: - new_reference = proposition_repository.recuperer_reference_suivante() - - pre_admission = DoctorateAdmission.objects.get( - uuid=cmd.pre_admission_associee, - candidate__global_id=cmd.matricule_candidat, - ) - - new_admission = DoctorateAdmission( - uuid=PropositionIdentityBuilder.build().uuid, - related_pre_admission=pre_admission, - reference=new_reference, - type=cmd.type_admission, - status=ChoixStatutPropositionDoctorale.EN_BROUILLON.name, - training_id=pre_admission.training_id, - comment=cmd.justification, - candidate_id=pre_admission.candidate_id, - proximity_commission=pre_admission.proximity_commission, - financing_type=pre_admission.financing_type, - financing_work_contract=pre_admission.financing_work_contract, - financing_eft=pre_admission.financing_eft, - international_scholarship_id=pre_admission.international_scholarship_id, - other_international_scholarship=pre_admission.other_international_scholarship, - scholarship_start_date=pre_admission.scholarship_start_date, - scholarship_end_date=pre_admission.scholarship_end_date, - scholarship_proof=pre_admission.scholarship_proof, - planned_duration=pre_admission.planned_duration, - dedicated_time=pre_admission.dedicated_time, - is_fnrs_fria_fresh_csc_linked=pre_admission.is_fnrs_fria_fresh_csc_linked, - financing_comment=pre_admission.financing_comment, - project_title=pre_admission.project_title, - project_abstract=pre_admission.project_abstract, - thesis_language_id=pre_admission.thesis_language_id, - thesis_institute_id=pre_admission.thesis_institute_id, - thesis_location=pre_admission.thesis_location, - phd_alread_started=pre_admission.phd_alread_started, - phd_alread_started_institute=pre_admission.phd_alread_started_institute, - work_start_date=pre_admission.work_start_date, - project_document=pre_admission.project_document, - gantt_graph=pre_admission.gantt_graph, - program_proposition=pre_admission.program_proposition, - additional_training_project=pre_admission.additional_training_project, - recommendation_letters=pre_admission.recommendation_letters, - phd_already_done=pre_admission.phd_already_done, - phd_already_done_institution=pre_admission.phd_already_done_institution, - phd_already_done_thesis_domain=pre_admission.phd_already_done_thesis_domain, - phd_already_done_defense_date=pre_admission.phd_already_done_defense_date, - phd_already_done_no_defense_reason=pre_admission.phd_already_done_no_defense_reason, - curriculum=pre_admission.curriculum, - ) - - # Duplicate the documents - copy_documents([new_admission]) - - # Duplicate the supervision group of the pre-admission - new_supervision_group = cls._duplicate_supervision_group(pre_admission) - new_admission.supervision_group = new_supervision_group - - new_admission.save() - - Accounting.objects.create(admission=new_admission) - - return PropositionIdentityBuilder.build_from_uuid(uuid=str(new_admission.uuid)) - - @classmethod - def _duplicate_supervision_group(cls, admission: DoctorateAdmission) -> Process: - process = Process.objects.create() - states = [] - for admission_actor in SupervisionActor.objects.filter(process_id=admission.supervision_group_id): - person_kwargs = ( - {'person_id': admission_actor.person_id} - if admission_actor.person_id - else { - 'first_name': admission_actor.first_name, - 'last_name': admission_actor.last_name, - 'email': admission_actor.email, - 'institute': admission_actor.institute, - 'city': admission_actor.city, - 'country_id': admission_actor.country_id, - 'language': admission_actor.language, - } - ) - - actor = SupervisionActor.objects.create( - process=process, - type=admission_actor.type, - is_doctor=admission_actor.is_doctor, - is_reference_promoter=admission_actor.is_reference_promoter, - **person_kwargs, - ) - states.append(StateHistory(actor=actor, state=SignatureState.NOT_INVITED.name)) - - StateHistory.objects.bulk_create(states) - - return process diff --git a/infrastructure/admission/doctorat/preparation/domain/service/membre_CA.py b/infrastructure/admission/doctorat/preparation/domain/service/membre_CA.py index 131bebdbc..ff78d505c 100644 --- a/infrastructure/admission/doctorat/preparation/domain/service/membre_CA.py +++ b/infrastructure/admission/doctorat/preparation/domain/service/membre_CA.py @@ -45,7 +45,7 @@ def get(cls, membre_ca_id: 'MembreCAIdentity') -> 'MembreCAIdentity': @classmethod def get_dto(cls, membre_ca_id: 'MembreCAIdentity') -> MembreCADTO: - actor = SupervisionActor.objects.select_related('person').get( + actor = SupervisionActor.objects.select_related('person__tutor', 'country').get( type=ActorType.CA_MEMBER.name, uuid=membre_ca_id.uuid, ) diff --git a/infrastructure/admission/doctorat/preparation/handlers.py b/infrastructure/admission/doctorat/preparation/handlers.py index d45ba9ecb..3f761344b 100644 --- a/infrastructure/admission/doctorat/preparation/handlers.py +++ b/infrastructure/admission/doctorat/preparation/handlers.py @@ -52,7 +52,6 @@ from infrastructure.shared_kernel.campus.repository.uclouvain_campus import UclouvainCampusRepository from infrastructure.shared_kernel.personne_connue_ucl.personne_connue_ucl import PersonneConnueUclTranslator from infrastructure.shared_kernel.profil.domain.service.parcours_interne import ExperienceParcoursInterneTranslator -from .builder.proposition_builder import PropositionBuilder from .domain.service.comptabilite import ComptabiliteTranslator from .domain.service.doctorat import DoctoratTranslator from .domain.service.historique import Historique @@ -84,7 +83,7 @@ doctorat_translator=DoctoratTranslator(), historique=Historique(), maximum_propositions_service=MaximumPropositionsAutorisees(), - proposition_builder=PropositionBuilder(), + groupe_supervision_repository=GroupeDeSupervisionRepository(), ), CompleterPropositionCommand: lambda msg_bus, cmd: completer_proposition( cmd, diff --git a/infrastructure/admission/doctorat/preparation/handlers_in_memory.py b/infrastructure/admission/doctorat/preparation/handlers_in_memory.py index f70d9c143..b6c46a31f 100644 --- a/infrastructure/admission/doctorat/preparation/handlers_in_memory.py +++ b/infrastructure/admission/doctorat/preparation/handlers_in_memory.py @@ -57,7 +57,6 @@ from infrastructure.shared_kernel.profil.domain.service.in_memory.parcours_interne import ( ExperienceParcoursInterneInMemoryTranslator, ) -from .builder.in_memory.proposition_builder import PropositionBuilderInMemory from .domain.service.in_memory.comptabilite import ComptabiliteInMemoryTranslator from .domain.service.in_memory.doctorat import DoctoratInMemoryTranslator from .domain.service.in_memory.historique import HistoriqueInMemory @@ -115,7 +114,6 @@ _experience_parcours_interne_translator = ExperienceParcoursInterneInMemoryTranslator() _digit_repository = DigitInMemoryRepository() _financabilite_fetcher = FinancabiliteInMemoryFetcher() -_proposition_builder = PropositionBuilderInMemory() COMMAND_HANDLERS = { @@ -125,7 +123,7 @@ doctorat_translator=_doctorat_translator, historique=_historique, maximum_propositions_service=_maximum_propositions_autorisees, - proposition_builder=_proposition_builder, + groupe_supervision_repository=_groupe_supervision_repository, ), CompleterPropositionCommand: lambda msg_bus, cmd: completer_proposition( cmd, diff --git a/infrastructure/admission/doctorat/preparation/repository/groupe_de_supervision.py b/infrastructure/admission/doctorat/preparation/repository/groupe_de_supervision.py index c6b652b2f..c133e994a 100644 --- a/infrastructure/admission/doctorat/preparation/repository/groupe_de_supervision.py +++ b/infrastructure/admission/doctorat/preparation/repository/groupe_de_supervision.py @@ -30,13 +30,12 @@ from django.db.models.functions import Coalesce from django.utils.translation import get_language, gettext_lazy as _ from osis_signature.enums import SignatureState +from osis_signature.models import Actor, Process, StateHistory from admission.auth.roles.ca_member import CommitteeMember from admission.auth.roles.promoter import Promoter -from admission.models import DoctorateAdmission, SupervisionActor -from admission.models.enums.actor_type import ActorType from admission.ddd.admission.doctorat.preparation.builder.proposition_identity_builder import PropositionIdentityBuilder -from admission.ddd.admission.doctorat.preparation.domain.model._cotutelle import Cotutelle, pas_de_cotutelle +from admission.ddd.admission.doctorat.preparation.domain.model._cotutelle import Cotutelle from admission.ddd.admission.doctorat.preparation.domain.model._membre_CA import MembreCAIdentity from admission.ddd.admission.doctorat.preparation.domain.model._promoteur import PromoteurIdentity from admission.ddd.admission.doctorat.preparation.domain.model._signature_membre_CA import SignatureMembreCA @@ -54,14 +53,16 @@ GroupeDeSupervisionIdentity, SignataireIdentity, ) +from admission.ddd.admission.doctorat.preparation.domain.model.proposition import Proposition from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity from admission.ddd.admission.doctorat.preparation.dtos import CotutelleDTO, MembreCADTO, PromoteurDTO from admission.ddd.admission.doctorat.preparation.repository.i_groupe_de_supervision import ( IGroupeDeSupervisionRepository, ) +from admission.models import DoctorateAdmission, SupervisionActor +from admission.models.enums.actor_type import ActorType from base.models.person import Person from osis_role.contrib.permissions import _get_roles_assigned_to_user -from osis_signature.models import Actor, Process, StateHistory from reference.models.country import Country @@ -325,7 +326,7 @@ def remove_member(cls, groupe_id: 'GroupeDeSupervisionIdentity', signataire: 'Si @classmethod def get_members(cls, groupe_id: 'GroupeDeSupervisionIdentity') -> List[Union['PromoteurDTO', 'MembreCADTO']]: - actors = SupervisionActor.objects.select_related('person__tutor').filter( + actors = SupervisionActor.objects.select_related('person__tutor', 'country').filter( process__uuid=groupe_id.uuid, ) members = [] @@ -373,3 +374,45 @@ def edit_external_member( country=Country.objects.get(iso_code=country_code) if country_code else None, language=language, ) + + @classmethod + def initialize_supervision_group_from_proposition( + cls, + uuid_proposition_originale: str, + nouvelle_proposition: 'Proposition', + ): + proposition = DoctorateAdmission.objects.select_related('supervision_group').get( + uuid=nouvelle_proposition.entity_id.uuid + ) + + # Create a supervision group if necessary + if not proposition.supervision_group_id: + proposition.supervision_group = Process.objects.create() + proposition.save(update_fields=['supervision_group']) + + if not uuid_proposition_originale: + return + + # Copy members of the supervision group of the original proposition + for admission_actor in SupervisionActor.objects.filter( + process__doctorateadmission__uuid=uuid_proposition_originale, + ): + SupervisionActor.objects.create( + process=proposition.supervision_group, + type=admission_actor.type, + is_doctor=admission_actor.is_doctor, + is_reference_promoter=admission_actor.is_reference_promoter, + **( + {'person_id': admission_actor.person_id} + if admission_actor.person_id + else { + 'first_name': admission_actor.first_name, + 'last_name': admission_actor.last_name, + 'email': admission_actor.email, + 'institute': admission_actor.institute, + 'city': admission_actor.city, + 'country_id': admission_actor.country_id, + 'language': admission_actor.language, + } + ), + ) diff --git a/infrastructure/admission/doctorat/preparation/repository/in_memory/groupe_de_supervision.py b/infrastructure/admission/doctorat/preparation/repository/in_memory/groupe_de_supervision.py index 616975ed6..e60a7f0d9 100644 --- a/infrastructure/admission/doctorat/preparation/repository/in_memory/groupe_de_supervision.py +++ b/infrastructure/admission/doctorat/preparation/repository/in_memory/groupe_de_supervision.py @@ -27,22 +27,23 @@ import uuid from typing import List, Optional, Union -from admission.models.enums.actor_type import ActorType from admission.ddd.admission.doctorat.preparation.builder.proposition_identity_builder import PropositionIdentityBuilder from admission.ddd.admission.doctorat.preparation.domain.model._cotutelle import pas_de_cotutelle from admission.ddd.admission.doctorat.preparation.domain.model._membre_CA import MembreCAIdentity from admission.ddd.admission.doctorat.preparation.domain.model._promoteur import PromoteurIdentity from admission.ddd.admission.doctorat.preparation.domain.model._signature_membre_CA import SignatureMembreCA from admission.ddd.admission.doctorat.preparation.domain.model._signature_promoteur import SignaturePromoteur -from admission.ddd.admission.doctorat.preparation.domain.model.enums import ChoixStatutPropositionDoctorale, \ - ChoixEtatSignature from admission.ddd.admission.doctorat.preparation.domain.model.doctorat import DoctoratIdentity +from admission.ddd.admission.doctorat.preparation.domain.model.enums import ( + ChoixStatutPropositionDoctorale, + ChoixEtatSignature, +) from admission.ddd.admission.doctorat.preparation.domain.model.groupe_de_supervision import ( GroupeDeSupervision, GroupeDeSupervisionIdentity, SignataireIdentity, ) -from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity +from admission.ddd.admission.doctorat.preparation.domain.model.proposition import PropositionIdentity, Proposition from admission.ddd.admission.doctorat.preparation.domain.validator.exceptions import ( GroupeDeSupervisionNonTrouveException, ) @@ -78,6 +79,7 @@ Promoteur, PromoteurInMemoryTranslator, ) +from admission.models.enums.actor_type import ActorType from base.ddd.utils.in_memory_repository import InMemoryGenericRepository @@ -245,3 +247,47 @@ def edit_external_member( membre.pays = country_code membre.langue = language return + + @classmethod + def initialize_supervision_group_from_proposition( + cls, + uuid_proposition_originale: str, + nouvelle_proposition: 'Proposition', + ): + try: + nouveau_groupe_supervision = cls.get_by_proposition_id(nouvelle_proposition.entity_id) + except GroupeDeSupervisionNonTrouveException: + nouveau_groupe_supervision = GroupeDeSupervision( + entity_id=GroupeDeSupervisionIdentity(uuid=str(uuid.uuid4())), + proposition_id=nouvelle_proposition.entity_id, + ) + + cls.save(nouveau_groupe_supervision) + + if not uuid_proposition_originale: + return + + try: + groupe_supervision_original = cls.get_by_proposition_id( + proposition_id=PropositionIdentity(uuid=str(uuid_proposition_originale)), + ) + + membres = cls.get_members(groupe_id=groupe_supervision_original.entity_id) + + except GroupeDeSupervisionNonTrouveException: + return + + for member in membres: + cls.add_member( + groupe_id=nouveau_groupe_supervision.entity_id, + proposition_status=nouvelle_proposition.statut, + type=ActorType.PROMOTER if isinstance(member, PromoteurDTO) else ActorType.CA_MEMBER, + matricule=member.matricule, + first_name=member.prenom, + last_name=member.nom, + email=member.email, + is_doctor=member.est_docteur, + institute=member.institution, + city=member.ville, + country_code=member.code_pays, + ) diff --git a/infrastructure/admission/doctorat/preparation/repository/in_memory/proposition.py b/infrastructure/admission/doctorat/preparation/repository/in_memory/proposition.py index 895e69585..47233d87d 100644 --- a/infrastructure/admission/doctorat/preparation/repository/in_memory/proposition.py +++ b/infrastructure/admission/doctorat/preparation/repository/in_memory/proposition.py @@ -150,7 +150,7 @@ def reset(cls): ] @classmethod - def save(cls, entity: 'Proposition') -> None: + def save(cls, entity: 'Proposition', dupliquer_documents=False) -> None: super().save(entity) @classmethod diff --git a/infrastructure/admission/doctorat/preparation/repository/proposition.py b/infrastructure/admission/doctorat/preparation/repository/proposition.py index 4184759d9..77a02783a 100644 --- a/infrastructure/admission/doctorat/preparation/repository/proposition.py +++ b/infrastructure/admission/doctorat/preparation/repository/proposition.py @@ -190,6 +190,9 @@ def _instantiate_admission(admission: 'DoctorateAdmission') -> 'Proposition': ), creee_le=admission.created_at, modifiee_le=admission.modified_at, + pre_admission_associee=PropositionIdentityBuilder.build_from_uuid( + str(admission.related_pre_admission.uuid) + ) if admission.related_pre_admission_id else None, soumise_le=admission.submitted_at, comptabilite=get_accounting_from_admission(admission=admission), reponses_questions_specifiques=admission.specific_question_answers, @@ -330,7 +333,7 @@ def delete(cls, entity_id: 'PropositionIdentity', **kwargs: ApplicationService) raise NotImplementedError @classmethod - def save(cls, entity: 'Proposition') -> None: + def save(cls, entity: 'Proposition', dupliquer_documents=False) -> None: doctorate = EducationGroupYear.objects.get( acronym=entity.sigle_formation, academic_year__year=entity.annee, @@ -346,6 +349,7 @@ def save(cls, entity: 'Proposition') -> None: entity.auteur_derniere_modification, entity.financabilite_derogation_premiere_notification_par, entity.financabilite_derogation_derniere_notification_par, + entity.financabilite_etabli_par, ] if matricule ] @@ -370,11 +374,17 @@ def save(cls, entity: 'Proposition') -> None: else None ) - financabilite_etabli_par_person = None - if entity.financabilite_etabli_par: - financabilite_etabli_par_person = Person.objects.filter( - global_id=entity.financabilite_etabli_par, - ).first() + financabilite_etabli_par_person = ( + persons[entity.financabilite_etabli_par] + if entity.financabilite_etabli_par in persons + else None + ) + + related_pre_admission_id = None + if entity.pre_admission_associee: + related_pre_admission_id = DoctorateAdmission.objects.only('pk').get( + uuid=entity.pre_admission_associee.uuid, + ).pk years = [year for year in [entity.annee_calculee, entity.millesime_condition_acces] if year] academic_years = {} @@ -385,6 +395,7 @@ def save(cls, entity: 'Proposition') -> None: admission, _ = DoctorateAdmission.objects.update_or_create( uuid=entity.entity_id.uuid, defaults={ + 'duplicate_documents_when_saving': dupliquer_documents, # Indicate if the documents must be duplicated # FIXME remove when upgrading to Django 5.2? https://code.djangoproject.com/ticket/35890 'modified_at': timezone.now(), 'reference': entity.reference, @@ -393,6 +404,7 @@ def save(cls, entity: 'Proposition') -> None: 'comment': entity.justification, 'candidate': candidate, 'submitted_at': entity.soumise_le, + 'related_pre_admission_id': related_pre_admission_id, 'proximity_commission': entity.commission_proximite and entity.commission_proximite.name or '', 'doctorate': doctorate, 'determined_academic_year': academic_years.get(entity.annee_calculee), diff --git a/models/doctorate.py b/models/doctorate.py index 984b50233..a6233817e 100644 --- a/models/doctorate.py +++ b/models/doctorate.py @@ -25,6 +25,7 @@ # ############################################################################## import datetime from contextlib import suppress +from typing import Optional from django.contrib.postgres.fields import ArrayField from django.core.cache import cache @@ -36,6 +37,7 @@ from osis_signature.contrib.fields import SignatureProcessField from rest_framework.settings import api_settings +from admission.admission_utils.copy_documents import copy_documents from admission.ddd import DUREE_MINIMALE_PROGRAMME, DUREE_MAXIMALE_PROGRAMME from admission.ddd.admission.doctorat.preparation.domain.model.enums import ( ChoixCommissionProximiteCDEouCLSM, @@ -641,6 +643,19 @@ def update_financability_computed_rule(self, author: 'Person'): ) ) + def __init__(self, *args, **kwargs): + self._duplicate_documents_when_saving: Optional[bool] = None + + super().__init__(*args, **kwargs) + + @property + def duplicate_documents_when_saving(self): + return self._duplicate_documents_when_saving + + @duplicate_documents_when_saving.setter + def duplicate_documents_when_saving(self, value): + self._duplicate_documents_when_saving = value + # The following properties are here to alias the training_id field to doctorate_id @property def doctorate(self): @@ -710,6 +725,9 @@ class Meta: ] def save(self, *args, **kwargs) -> None: + if self._state.adding and self.duplicate_documents_when_saving: + copy_documents(objs=[self]) + super().save(*args, **kwargs) cache.delete('admission_permission_{}'.format(self.uuid)) @@ -784,6 +802,7 @@ def get_queryset(self): "financability_established_by", "financability_dispensation_first_notification_by", "financability_dispensation_last_notification_by", + "related_pre_admission", ) .annotate( code_secteur_formation=CTESubquery(sector_subqs.values("acronym")[:1]), @@ -811,7 +830,6 @@ def for_dto(self): self.get_queryset() .select_related( 'training__enrollment_campus__country', - 'related_pre_admission', ) .annotate_campus_info() .annotate_training_management_entity() diff --git a/tests/api/views/test_training_choice.py b/tests/api/views/test_training_choice.py index e71cf775a..5c2278668 100644 --- a/tests/api/views/test_training_choice.py +++ b/tests/api/views/test_training_choice.py @@ -119,7 +119,7 @@ def setUpTestData(cls): cls.commission = EntityVersionFactory( parent=cls.sector, entity_type=EntityType.DOCTORAL_COMMISSION.name, - acronym='CDA', + acronym='CDSS', ).entity cls.doctorate = DoctorateFactory( management_entity=cls.commission, @@ -135,7 +135,7 @@ def setUpTestData(cls): "sigle_formation": cls.doctorate.acronym, "annee_formation": cls.doctorate.academic_year.year, "matricule_candidat": cls.candidate.global_id, - "commission_proximite": '', + "commission_proximite": ChoixCommissionProximiteCDSS.ECLI.name, } cls.url = resolve_url("admission_api_v1:doctorate_training_choice") cls.list_url = resolve_url("admission_api_v1:propositions") @@ -231,7 +231,7 @@ def test_admission_doctorate_creation_using_api_candidate(self): admission.reference, seq_value + 1, ) - self.assertEqual(response.json()['doctorate_propositions'][0]["reference"], f'M-CDA22-{str(admission)}') + self.assertEqual(response.json()['doctorate_propositions'][0]["reference"], f'M-CDSS22-{str(admission)}') @freezegun.freeze_time('2023-01-01') def test_admission_doctorate_creation_based_on_pre_admission(self): @@ -267,7 +267,7 @@ def test_admission_doctorate_creation_based_on_pre_admission(self): status=ChoixStatutPropositionDoctorale.INSCRIPTION_AUTORISEE.name, comment='Comment', proximity_commission=ChoixCommissionProximiteCDSS.ECLI.name, - financing_type=ChoixTypeFinancement.SELF_FUNDING.name, + financing_type=ChoixTypeFinancement.WORK_CONTRACT.name, financing_work_contract=ChoixTypeContratTravail.UCLOUVAIN_SCIENTIFIC_STAFF.name, financing_eft=10, international_scholarship_id=self.scholarship.pk, @@ -324,32 +324,32 @@ def test_admission_doctorate_creation_based_on_pre_admission(self): self.assertEqual(admissions.count(), 1) new_admission = admissions[0] - self.assertEqual(new_admission.candidate, new_admission.candidate) - self.assertEqual(new_admission.training, new_admission.training) + self.assertEqual(new_admission.candidate, pre_admission.candidate) + self.assertEqual(new_admission.training, pre_admission.training) self.assertEqual(new_admission.type, ChoixTypeAdmission.ADMISSION.name) self.assertEqual(new_admission.status, ChoixStatutPropositionDoctorale.EN_BROUILLON.name) - self.assertEqual(new_admission.comment, 'Some new justification') - self.assertEqual(new_admission.proximity_commission, new_admission.proximity_commission) - self.assertEqual(new_admission.financing_type, new_admission.financing_type) - self.assertEqual(new_admission.financing_work_contract, new_admission.financing_work_contract) - self.assertEqual(new_admission.financing_eft, new_admission.financing_eft) - self.assertEqual(new_admission.international_scholarship_id, new_admission.international_scholarship_id) - self.assertEqual(new_admission.other_international_scholarship, new_admission.other_international_scholarship) - self.assertEqual(new_admission.scholarship_start_date, new_admission.scholarship_start_date) - self.assertEqual(new_admission.scholarship_end_date, new_admission.scholarship_end_date) + self.assertEqual(new_admission.comment, pre_admission.comment) + self.assertEqual(new_admission.proximity_commission, pre_admission.proximity_commission) + self.assertEqual(new_admission.financing_type, pre_admission.financing_type) + self.assertEqual(new_admission.financing_work_contract, pre_admission.financing_work_contract) + self.assertEqual(new_admission.financing_eft, pre_admission.financing_eft) + self.assertEqual(new_admission.international_scholarship_id, pre_admission.international_scholarship_id) + self.assertEqual(new_admission.other_international_scholarship, pre_admission.other_international_scholarship) + self.assertEqual(new_admission.scholarship_start_date, pre_admission.scholarship_start_date) + self.assertEqual(new_admission.scholarship_end_date, pre_admission.scholarship_end_date) self.assertEqual(new_admission.scholarship_proof, self.duplicated_documents_tokens['scholarship_proof']) - self.assertEqual(new_admission.planned_duration, new_admission.planned_duration) - self.assertEqual(new_admission.dedicated_time, new_admission.dedicated_time) - self.assertEqual(new_admission.is_fnrs_fria_fresh_csc_linked, new_admission.is_fnrs_fria_fresh_csc_linked) - self.assertEqual(new_admission.financing_comment, new_admission.financing_comment) - self.assertEqual(new_admission.project_title, new_admission.project_title) - self.assertEqual(new_admission.project_abstract, new_admission.project_abstract) - self.assertEqual(new_admission.thesis_language, new_admission.thesis_language) - self.assertEqual(new_admission.thesis_institute, new_admission.thesis_institute) - self.assertEqual(new_admission.thesis_location, new_admission.thesis_location) - self.assertEqual(new_admission.phd_alread_started, new_admission.phd_alread_started) - self.assertEqual(new_admission.phd_alread_started_institute, new_admission.phd_alread_started_institute) - self.assertEqual(new_admission.work_start_date, new_admission.work_start_date) + self.assertEqual(new_admission.planned_duration, pre_admission.planned_duration) + self.assertEqual(new_admission.dedicated_time, pre_admission.dedicated_time) + self.assertEqual(new_admission.is_fnrs_fria_fresh_csc_linked, pre_admission.is_fnrs_fria_fresh_csc_linked) + self.assertEqual(new_admission.financing_comment, pre_admission.financing_comment) + self.assertEqual(new_admission.project_title, pre_admission.project_title) + self.assertEqual(new_admission.project_abstract, pre_admission.project_abstract) + self.assertEqual(new_admission.thesis_language, pre_admission.thesis_language) + self.assertEqual(new_admission.thesis_institute, pre_admission.thesis_institute) + self.assertEqual(new_admission.thesis_location, pre_admission.thesis_location) + self.assertEqual(new_admission.phd_alread_started, pre_admission.phd_alread_started) + self.assertEqual(new_admission.phd_alread_started_institute, pre_admission.phd_alread_started_institute) + self.assertEqual(new_admission.work_start_date, pre_admission.work_start_date) self.assertEqual(new_admission.project_document, self.duplicated_documents_tokens['project_document']) self.assertEqual(new_admission.gantt_graph, self.duplicated_documents_tokens['gantt_graph']) self.assertEqual(new_admission.program_proposition, self.duplicated_documents_tokens['program_proposition']) @@ -361,13 +361,13 @@ def test_admission_doctorate_creation_based_on_pre_admission(self): new_admission.recommendation_letters, self.duplicated_documents_tokens['recommendation_letters'], ) - self.assertEqual(new_admission.phd_already_done, new_admission.phd_already_done) - self.assertEqual(new_admission.phd_already_done_institution, new_admission.phd_already_done_institution) - self.assertEqual(new_admission.phd_already_done_thesis_domain, new_admission.phd_already_done_thesis_domain) - self.assertEqual(new_admission.phd_already_done_defense_date, new_admission.phd_already_done_defense_date) + self.assertEqual(new_admission.phd_already_done, pre_admission.phd_already_done) + self.assertEqual(new_admission.phd_already_done_institution, pre_admission.phd_already_done_institution) + self.assertEqual(new_admission.phd_already_done_thesis_domain, pre_admission.phd_already_done_thesis_domain) + self.assertEqual(new_admission.phd_already_done_defense_date, pre_admission.phd_already_done_defense_date) self.assertEqual( new_admission.phd_already_done_no_defense_reason, - new_admission.phd_already_done_no_defense_reason, + pre_admission.phd_already_done_no_defense_reason, ) self.assertEqual(new_admission.curriculum, self.duplicated_documents_tokens['curriculum']) diff --git a/tests/utils/test_copy_documents.py b/tests/utils/test_copy_documents.py index 445f67566..e0a6b0b20 100644 --- a/tests/utils/test_copy_documents.py +++ b/tests/utils/test_copy_documents.py @@ -28,8 +28,8 @@ from django.test import TestCase +from admission.admission_utils.copy_documents import copy_documents from admission.tests.factories.curriculum import EducationalExperienceFactory, ProfessionalExperienceFactory -from admission.utils import copy_documents from base.tests.factories.person import PersonFactory diff --git a/utils.py b/utils.py index 8f626a65d..7b58c1b32 100644 --- a/utils.py +++ b/utils.py @@ -440,74 +440,6 @@ def get_access_titles_names( return access_titles_names -def copy_documents(objs): - """ - Create copies of the files of the specified objects and affect them to the specified objects. - :param objs: The list of objects. - """ - from osis_document.api.utils import get_several_remote_metadata, get_remote_tokens, documents_remote_duplicate - from osis_document.contrib import FileField - from osis_document.utils import generate_filename - - all_document_uuids = [] - all_document_upload_paths = {} - document_fields_by_obj_uuid = {} - - # Get all the document fields and the uuids of the documents to duplicate - for obj in objs: - document_fields_by_obj_uuid[obj.uuid] = {} - - for field in obj._meta.get_fields(): - if isinstance(field, FileField): - document_uuids = getattr(obj, field.name) - - if document_uuids: - document_fields_by_obj_uuid[obj.uuid][field.name] = field - all_document_uuids += [document_uuid for document_uuid in document_uuids if document_uuid] - - all_tokens = get_remote_tokens(all_document_uuids) - metadata_by_token = get_several_remote_metadata(tokens=list(all_tokens.values())) - - # Get the upload paths of the documents to duplicate - for obj in objs: - for field_name, field in document_fields_by_obj_uuid[obj.uuid].items(): - document_uuids = getattr(obj, field_name) - - for document_uuid in document_uuids: - if not document_uuid: - continue - - document_uuid_str = str(document_uuid) - file_name = 'file' - - if document_uuid_str in all_tokens and all_tokens[document_uuid_str] in metadata_by_token: - metadata = metadata_by_token[all_tokens[document_uuid_str]] - if metadata.get('name'): - file_name = metadata['name'] - - all_document_upload_paths[document_uuid_str] = generate_filename(obj, file_name, field.upload_to) - - # Make a copy of the documents and return the uuids of the copied documents - duplicates_documents_uuids = documents_remote_duplicate( - uuids=all_document_uuids, - with_modified_upload=True, - upload_path_by_uuid=all_document_upload_paths, - ) - - # Update the uuids of the documents with the uuids of the copied documents - for obj in objs: - for field_name in document_fields_by_obj_uuid[obj.uuid]: - setattr( - obj, - field_name, - [ - uuid.UUID(duplicates_documents_uuids[str(document_uuid)]) - for document_uuid in getattr(obj, field_name) - if duplicates_documents_uuids.get(str(document_uuid)) - ], - ) - - def get_experience_urls( user: User, admission: Union[DoctorateAdmission, GeneralEducationAdmission, ContinuingEducationAdmission], diff --git a/views/common/form_tabs/curriculum.py b/views/common/form_tabs/curriculum.py index 7fa5db144..2fe3e7e66 100644 --- a/views/common/form_tabs/curriculum.py +++ b/views/common/form_tabs/curriculum.py @@ -37,6 +37,7 @@ from django.utils.translation import gettext_lazy as _ from django.views.generic import FormView +from admission.admission_utils.copy_documents import copy_documents from admission.ddd.admission.formation_generale.domain.service.checklist import Checklist from admission.models import EPCInjection as AdmissionEPCInjection from admission.models.base import ( @@ -46,7 +47,6 @@ from admission.models.base import BaseAdmission from admission.models.checklist import FreeAdditionalApprovalCondition from admission.models.epc_injection import EPCInjectionType, EPCInjectionStatus as AdmissionEPCInjectionStatus -from admission.utils import copy_documents from admission.views.common.mixins import AdmissionFormMixin, LoadDossierViewMixin from osis_profile.models import ProfessionalExperience, EducationalExperience, EducationalExperienceYear from osis_profile.models.epc_injection import (