phoneNumberUtil = PhoneNumberUtil::getInstance(); } #[Required] /** @see https://symfony.com/doc/current/logging/channels_handlers.html#how-to-autowire-logger-channels */ public function setLoggerInterface(LoggerInterface $adminLogger): void { $this->logger = $adminLogger; } /** * Créé une nouvelle organisation à partir des données contenues dans une OrganizationCreationRequest. * * @throws TransportExceptionInterface * @throws \Throwable */ public function create(OrganizationCreationRequest $organizationCreationRequest): Organization { $this->logger->info( "Start the creation of a new organization named '".$organizationCreationRequest->getName()."'" ); $this->entityManager->beginTransaction(); try { // On vérifie si cette organisation n'existe pas déjà $this->interruptIfOrganizationExists($organizationCreationRequest); // On vérifie la validité et la disponibilité du sous domaine $this->validateSubdomain($organizationCreationRequest->getSubdomain()); $this->logger->info("Subdomain is valid and available : '".$organizationCreationRequest->getSubdomain()."'"); // On construit l'organisation et ses relations $organization = $this->makeOrganizationWithRelations($organizationCreationRequest); $this->logger->info('Organization created with all its relations'); // On persiste et on commit, les objets liés seront persistés en cascade $this->entityManager->persist($organization); $this->entityManager->flush(); $this->entityManager->commit(); $this->logger->debug(' - New entities committed in DB'); $this->logger->info('Organization persisted in the DB'); } catch (\Throwable $e) { $this->logger->critical("An error happened, operation cancelled\n".$e); $this->entityManager->rollback(); throw $e; } $withError = false; // Création de la société Dolibarr try { $dolibarrId = $this->dolibarrApiService->createSociety( $organization, $organizationCreationRequest->isClient() ); $this->logger->info('New dolibarr structure created (uid : '.$dolibarrId.')'); } catch (\Throwable $e) { $this->logger->critical('An error happened while creating the dolibarr society, please proceed manually.'); $this->logger->debug((string) $e); $withError = true; } // Register the subdomain into the BindFile (takes up to 5min to take effect) try { $this->bindFileService->registerSubdomain($organizationCreationRequest->getSubdomain()); $this->logger->info('Subdomain registered'); } catch (\Throwable $e) { $this->logger->critical('An error happened while updating the bind file, please proceed manually.'); $this->logger->debug((string) $e); $withError = true; } // Création du site typo3 (on est obligé d'attendre que l'organisation soit persistée en base) if ($organizationCreationRequest->getCreateWebsite()) { try { $rootUid = $this->createTypo3Website($organization); $this->logger->info('Typo3 website created (root uid: '.$rootUid.')'); } catch (\Throwable $e) { $this->logger->critical('An error happened while creating the typo3 website, please proceed manually.'); $this->logger->debug((string) $e); $withError = true; } } else { $this->logger->warning('Typo3 website creation was not required'); } // Création de l'organisation dans la base adminassos (géré par la V1) try { $this->updateAdminassosDb($organization); $this->logger->info('Adminassos db updated'); } catch (\Throwable $e) { $this->logger->critical('An error happened while updating the adminassos db, please proceed manually.'); $this->logger->debug((string) $e); $withError = true; } if ($withError) { $organizationCreationRequest->setStatus(OrganizationCreationRequest::STATUS_OK_WITH_ERRORS); $this->logger->warning('-- Operation ended with errors, check the logs for more information --'); } else { $organizationCreationRequest->setStatus(OrganizationCreationRequest::STATUS_OK); $this->logger->info('The organization has been created (id='.$organization->getId().').'); } return $organization; } /** * Lève une exception si cette organisation existe déjà. */ public function interruptIfOrganizationExists(OrganizationCreationRequest $organizationCreationRequest): void { if ( $organizationCreationRequest->getSiretNumber() && $this->organizationIdentificationRepository->findOneBy( ['siretNumber' => $organizationCreationRequest->getSiretNumber()] ) ) { throw new \RuntimeException("This siret number is already registered : '".$organizationCreationRequest->getSiretNumber()."'"); } if ( $organizationCreationRequest->getWaldecNumber() && $this->organizationIdentificationRepository->findOneBy( ['waldecNumber' => $organizationCreationRequest->getWaldecNumber()] ) ) { throw new \RuntimeException("This RNA identifier (waldec number) is already registered : '".$organizationCreationRequest->getWaldecNumber()."'"); } if ( $organizationCreationRequest->getIdentifier() && $this->organizationIdentificationRepository->findOneBy( ['identifier' => $organizationCreationRequest->getIdentifier()] ) ) { throw new \RuntimeException("This CMF identifier is already registered : '".$organizationCreationRequest->getIdentifier()."'"); } $normalizedName = $this->normalizeIdentificationField($organizationCreationRequest->getName()); if ( $this->organizationIdentificationRepository->findOneBy( ['normalizedName' => $normalizedName, 'addressCity' => $organizationCreationRequest->getCity()] ) ) { throw new \RuntimeException("An organization named '".$organizationCreationRequest->getName()."' already exists in ".$organizationCreationRequest->getCity()); } $address = $this->normalizeIdentificationField(implode(' ', [ $organizationCreationRequest->getStreetAddress1(), $organizationCreationRequest->getStreetAddress2(), $organizationCreationRequest->getStreetAddress3(), ])); if ( $this->organizationIdentificationRepository->findOneBy( [ 'normalizedAddress' => $address, 'addressCity' => $organizationCreationRequest->getCity(), 'postalCode' => $organizationCreationRequest->getPostalCode(), ] ) ) { throw new \RuntimeException('An organization already exists at this address.'); } } /** * Vérifie la disponibilité et la validité d'un sous domaine. * * @throws \Exception */ protected function validateSubdomain(string $subdomainValue): void { if (!$this->subdomainService->isValidSubdomain($subdomainValue)) { throw new \RuntimeException('Not a valid subdomain : '.$subdomainValue); } if ($this->subdomainService->isReservedSubdomain($subdomainValue)) { throw new \RuntimeException('This subdomain is not available : '.$subdomainValue); } if ($this->subdomainService->isRegistered($subdomainValue)) { throw new \RuntimeException('This subdomain is already registered : '.$subdomainValue); } } /** * Créé une nouvelle instance d'organisation, et toutes les instances liées (paramètres, contact, adresses, ...), * selon le contenu de la requête de création. * * @throws \Throwable */ protected function makeOrganizationWithRelations( OrganizationCreationRequest $organizationCreationRequest, ): Organization { // Création de l'organisation $organization = $this->makeOrganization($organizationCreationRequest); $this->logger->debug(' - Organization created'); // Création des Parameters $parameters = $this->makeParameters($organizationCreationRequest); $organization->setParameters($parameters); $this->logger->debug(' - Parameters created'); // Création des Settings $settings = $this->makeSettings($organizationCreationRequest); $organization->setSettings($settings); $this->logger->debug(' - Settings created'); // Création de l'adresse postale $organizationAddressPostal = $this->makePostalAddress($organizationCreationRequest); $organization->addOrganizationAddressPostal($organizationAddressPostal); $this->logger->debug(' - OrganizationAddressPostal created'); // Création du point de contact $contactPoint = $this->makeContactPoint($organizationCreationRequest); $organization->addContactPoint($contactPoint); $this->logger->debug(' - ContactPoint created'); // Rattachement au réseau $networkOrganization = $this->makeNetworkOrganization($organizationCreationRequest); $organization->addNetworkOrganization($networkOrganization); $this->logger->debug(' - NetworkOrganization created'); // Créé l'admin $adminAccess = $this->makeAdminAccess($organizationCreationRequest); $organization->addAccess($adminAccess); $this->logger->debug(' - Admin access created'); // Création des cycles foreach ($this->makeCycles() as $cycle) { $organization->addCycle($cycle); } $this->logger->debug(' - Cycles created'); // Création du président (si renseigné) $presidentCreationRequest = $organizationCreationRequest->getPresident(); if ($presidentCreationRequest !== null) { $presidentAccess = $this->makeAccess( $presidentCreationRequest, FunctionEnum::PRESIDENT, $organizationCreationRequest->getCreationDate(), $organizationCreationRequest->getAuthorId() ); $organization->addAccess($presidentAccess); $this->logger->debug(' - President access created'); } // Création du directeur (si renseigné) $directorCreationRequest = $organizationCreationRequest->getDirector(); if ($directorCreationRequest !== null) { $directorAccess = $this->makeAccess( $directorCreationRequest, FunctionEnum::DIRECTOR, $organizationCreationRequest->getCreationDate(), $organizationCreationRequest->getAuthorId() ); $organization->addAccess($directorAccess); $this->logger->debug(' - Director access created'); } // Création du sous-domaine $subdomain = $this->makeSubdomain($organizationCreationRequest); $organization->addSubdomain($subdomain); // <--- Pour la rétrocompatibilité avec la v1 ; pourra être supprimé lorsque la migration sera achevée $parameters = $organization->getParameters(); $parameters->setSubDomain($organizationCreationRequest->getSubdomain()); $parameters->setOtherWebsite('https://'.$organizationCreationRequest->getSubdomain().'.opentalent.fr'); // ---> $this->logger->debug(' - Subdomain created'); return $organization; } /** * Créé une nouvelle instance d'organisation. */ protected function makeOrganization(OrganizationCreationRequest $organizationCreationRequest): Organization { // Création de l'organisation $organization = new Organization(); $organization->setName($organizationCreationRequest->getName()); $organization->setLegalStatus($organizationCreationRequest->getLegalStatus()); $organization->setPrincipalType($organizationCreationRequest->getPrincipalType()); $organization->setIdentifier($organizationCreationRequest->getIdentifier()); $organization->setCreationDate($organizationCreationRequest->getCreationDate()); $organization->setCreateDate($organizationCreationRequest->getCreationDate()); $organization->setCreatedBy($organizationCreationRequest->getAuthorId()); return $organization; } /** * Create a new Parameters object from the data in an OrganizationCreationRequest. * * @param OrganizationCreationRequest $organizationCreationRequest The organization creation request * * @return Parameters The created Parameters object * * @throws \Throwable If there is an error */ protected function makeParameters(OrganizationCreationRequest $organizationCreationRequest): Parameters { $parameters = new Parameters(); return $parameters; } /** * Creates a new instance of the Settings class based on the given OrganizationCreationRequest object. * * @param OrganizationCreationRequest $organizationCreationRequest the OrganizationCreationRequest object containing the required data * * @return Settings the newly created instance of the Settings class */ protected function makeSettings(OrganizationCreationRequest $organizationCreationRequest): Settings { $settings = new Settings(); $settings->setProduct($organizationCreationRequest->getProduct()); // TODO: à revoir, pour étendre à d'autres pays (voir à remplacer le champs 'country' par un champs 'currency'?) $settings->setCountry( $organizationCreationRequest->getCountryId() === 41 ? 'SWITZERLAND' : 'FRANCE' ); $settings->setCreateDate($organizationCreationRequest->getCreationDate()); $settings->setCreatedBy($organizationCreationRequest->getAuthorId()); return $settings; } /** * Creates a new instance of the OrganizationAddressPostal class based on the given OrganizationCreationRequest object. * * @param OrganizationCreationRequest $organizationCreationRequest the OrganizationCreationRequest object containing the required data * * @return OrganizationAddressPostal the newly created instance of the OrganizationAddressPostal class */ protected function makePostalAddress(OrganizationCreationRequest $organizationCreationRequest): OrganizationAddressPostal { $country = $this->countryRepository->find($organizationCreationRequest->getCountryId()); if (!$country) { throw new \RuntimeException('No country found for id '.$organizationCreationRequest->getCountryId()); } $addressPostal = new AddressPostal(); $addressPostal->setStreetAddress($organizationCreationRequest->getStreetAddress1()); $addressPostal->setStreetAddressSecond($organizationCreationRequest->getStreetAddress2()); $addressPostal->setStreetAddressThird($organizationCreationRequest->getStreetAddress3()); $addressPostal->setPostalCode($organizationCreationRequest->getPostalCode()); $addressPostal->setAddressCity($organizationCreationRequest->getCity()); $addressPostal->setAddressCountry($country); $addressPostal->setCreateDate($organizationCreationRequest->getCreationDate()); $addressPostal->setCreatedBy($organizationCreationRequest->getAuthorId()); $organizationAddressPostal = new OrganizationAddressPostal(); $organizationAddressPostal->setAddressPostal($addressPostal); $organizationAddressPostal->setType(AddressPostalOrganizationTypeEnum::ADDRESS_HEAD_OFFICE); $organizationAddressPostal->setCreateDate($organizationCreationRequest->getCreationDate()); $organizationAddressPostal->setCreatedBy($organizationCreationRequest->getAuthorId()); return $organizationAddressPostal; } /** * Creates a new instance of the ContactPoint class based on the given OrganizationCreationRequest object. * * @param OrganizationCreationRequest $organizationCreationRequest the OrganizationCreationRequest object containing the required data * * @return ContactPoint the newly created instance of the ContactPoint class * * @throws NumberParseException */ protected function makeContactPoint(OrganizationCreationRequest $organizationCreationRequest): ContactPoint { if (!$this->phoneNumberUtil->isPossibleNumber($organizationCreationRequest->getPhoneNumber())) { throw new \RuntimeException('Phone number is invalid or missing'); } $phoneNumber = $this->phoneNumberUtil->parse($organizationCreationRequest->getPhoneNumber()); $contactPoint = new ContactPoint(); $contactPoint->setContactType(ContactPointTypeEnum::PRINCIPAL); $contactPoint->setEmail($organizationCreationRequest->getEmail()); $contactPoint->setTelphone($phoneNumber); $contactPoint->setCreateDate($organizationCreationRequest->getCreationDate()); $contactPoint->setCreatedBy($organizationCreationRequest->getAuthorId()); return $contactPoint; } /** * Creates a new instance of the NetworkOrganization class based on the given OrganizationCreationRequest object. * * @param OrganizationCreationRequest $organizationCreationRequest the OrganizationCreationRequest object containing the required data * * @return NetworkOrganization the newly created instance of the NetworkOrganization class * * @throws \RuntimeException|\Exception if no parent organization is found for the given parent ID or if no network is found for the given network ID */ protected function makeNetworkOrganization(OrganizationCreationRequest $organizationCreationRequest): NetworkOrganization { $parent = $this->organizationRepository->find($organizationCreationRequest->getParentId()); if (!$parent) { throw new \RuntimeException('No parent organization found for id '.$organizationCreationRequest->getParentId()); } if (!in_array($parent->getSettings()->getProduct(), [SettingsProductEnum::MANAGER, SettingsProductEnum::MANAGER_PREMIUM])) { throw new \RuntimeException("Parent organization must have the product 'manager' (actual product: '".$parent->getSettings()->getProduct()->value."')"); } $networkOrganization = $this->organizationUtils->getActiveNetworkOrganization($parent); if (!$networkOrganization) { throw new \RuntimeException('No network found for parent '.$organizationCreationRequest->getParentId()); } $network = $networkOrganization->getNetwork(); // Si réseau CMF, on vérifie que le matricule est valide if ($network->getId() === NetworkEnum::CMF->value) { if (!preg_match("/FR\d{3}\w\d{8}/", $organizationCreationRequest->getIdentifier())) { throw new \RuntimeException('CMF identifier is missing or invalid.'); } } $networkOrganization = new NetworkOrganization(); $networkOrganization->setParent($parent); $networkOrganization->setNetwork($network); $networkOrganization->setStartDate(DatesUtils::new()); $networkOrganization->setCreateDate($organizationCreationRequest->getCreationDate()); $networkOrganization->setCreatedBy($organizationCreationRequest->getAuthorId()); return $networkOrganization; } /** * Creates a new instance of the Access class with admin access based on the given OrganizationCreationRequest object. * * @param OrganizationCreationRequest $organizationCreationRequest the OrganizationCreationRequest object containing the required data * * @return Access the newly created instance of the Access class with admin access */ protected function makeAdminAccess(OrganizationCreationRequest $organizationCreationRequest): Access { $admin = new Person(); $admin->setUsername('admin'.strtolower($organizationCreationRequest->getSubdomain())); $randomString = ByteString::fromRandom(32)->toString(); $admin->setPassword($randomString); $admin->setEnabled(true); $adminAccess = new Access(); $adminAccess->setAdminAccess(true); $adminAccess->setPerson($admin); $adminAccess->setLoginEnabled(true); $adminAccess->setRoles(['ROLE_ADMIN', 'ROLE_ADMIN_CORE']); $adminAccess->setCreateDate($organizationCreationRequest->getCreationDate()); $adminAccess->setCreatedBy($organizationCreationRequest->getAuthorId()); $contactPoint = new ContactPoint(); $contactPoint->setContactType(ContactPointTypeEnum::PRINCIPAL); $contactPoint->setEmail($organizationCreationRequest->getEmail()); $admin->addContactPoint($contactPoint); return $adminAccess; } /** * Creates an array of Cycle objects based on a predefined set of data. * * @return Cycle[] an array of Cycle objects */ protected function makeCycles(): array { $cyclesData = [ ['Cycle initiation', 10, CycleEnum::INITIATION_CYCLE], ['Cycle 1', 20, CycleEnum::CYCLE_1], ['Cycle 2', 30, CycleEnum::CYCLE_2], ['Cycle 3', 40, CycleEnum::CYCLE_3], ['Cycle 4', 50, CycleEnum::CYCLE_4], ['Hors cycle', 60, CycleEnum::OUT_CYCLE], ]; $cycles = []; foreach ($cyclesData as $cycleData) { $cycle = new Cycle(); $cycle->setLabel($cycleData[0]); $cycle->setOrder($cycleData[1]); $cycle->setCycleEnum($cycleData[2]); $cycle->setIsSystem(false); $cycles[] = $cycle; } return $cycles; } /** * Creates an Access object based on the given OrganizationMemberCreationRequest. * * @param int|OrganizationMemberCreationRequest $creationRequestData the request object containing the * necessary data for creating a Person object, * or the id of an existing one * * @return Access the created Access object * * @throws NumberParseException */ protected function makeAccess( int|OrganizationMemberCreationRequest $creationRequestData, FunctionEnum $function, \DateTime $creationDate, ?int $authorId, ): Access { if (is_int($creationRequestData)) { $person = $this->personRepository->find($creationRequestData); } else { $person = new Person(); if ( $creationRequestData->getUsername() !== null && $this->personRepository->findOneBy(['username' => $creationRequestData->getUsername()]) ) { throw new \RuntimeException('Username already in use : '.$creationRequestData->getUsername()); } $person->setUsername($creationRequestData->getUsername()); $person->setPassword(ByteString::fromRandom(32)->toString()); $person->setGender($creationRequestData->getGender()); $person->setName( ucfirst(strtolower($creationRequestData->getName())) ); $person->setGivenName( ucfirst(strtolower($creationRequestData->getGivenName())) ); $personPostalAddress = $this->makePersonPostalAddress($creationRequestData, $creationDate, $authorId); $person->addPersonAddressPostal($personPostalAddress); $contactPoint = $this->makePersonContactPoint($creationRequestData, $creationDate, $authorId); $person->addContactPoint($contactPoint); $person->setCreateDate($creationDate); $person->setCreatedBy($authorId); } $access = new Access(); $access->setPerson($person); $functionType = $this->functionTypeRepository->findOneBy(['mission' => $function]); $organizationFunction = new OrganizationFunction(); $organizationFunction->setFunctionType($functionType); $organizationFunction->setStartDate($creationDate); $organizationFunction->setCreateDate($creationDate); $organizationFunction->setCreatedBy($authorId); $access->addOrganizationFunction($organizationFunction); $access->setCreateDate($creationDate); $access->setCreatedBy($authorId); return $access; } /** * Creates a PersonAddressPostal object based on the given OrganizationMemberCreationRequest. * * @param OrganizationMemberCreationRequest $organizationMemberCreationRequest the request object containing the * necessary data for creating a * PersonAddressPostal object * * @return PersonAddressPostal the created PersonAddressPostal object */ protected function makePersonPostalAddress( OrganizationMemberCreationRequest $organizationMemberCreationRequest, \DateTime $creationDate, ?int $authorId, ): PersonAddressPostal { $addressPostal = new AddressPostal(); $addressPostal->setStreetAddress($organizationMemberCreationRequest->getStreetAddress1()); $addressPostal->setStreetAddressSecond($organizationMemberCreationRequest->getStreetAddress2()); $addressPostal->setStreetAddressThird($organizationMemberCreationRequest->getStreetAddress3()); $addressPostal->setPostalCode($organizationMemberCreationRequest->getPostalCode()); $addressPostal->setAddressCity($organizationMemberCreationRequest->getCity()); $addressPostal->setCreateDate($creationDate); $addressPostal->setCreatedBy($authorId); $country = $this->countryRepository->find($organizationMemberCreationRequest->getCountryId()); $addressPostal->setAddressCountry($country); $personAddressPostal = new PersonAddressPostal(); $personAddressPostal->setAddressPostal($addressPostal); $personAddressPostal->setType(AddressPostalPersonTypeEnum::ADDRESS_PRINCIPAL); $personAddressPostal->setCreateDate($creationDate); $personAddressPostal->setCreatedBy($authorId); return $personAddressPostal; } /** * Creates a new instance of the ContactPoint class based on the given OrganizationCreationRequest object. * * @param OrganizationMemberCreationRequest $organizationMemberCreationRequest the OrganizationMemberCreationRequest object containing the required data * * @return ContactPoint the newly created instance of the ContactPoint class * * @throws NumberParseException */ protected function makePersonContactPoint( OrganizationMemberCreationRequest $organizationMemberCreationRequest, \DateTime $creationDate, ?int $authorId, ): ContactPoint { if ( $organizationMemberCreationRequest->getPhone() !== null && !$this->phoneNumberUtil->isPossibleNumber($organizationMemberCreationRequest->getPhone()) ) { throw new \RuntimeException('Phone number is invalid or missing (person: '.$organizationMemberCreationRequest->getUsername().')'); } if ( $organizationMemberCreationRequest->getMobile() !== null && !$this->phoneNumberUtil->isPossibleNumber($organizationMemberCreationRequest->getMobile())) { throw new \RuntimeException('Mobile phone number is invalid (person: '.$organizationMemberCreationRequest->getUsername().')'); } $contactPoint = new ContactPoint(); $contactPoint->setContactType(ContactPointTypeEnum::PRINCIPAL); $contactPoint->setEmail($organizationMemberCreationRequest->getEmail()); if ($organizationMemberCreationRequest->getPhone() !== null) { $phoneNumber = $this->phoneNumberUtil->parse($organizationMemberCreationRequest->getPhone()); $contactPoint->setTelphone($phoneNumber); } if ($organizationMemberCreationRequest->getMobile() !== null) { $mobileNumber = $this->phoneNumberUtil->parse($organizationMemberCreationRequest->getMobile()); $contactPoint->setMobilPhone($mobileNumber); } $contactPoint->setCreateDate($creationDate); $contactPoint->setCreatedBy($authorId); return $contactPoint; } protected function makeSubdomain(OrganizationCreationRequest $organizationCreationRequest): Subdomain { $subdomain = new Subdomain(); $subdomain->setSubdomain($organizationCreationRequest->getSubdomain()); $subdomain->setActive(true); return $subdomain; } /** * Créé le site Typo3 et retourne l'id de la page racine du site nouvellement créé, ou null en cas d'erreur. * * @throws RedirectionExceptionInterface * @throws ClientExceptionInterface * @throws TransportExceptionInterface * @throws ServerExceptionInterface */ protected function createTypo3Website(Organization $organization): ?int { $response = $this->typo3Service->createSite($organization->getId()); $content = json_decode($response->getContent(), true); $rootPageUid = $content['root_uid']; if ($response->getStatusCode() === Response::HTTP_OK && $rootPageUid > 0) { // TODO: revoir l'utilité du champs cmsId $organization->setCmsId($rootPageUid); $this->entityManager->persist($organization); $this->entityManager->flush(); return $rootPageUid; } else { $this->logger->critical("/!\ A critical error happened while creating the Typo3 website"); $this->logger->debug($response->getContent()); } return null; } protected function updateAdminassosDb(Organization $organization): void { $response = $this->apiLegacyRequestService->get( UrlBuilder::concatPath('/_internal/request/adminassos/create/organization/', [(string) $organization->getId()]) ); if ($response->getStatusCode() !== Response::HTTP_OK) { throw new \RuntimeException('An error happened while updating the adminassos database: '.$response->getContent()); } } /** * Normalise la chaine comme sont normalisées les champs de l'entité OrganizationIdentification. * * @øee sql/schema-extensions/003-view_organization_identification.sql */ protected function normalizeIdentificationField(string $value): string { $value = strtolower(trim($value)); $value = preg_replace('/[éèê]/u', 'e', $value); $value = preg_replace('/[à]/u', 'a', $value); $value = preg_replace('/[ç]/u', 'c', $value); return preg_replace('/[^a-z0-9]+/u', '+', $value); } /** * /!\ Danger zone /!\. * * Supprime définitivement une organisation, ses données, ses fichiers, son site internet, et son profil Dolibarr. * * Pour éviter une suppression accidentelle, cette méthode ne doit pouvoir être exécutée que si la requête a été * envoyée depuis le localhost. * * @throws \Exception */ public function delete(OrganizationDeletionRequest $organizationDeletionRequest): OrganizationDeletionRequest { SecurityUtils::preventIfNotLocalhost(); $organization = $this->organizationRepository->find($organizationDeletionRequest->getOrganizationId()); if (!$organization) { throw new \RuntimeException('No organization was found for id : '.$organizationDeletionRequest->getOrganizationId()); } $this->logger->info( "Start the deletion of organization '".$organization->getName()."' [".$organization->getId().']' ); $this->entityManager->beginTransaction(); $withError = false; try { $orphanPersons = $this->getFutureOrphanPersons($organization); // On est obligé de supprimer manuellement les paramètres, car c'est l'entité Parameters qui est // propriétaire de la relation Organization ↔ Parameters. $this->entityManager->remove($organization->getParameters()); // Toutes les autres entités liées seront supprimées en cascade $this->entityManager->remove($organization); // Supprime les personnes qui n'avaient pas d'autre Access attaché $deletedPersonIds = []; foreach ($orphanPersons as $person) { $deletedPersonIds[] = $person->getId(); $this->entityManager->remove($person); } $this->entityManager->flush(); $this->entityManager->commit(); $this->logger->info('Organization deleted'); } catch (\Exception $e) { $this->logger->critical("An error happened, operation cancelled\n".$e); $this->entityManager->rollback(); throw $e; } try { $this->deleteTypo3Website($organizationDeletionRequest->getOrganizationId()); $this->logger->info('Typo3 website deleted'); } catch (\Exception $e) { $this->logger->critical('An error happened while deleting the Typo3 website, please proceed manually.'); $this->logger->debug((string) $e); $withError = true; } try { $this->switchDolibarrSocietyToProspect($organizationDeletionRequest->getOrganizationId()); $this->logger->info('Dolibarr society switched to prospect'); } catch (\Exception $e) { $this->logger->critical('An error happened while updating the Dolibarr society, please proceed manually.'); $this->logger->debug((string) $e); $withError = true; } try { $this->fileManager->deleteOrganizationFiles($organizationDeletionRequest->getOrganizationId()); $this->logger->info('Organization files deleted'); } catch (\RuntimeException $e) { // Nothing to delete } catch (\Exception $e) { $this->logger->critical("An error happened while deleting the organization's files, please proceed manually."); $this->logger->debug((string) $e); $withError = true; } foreach ($deletedPersonIds as $personId) { try { $this->fileManager->deletePersonFiles($personId); } catch (\RuntimeException $e) { // Nothing to delete } catch (\Exception $e) { $this->logger->critical("An error happened while deleting the person's files, please proceed manually (id=".$personId.').'); $this->logger->debug((string) $e); $withError = true; } } $this->logger->info("Organization's persons files deleted"); if ($withError) { $organizationDeletionRequest->setStatus(OrganizationDeletionRequest::STATUS_OK_WITH_ERRORS); $this->logger->warning('-- Operation ended with errors, check the logs for more information --'); } else { $organizationDeletionRequest->setStatus(OrganizationDeletionRequest::STATUS_OK); } $this->logger->info( 'The organization has been deleted.' ); return $organizationDeletionRequest; } /** * Supprime tous les Access d'une organisation, ainsi que la Person * rattachée (si celle-ci n'est pas liée à d'autres Access). * * @return array */ protected function getFutureOrphanPersons(Organization $organization): array { $orphans = []; foreach ($organization->getAccesses() as $access) { $person = $access->getPerson(); if ($person->getAccesses()->count() === 1) { $orphans[] = $person; } } return $orphans; } protected function deleteTypo3Website(int $organizationId): void { $this->typo3Service->hardDeleteSite($organizationId); } protected function switchDolibarrSocietyToProspect(int $organizationId): void { $this->dolibarrApiService->switchSocietyToProspect($organizationId); } /** * Sets the password for the admin account of an organization. * * The admin account is identified by the adminaccess property set to true. * The password must meet the complexity requirements: at least 8 characters, * including at least one uppercase letter, one lowercase letter, one digit, * and one special character. * * @param Organization $organization The organization whose admin account password will be set * @param string $password The plain password to set * * @throws \RuntimeException If no admin account is found or if the password doesn't meet the requirements */ public function setAdminAccountPassword(Organization $organization, string $password): void { // Validate password complexity if (!preg_match(self::PASSWORD_PATTERN, $password)) { throw new \RuntimeException('Password must be at least 8 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one special character.'); } // Find the admin access using the repository $adminAccess = $this->accessRepository->findAdminAccess($organization); if (!$adminAccess) { throw new \RuntimeException('No admin account found for this organization.'); } // Set the password on the Person entity $person = $adminAccess->getPerson(); $person->setPassword($password); // Persist the changes $this->entityManager->persist($person); $this->entityManager->flush(); $this->logger->info('Admin account password set for organization: ' . $organization->getId()); } }