logger = $adminLogger; } /** * Créé une nouvelle organisation à partir des données contenues dans une OrganizationCreationRequest * * @param OrganizationCreationRequest $organizationCreationRequest * @return Organization * @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à if ($this->isExistingOrganization($organizationCreationRequest)) { throw new \RuntimeException('An organization named ' . $organizationCreationRequest->getName() . ' already exists'); } // 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 (\Exception $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 (\Exception $e) { $this->logger->critical("An error happened while creating the dolibarr society, please proceed manually."); $this->logger->debug($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 (\Exception $e) { $this->logger->critical("An error happened while updating the bind file, please proceed manually."); $this->logger->debug($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 { $this->createTypo3Website($organization); } catch (\Exception $e) { $this->logger->critical("An error happened while creating the typo3 website, please proceed manually."); $this->logger->debug($e); $withError = true; } } else { $this->logger->warning("Typo3 website creation was not required"); } 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); } return $organization; } /** * Une organisation du même nom existe-t-elle déjà à la même adresse ? * * @param OrganizationCreationRequest $organizationCreationRequest * @return bool */ protected function isExistingOrganization(OrganizationCreationRequest $organizationCreationRequest): bool { return $this ->organizationRepository ->count( [ 'name' => $organizationCreationRequest->getName(), ] ) > 0; } /** * Vérifie la disponibilité et la validité d'un sous domaine * * @param string $subdomainValue * @return void * @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. * * @param OrganizationCreationRequest $organizationCreationRequest * @return Organization * @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); $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); $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 * * @param OrganizationCreationRequest $organizationCreationRequest * @return Organization */ 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()); 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()); 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); $organizationAddressPostal = new OrganizationAddressPostal(); $organizationAddressPostal->setAddressPostal($addressPostal); $organizationAddressPostal->setType(AddressPostalOrganizationTypeEnum::ADDRESS_HEAD_OFFICE); 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 { $phoneUtil = PhoneNumberUtil::getInstance(); if (!$phoneUtil->isPossibleNumber($organizationCreationRequest->getPhoneNumber())) { throw new \RuntimeException("Phone number is invalid or missing"); } $phoneNumber = $phoneUtil->parse($organizationCreationRequest->getPhoneNumber()); $contactPoint = new ContactPoint(); $contactPoint->setContactType(ContactPointTypeEnum::PRINCIPAL); $contactPoint->setEmail($organizationCreationRequest->getEmail()); $contactPoint->setTelphone($phoneNumber); 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 ($parent->getSettings()->getProduct() !== SettingsProductEnum::MANAGER) { throw new \RuntimeException( "Parent organization must have the product 'manager' (actual product: " . $organizationCreationRequest->getParentId() . ")" ); } $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{12}/", $organizationCreationRequest->getIdentifier())) { throw new \RuntimeException("CMF identifier is missing or invalid."); } if ($this ->organizationRepository ->count( ['identifier' => $organizationCreationRequest->getIdentifier()] ) > 0) { throw new \RuntimeException("CMF identifier is already registered : '" . $organizationCreationRequest->getIdentifier() ."'"); } } $networkOrganization = new NetworkOrganization(); $networkOrganization->setParent($parent); $networkOrganization->setNetwork($network); $networkOrganization->setStartDate(DatesUtils::new()); 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); $adminAccess = new Access(); $adminAccess->setAdminAccess(true); $adminAccess->setPerson($admin); 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 ): Access { if (is_int($creationRequestData)) { $person = $this->personRepository->find($creationRequestData); } else { $person = new Person(); $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); $person->addPersonAddressPostal($personPostalAddress); $contactPoint = $this->makePersonContactPoint($creationRequestData); $person->addContactPoint($contactPoint); } $access = new Access(); $access->setPerson($person); 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): PersonAddressPostal { $addressPostal = new AddressPostal(); $addressPostal->setStreetAddress($organizationMemberCreationRequest->getStreetAddress1()); $addressPostal->setStreetAddressSecond($organizationMemberCreationRequest->getStreetAddress2()); $addressPostal->setStreetAddressThird($organizationMemberCreationRequest->getStreetAddress3()); $addressPostal->setPostalCode($organizationMemberCreationRequest->getPostalCode()); $addressPostal->setAddressCity($organizationMemberCreationRequest->getCity()); $country = $this->countryRepository->find($organizationMemberCreationRequest->getCountryId()); $addressPostal->setAddressCountry($country); $personAddressPostal = new PersonAddressPostal(); $personAddressPostal->setAddressPostal($addressPostal); $personAddressPostal->setType(AddressPostalPersonTypeEnum::ADDRESS_PRINCIPAL); 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): ContactPoint { $phoneUtil = PhoneNumberUtil::getInstance(); $phoneNumber = $phoneUtil->parse($organizationMemberCreationRequest->getPhone()); $contactPoint = new ContactPoint(); $contactPoint->setContactType(ContactPointTypeEnum::PRINCIPAL); $contactPoint->setEmail($organizationMemberCreationRequest->getEmail()); $contactPoint->setTelphone($phoneNumber); if ($organizationMemberCreationRequest->getMobile() !== null) { $mobileNumber = $phoneUtil->parse($organizationMemberCreationRequest->getMobile()); $contactPoint->setMobilPhone($mobileNumber); } 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()); $rootPageUid = json_decode($response->getContent(), true); 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; } }