| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980 |
- <?php
- declare(strict_types=1);
- namespace App\Service\Organization;
- use App\ApiResources\Organization\OrganizationCreationRequest;
- use App\ApiResources\Organization\OrganizationDeletionRequest;
- use App\ApiResources\Organization\OrganizationMemberCreationRequest;
- use App\Entity\Access\Access;
- use App\Entity\Access\OrganizationFunction;
- use App\Entity\Core\AddressPostal;
- use App\Entity\Core\ContactPoint;
- use App\Entity\Education\Cycle;
- use App\Entity\Network\NetworkOrganization;
- use App\Entity\Organization\Organization;
- use App\Entity\Organization\OrganizationAddressPostal;
- use App\Entity\Organization\Parameters;
- use App\Entity\Organization\Settings;
- use App\Entity\Organization\Subdomain;
- use App\Entity\Person\Person;
- use App\Entity\Person\PersonAddressPostal;
- use App\Enum\Access\FunctionEnum;
- use App\Enum\Core\ContactPointTypeEnum;
- use App\Enum\Education\CycleEnum;
- use App\Enum\Network\NetworkEnum;
- use App\Enum\Organization\AddressPostalOrganizationTypeEnum;
- use App\Enum\Organization\SettingsProductEnum;
- use App\Enum\Person\AddressPostalPersonTypeEnum;
- use App\Repository\Access\FunctionTypeRepository;
- use App\Repository\Core\CountryRepository;
- use App\Repository\Organization\OrganizationIdentificationRepository;
- use App\Repository\Organization\OrganizationRepository;
- use App\Repository\Person\PersonRepository;
- use App\Service\ApiLegacy\ApiLegacyRequestService;
- use App\Service\Dolibarr\DolibarrApiService;
- use App\Service\File\FileManager;
- use App\Service\Organization\Utils as OrganizationUtils;
- use App\Service\Typo3\BindFileService;
- use App\Service\Typo3\SubdomainService;
- use App\Service\Typo3\Typo3Service;
- use App\Service\Utils\DatesUtils;
- use App\Service\Utils\SecurityUtils;
- use App\Service\Utils\UrlBuilder;
- use Doctrine\ORM\EntityManagerInterface;
- use libphonenumber\NumberParseException;
- use libphonenumber\PhoneNumberUtil;
- use Psr\Log\LoggerInterface;
- use Symfony\Component\HttpFoundation\Response;
- use Symfony\Component\String\ByteString;
- use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
- use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
- use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
- use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
- use Symfony\Contracts\Service\Attribute\Required;
- class OrganizationFactory
- {
- private LoggerInterface $logger;
- protected PhoneNumberUtil $phoneNumberUtil;
- /**
- * Regex pattern for password validation.
- * Requires at least 8 characters, including at least one uppercase letter,
- * one lowercase letter, one digit, and one special character.
- */
- private const PASSWORD_PATTERN = '/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^\da-zA-Z]).{8,}$/';
- public function __construct(
- private readonly SubdomainService $subdomainService,
- private readonly OrganizationRepository $organizationRepository,
- private readonly CountryRepository $countryRepository,
- private readonly OrganizationUtils $organizationUtils,
- private readonly Typo3Service $typo3Service,
- private readonly DolibarrApiService $dolibarrApiService,
- private readonly EntityManagerInterface $entityManager,
- private readonly PersonRepository $personRepository,
- private readonly BindFileService $bindFileService,
- private readonly OrganizationIdentificationRepository $organizationIdentificationRepository,
- private readonly ApiLegacyRequestService $apiLegacyRequestService,
- private readonly FunctionTypeRepository $functionTypeRepository,
- private readonly FileManager $fileManager,
- private readonly \App\Repository\Access\AccessRepository $accessRepository,
- ) {
- $this->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<Person>
- */
- 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());
- }
- }
|