OrganizationFactory.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769
  1. <?php
  2. namespace App\Service\Organization;
  3. use App\ApiResources\Organization\OrganizationCreationRequest;
  4. use App\ApiResources\Organization\OrganizationDeletionRequest;
  5. use App\ApiResources\Organization\OrganizationMemberCreationRequest;
  6. use App\Entity\Access\Access;
  7. use App\Entity\Billing\BillingSetting;
  8. use App\Entity\Core\AddressPostal;
  9. use App\Entity\Core\ContactPoint;
  10. use App\Entity\Education\CriteriaNotation;
  11. use App\Entity\Education\Cycle;
  12. use App\Entity\Network\NetworkOrganization;
  13. use App\Entity\Organization\Organization;
  14. use App\Entity\Organization\OrganizationAddressPostal;
  15. use App\Entity\Organization\Parameters;
  16. use App\Entity\Organization\Settings;
  17. use App\Entity\Organization\Subdomain;
  18. use App\Entity\Person\Person;
  19. use App\Entity\Person\PersonAddressPostal;
  20. use App\Enum\Core\ContactPointTypeEnum;
  21. use App\Enum\Education\CycleEnum;
  22. use App\Enum\Network\NetworkEnum;
  23. use App\Enum\Organization\AddressPostalOrganizationTypeEnum;
  24. use App\Enum\Person\AddressPostalPersonTypeEnum;
  25. use App\Repository\Core\CountryRepository;
  26. use App\Repository\Organization\OrganizationRepository;
  27. use App\Repository\Person\PersonRepository;
  28. use App\Service\Dolibarr\DolibarrApiService;
  29. use App\Service\Typo3\BindFileService;
  30. use App\Service\Typo3\SubdomainService;
  31. use App\Service\Typo3\Typo3Service;
  32. use App\Service\Utils\DatesUtils;
  33. use Doctrine\Common\Collections\Collection;
  34. use Doctrine\ORM\EntityManagerInterface;
  35. use Elastica\Param;
  36. use Exception;
  37. use libphonenumber\NumberParseException;
  38. use libphonenumber\PhoneNumberUtil;
  39. use Psr\Log\LoggerInterface;
  40. use SebastianBergmann\Type\RuntimeException;
  41. use Symfony\Component\HttpFoundation\Response;
  42. use Symfony\Component\String\ByteString;
  43. use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
  44. use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
  45. use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
  46. use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
  47. use Symfony\Contracts\Service\Attribute\Required;
  48. use Throwable;
  49. use App\Service\Organization\Utils as OrganizationUtils;
  50. class OrganizationFactory
  51. {
  52. private LoggerInterface $logger;
  53. public function __construct(
  54. private readonly SubdomainService $subdomainService,
  55. private readonly OrganizationRepository $organizationRepository,
  56. private readonly CountryRepository $countryRepository,
  57. private readonly OrganizationUtils $organizationUtils,
  58. private readonly Typo3Service $typo3Service,
  59. private readonly DolibarrApiService $dolibarrApiService,
  60. private readonly EntityManagerInterface $entityManager,
  61. private readonly PersonRepository $personRepository,
  62. private readonly BindFileService $bindFileService,
  63. ) {}
  64. #[Required]
  65. /** @see https://symfony.com/doc/current/logging/channels_handlers.html#how-to-autowire-logger-channels */
  66. public function setLoggerInterface(LoggerInterface $adminLogger): void
  67. {
  68. $this->logger = $adminLogger;
  69. }
  70. /**
  71. * Créé une nouvelle organisation à partir des données contenues dans une OrganizationCreationRequest
  72. *
  73. * @param OrganizationCreationRequest $organizationCreationRequest
  74. * @return Organization
  75. * @throws TransportExceptionInterface
  76. * @throws Throwable
  77. */
  78. public function create(OrganizationCreationRequest $organizationCreationRequest): Organization
  79. {
  80. $this->logger->info(
  81. "Start the creation of a new organization named '" . $organizationCreationRequest->getName() . "'"
  82. );
  83. $this->entityManager->beginTransaction();
  84. try {
  85. // On vérifie si cette organisation n'existe pas déjà
  86. if ($this->isExistingOrganization($organizationCreationRequest)) {
  87. throw new \RuntimeException('An organization named ' . $organizationCreationRequest->getName() . ' already exists');
  88. }
  89. // On vérifie la validité et la disponibilité du sous domaine
  90. $this->validateSubdomain($organizationCreationRequest->getSubdomain());
  91. $this->logger->info("Subdomain is valid and available : '" . $organizationCreationRequest->getSubdomain() . "'");
  92. // On construit l'organisation et ses relations
  93. $organization = $this->makeOrganizationWithRelations($organizationCreationRequest);
  94. $this->logger->info("Organization created with all its relations");
  95. // On persiste et on commit
  96. $this->entityManager->persist($organization);
  97. $this->entityManager->flush();
  98. // Création de la société Dolibarr
  99. $dolibarrId = $this->dolibarrApiService->createSociety(
  100. $organization,
  101. $organizationCreationRequest->isClient()
  102. );
  103. $this->logger->info("New dolibarr structure created (uid : " . $dolibarrId . ")");
  104. $this->entityManager->commit();
  105. $this->logger->debug(" - New entities committed in DB");
  106. $this->logger->info("Organization persisted in the DB");
  107. } catch (\Exception $e) {
  108. $this->logger->critical("An error happened, operation cancelled\n" . $e);
  109. $this->entityManager->rollback();
  110. throw $e;
  111. }
  112. // Register the subdomain into the BindFile (takes up to 5min to take effect)
  113. $this->bindFileService->registerSubdomain($organizationCreationRequest->getSubdomain());
  114. $this->logger->info("Subdomain registered");
  115. // Création du site typo3 (on est obligé d'attendre que l'organisation soit persistée en base)
  116. if ($organizationCreationRequest->getCreateWebsite()) {
  117. $this->createTypo3Website($organization);
  118. } else {
  119. $this->logger->warning("Typo3 website creation was not required");
  120. }
  121. return $organization;
  122. }
  123. /**
  124. * Une organisation du même nom existe-t-elle déjà à la même adresse ?
  125. *
  126. * @param OrganizationCreationRequest $organizationCreationRequest
  127. * @return bool
  128. */
  129. protected function isExistingOrganization(OrganizationCreationRequest $organizationCreationRequest): bool
  130. {
  131. return $this
  132. ->organizationRepository
  133. ->count(
  134. [
  135. 'name' => $organizationCreationRequest->getName(),
  136. ]
  137. ) > 0;
  138. }
  139. /**
  140. * Vérifie la disponibilité et la validité d'un sous domaine
  141. *
  142. * @param string $subdomainValue
  143. * @return void
  144. * @throws \Exception
  145. */
  146. protected function validateSubdomain(string $subdomainValue): void
  147. {
  148. if (!$this->subdomainService->isValidSubdomain($subdomainValue)) {
  149. throw new \RuntimeException("Not a valid subdomain : " . $subdomainValue);
  150. }
  151. if ($this->subdomainService->isReservedSubdomain($subdomainValue)) {
  152. throw new \RuntimeException("This subdomain is not available : " . $subdomainValue);
  153. }
  154. if ($this->subdomainService->isRegistered($subdomainValue)) {
  155. throw new \RuntimeException("This subdomain is already registered : " . $subdomainValue);
  156. }
  157. }
  158. /**
  159. * Créé une nouvelle instance d'organisation, et toutes les instances liées (paramètres, contact, adresses, ...),
  160. * selon le contenu de la requête de création.
  161. *
  162. * @param OrganizationCreationRequest $organizationCreationRequest
  163. * @return Organization
  164. * @throws Throwable
  165. */
  166. protected function makeOrganizationWithRelations(
  167. OrganizationCreationRequest $organizationCreationRequest
  168. ): Organization
  169. {
  170. // Création de l'organisation
  171. $organization = $this->makeOrganization($organizationCreationRequest);
  172. $this->logger->debug(" - Organization created");
  173. // Création des Parameters
  174. $parameters = $this->makeParameters($organizationCreationRequest);
  175. $organization->setParameters($parameters);
  176. $this->logger->debug(" - Parameters created");
  177. // Création des Settings
  178. $settings = $this->makeSettings($organizationCreationRequest);
  179. $organization->setSettings($settings);
  180. $this->logger->debug(" - Settings created");
  181. // Création de l'adresse postale
  182. $organizationAddressPostal = $this->makePostalAddress($organizationCreationRequest);
  183. $organization->addOrganizationAddressPostal($organizationAddressPostal);
  184. $this->logger->debug(" - OrganizationAddressPostal created");
  185. // Création du point de contact
  186. $contactPoint = $this->makeContactPoint($organizationCreationRequest);
  187. $organization->addContactPoint($contactPoint);
  188. $this->logger->debug(" - ContactPoint created");
  189. // Rattachement au réseau
  190. $networkOrganization = $this->makeNetworkOrganization($organizationCreationRequest);
  191. $organization->addNetworkOrganization($networkOrganization);
  192. $this->logger->debug(" - NetworkOrganization created");
  193. // Créé l'admin
  194. $adminAccess = $this->makeAdminAccess($organizationCreationRequest);
  195. $organization->addAccess($adminAccess);
  196. $this->logger->debug(" - Admin access created");
  197. // Création des cycles
  198. foreach ($this->makeCycles() as $cycle) {
  199. $organization->addCycle($cycle);
  200. }
  201. $this->logger->debug(" - Cycles created");
  202. // Création du président (si renseigné)
  203. $presidentCreationRequest = $organizationCreationRequest->getPresident();
  204. if ($presidentCreationRequest !== null) {
  205. $presidentAccess = $this->makeAccess($presidentCreationRequest);
  206. $organization->addAccess($presidentAccess);
  207. $this->logger->debug(" - President access created");
  208. }
  209. // Création du directeur (si renseigné)
  210. $directorCreationRequest = $organizationCreationRequest->getDirector();
  211. if ($directorCreationRequest !== null) {
  212. $directorAccess = $this->makeAccess($directorCreationRequest);
  213. $organization->addAccess($directorAccess);
  214. $this->logger->debug(" - Director access created");
  215. }
  216. // Création du sous-domaine
  217. $subdomain = $this->makeSubdomain($organizationCreationRequest);
  218. $subdomain->setOrganization($organization);
  219. // <--- Pour la rétrocompatibilité avec la v1 ; pourra être supprimé lorsque la migration sera achevée
  220. $parameters = $organization->getParameters();
  221. $parameters->setSubDomain($organizationCreationRequest->getSubdomain());
  222. $parameters->setOtherWebsite('https://' . $organizationCreationRequest->getSubdomain() . '.opentalent.fr');
  223. $this->entityManager->persist($parameters);
  224. // --->
  225. $this->logger->debug(" - Subdomain created");
  226. return $organization;
  227. }
  228. /**
  229. * Créé une nouvelle instance d'organisation
  230. *
  231. * @param OrganizationCreationRequest $organizationCreationRequest
  232. * @return Organization
  233. */
  234. protected function makeOrganization(OrganizationCreationRequest $organizationCreationRequest): Organization
  235. {
  236. // Création de l'organisation
  237. $organization = new Organization();
  238. $organization->setName($organizationCreationRequest->getName());
  239. $organization->setLegalStatus($organizationCreationRequest->getLegalStatus());
  240. $organization->setPrincipalType($organizationCreationRequest->getPrincipalType());
  241. $organization->setIdentifier($organizationCreationRequest->getIdentifier());
  242. $this->entityManager->persist($organization);
  243. return $organization;
  244. }
  245. /**
  246. * Create a new Parameters object from the data in an OrganizationCreationRequest
  247. *
  248. * @param OrganizationCreationRequest $organizationCreationRequest The organization creation request
  249. * @return Parameters The created Parameters object
  250. * @throws Throwable If there is an error
  251. */
  252. protected function makeParameters(OrganizationCreationRequest $organizationCreationRequest): Parameters
  253. {
  254. $parameters = new Parameters();
  255. $this->entityManager->persist($parameters);
  256. return $parameters;
  257. }
  258. /**
  259. * Creates a new instance of the Settings class based on the given OrganizationCreationRequest object.
  260. *
  261. * @param OrganizationCreationRequest $organizationCreationRequest The OrganizationCreationRequest object containing the required data.
  262. *
  263. * @return Settings The newly created instance of the Settings class.
  264. */
  265. protected function makeSettings(OrganizationCreationRequest $organizationCreationRequest): Settings
  266. {
  267. $settings = new Settings();
  268. $settings->setProduct($organizationCreationRequest->getProduct());
  269. $this->entityManager->persist($settings);
  270. return $settings;
  271. }
  272. /**
  273. * Creates a new instance of the OrganizationAddressPostal class based on the given OrganizationCreationRequest object.
  274. *
  275. * @param OrganizationCreationRequest $organizationCreationRequest The OrganizationCreationRequest object containing the required data.
  276. *
  277. * @return OrganizationAddressPostal The newly created instance of the OrganizationAddressPostal class.
  278. */
  279. protected function makePostalAddress(OrganizationCreationRequest $organizationCreationRequest): OrganizationAddressPostal
  280. {
  281. $addressPostal = new AddressPostal();
  282. $addressPostal->setStreetAddress($organizationCreationRequest->getStreetAddress1());
  283. $addressPostal->setStreetAddressSecond($organizationCreationRequest->getStreetAddress2());
  284. $addressPostal->setStreetAddressThird($organizationCreationRequest->getStreetAddress3());
  285. $addressPostal->setPostalCode($organizationCreationRequest->getPostalCode());
  286. $addressPostal->setAddressCity($organizationCreationRequest->getCity());
  287. $country = $this->countryRepository->find($organizationCreationRequest->getCountryId());
  288. $addressPostal->setAddressCountry($country);
  289. $this->entityManager->persist($addressPostal);
  290. $organizationAddressPostal = new OrganizationAddressPostal();
  291. $organizationAddressPostal->setAddressPostal($addressPostal);
  292. $organizationAddressPostal->setType(AddressPostalOrganizationTypeEnum::ADDRESS_HEAD_OFFICE);
  293. $this->entityManager->persist($organizationAddressPostal);
  294. return $organizationAddressPostal;
  295. }
  296. /**
  297. * Creates a new instance of the ContactPoint class based on the given OrganizationCreationRequest object.
  298. *
  299. * @param OrganizationCreationRequest $organizationCreationRequest The OrganizationCreationRequest object containing the required data.
  300. *
  301. * @return ContactPoint The newly created instance of the ContactPoint class.
  302. * @throws NumberParseException
  303. */
  304. protected function makeContactPoint(OrganizationCreationRequest $organizationCreationRequest): ContactPoint
  305. {
  306. $phoneUtil = PhoneNumberUtil::getInstance();
  307. $phoneNumber = $phoneUtil->parse($organizationCreationRequest->getPhoneNumber());
  308. $contactPoint = new ContactPoint();
  309. $contactPoint->setContactType(ContactPointTypeEnum::PRINCIPAL);
  310. $contactPoint->setEmail($organizationCreationRequest->getEmail());
  311. $contactPoint->setTelphone($phoneNumber);
  312. $this->entityManager->persist($contactPoint);
  313. return $contactPoint;
  314. }
  315. /**
  316. * Creates a new instance of the NetworkOrganization class based on the given OrganizationCreationRequest object.
  317. *
  318. * @param OrganizationCreationRequest $organizationCreationRequest The OrganizationCreationRequest object containing the required data.
  319. *
  320. * @return NetworkOrganization The newly created instance of the NetworkOrganization class.
  321. *
  322. * @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.
  323. */
  324. protected function makeNetworkOrganization(OrganizationCreationRequest $organizationCreationRequest): NetworkOrganization
  325. {
  326. $parent = $this->organizationRepository->find($organizationCreationRequest->getParentId());
  327. if (!$parent) {
  328. throw new \RuntimeException('No parent organization found for id ' . $organizationCreationRequest->getParentId());
  329. }
  330. $networkOrganization = $this->organizationUtils->getActiveNetworkOrganization($parent);
  331. if (!$networkOrganization) {
  332. throw new \RuntimeException('No network found for parent ' . $organizationCreationRequest->getParentId());
  333. }
  334. $network = $networkOrganization->getNetwork();
  335. // Si réseau CMF, on vérifie que le matricule est valide
  336. if ($network->getId() === NetworkEnum::CMF->value) {
  337. if (!preg_match("/FR\d{12}/", $organizationCreationRequest->getIdentifier())) {
  338. throw new \RuntimeException("CMF identifier is missing or invalid.");
  339. }
  340. }
  341. $networkOrganization = new NetworkOrganization();
  342. $networkOrganization->setParent($parent);
  343. $networkOrganization->setNetwork($network);
  344. $networkOrganization->setStartDate(DatesUtils::new());
  345. $this->entityManager->persist($networkOrganization);
  346. return $networkOrganization;
  347. }
  348. /**
  349. * Creates a new instance of the Access class with admin access based on the given OrganizationCreationRequest object.
  350. *
  351. * @param OrganizationCreationRequest $organizationCreationRequest The OrganizationCreationRequest object containing the required data.
  352. *
  353. * @return Access The newly created instance of the Access class with admin access.
  354. */
  355. protected function makeAdminAccess(OrganizationCreationRequest $organizationCreationRequest): Access
  356. {
  357. $admin = new Person();
  358. $admin->setUsername('admin' . strtolower($organizationCreationRequest->getSubdomain()));
  359. $randomString = ByteString::fromRandom(32)->toString();
  360. $admin->setPassword($randomString);
  361. $this->entityManager->persist($admin);
  362. $adminAccess = new Access();
  363. $adminAccess->setAdminAccess(true);
  364. $adminAccess->setPerson($admin);
  365. $this->entityManager->persist($adminAccess);
  366. return $adminAccess;
  367. }
  368. /**
  369. * Creates an array of Cycle objects based on a predefined set of data.
  370. *
  371. * @return Cycle[] An array of Cycle objects.
  372. */
  373. protected function makeCycles(): array
  374. {
  375. $cyclesData = [
  376. ['Cycle initiation', 10, CycleEnum::INITIATION_CYCLE],
  377. ['Cycle 1', 20, CycleEnum::CYCLE_1],
  378. ['Cycle 2', 30, CycleEnum::CYCLE_2],
  379. ['Cycle 3', 40, CycleEnum::CYCLE_3],
  380. ['Cycle 4', 50, CycleEnum::CYCLE_4],
  381. ['Hors cycle', 60, CycleEnum::OUT_CYCLE],
  382. ];
  383. $cycles = [];
  384. foreach ($cyclesData as $cycleData) {
  385. $cycle = new Cycle();
  386. $cycle->setLabel($cycleData[0]);
  387. $cycle->setOrder($cycleData[1]);
  388. $cycle->setCycleEnum($cycleData[2]);
  389. $cycle->setIsSystem(false);
  390. $this->entityManager->persist($cycle);
  391. $cycles[] = $cycle;
  392. }
  393. return $cycles;
  394. }
  395. /**
  396. * Creates an Access object based on the given OrganizationMemberCreationRequest.
  397. *
  398. * @param int|OrganizationMemberCreationRequest $creationRequestData The request object containing the
  399. * necessary data for creating a Person object,
  400. * or the id of an existing one.
  401. * @return Access The created Access object.
  402. * @throws NumberParseException
  403. */
  404. protected function makeAccess(
  405. int | OrganizationMemberCreationRequest $creationRequestData
  406. ): Access
  407. {
  408. if (is_int($creationRequestData)) {
  409. $person = $this->personRepository->find($creationRequestData);
  410. } else {
  411. $person = new Person();
  412. $person->setUsername($creationRequestData->getUsername());
  413. $person->setPassword(ByteString::fromRandom(32)->toString());
  414. $person->setGender($creationRequestData->getGender());
  415. $person->setName(
  416. ucfirst(strtolower($creationRequestData->getName()))
  417. );
  418. $person->setGivenName(
  419. ucfirst(strtolower($creationRequestData->getGivenName()))
  420. );
  421. $personPostalAddress = $this->makePersonPostalAddress($creationRequestData);
  422. $person->addPersonAddressPostal($personPostalAddress);
  423. $contactPoint = $this->makePersonContactPoint($creationRequestData);
  424. $person->addContactPoint($contactPoint);
  425. $this->entityManager->persist($person);
  426. }
  427. $access = new Access();
  428. $access->setPerson($person);
  429. $this->entityManager->persist($access);
  430. return $access;
  431. }
  432. /**
  433. * Creates a PersonAddressPostal object based on the given OrganizationMemberCreationRequest.
  434. *
  435. * @param OrganizationMemberCreationRequest $organizationMemberCreationRequest The request object containing the
  436. * necessary data for creating a
  437. * PersonAddressPostal object.
  438. * @return PersonAddressPostal The created PersonAddressPostal object.
  439. */
  440. protected function makePersonPostalAddress(OrganizationMemberCreationRequest $organizationMemberCreationRequest): PersonAddressPostal
  441. {
  442. $addressPostal = new AddressPostal();
  443. $addressPostal->setStreetAddress($organizationMemberCreationRequest->getStreetAddress1());
  444. $addressPostal->setStreetAddressSecond($organizationMemberCreationRequest->getStreetAddress2());
  445. $addressPostal->setStreetAddressThird($organizationMemberCreationRequest->getStreetAddress3());
  446. $addressPostal->setPostalCode($organizationMemberCreationRequest->getPostalCode());
  447. $addressPostal->setAddressCity($organizationMemberCreationRequest->getCity());
  448. $country = $this->countryRepository->find($organizationMemberCreationRequest->getCountryId());
  449. $addressPostal->setAddressCountry($country);
  450. $this->entityManager->persist($addressPostal);
  451. $personAddressPostal = new PersonAddressPostal();
  452. $personAddressPostal->setAddressPostal($addressPostal);
  453. $personAddressPostal->setType(AddressPostalPersonTypeEnum::ADDRESS_PRINCIPAL);
  454. $this->entityManager->persist($personAddressPostal);
  455. return $personAddressPostal;
  456. }
  457. /**
  458. * Creates a new instance of the ContactPoint class based on the given OrganizationCreationRequest object.
  459. *
  460. * @param OrganizationMemberCreationRequest $organizationMemberCreationRequest The OrganizationMemberCreationRequest object containing the required data.
  461. *
  462. * @return ContactPoint The newly created instance of the ContactPoint class.
  463. * @throws NumberParseException
  464. */
  465. protected function makePersonContactPoint(OrganizationMemberCreationRequest $organizationMemberCreationRequest): ContactPoint
  466. {
  467. $phoneUtil = PhoneNumberUtil::getInstance();
  468. $phoneNumber = $phoneUtil->parse($organizationMemberCreationRequest->getPhone());
  469. $contactPoint = new ContactPoint();
  470. $contactPoint->setContactType(ContactPointTypeEnum::PRINCIPAL);
  471. $contactPoint->setEmail($organizationMemberCreationRequest->getEmail());
  472. $contactPoint->setTelphone($phoneNumber);
  473. if ($organizationMemberCreationRequest->getMobile() !== null) {
  474. $mobileNumber = $phoneUtil->parse($organizationMemberCreationRequest->getMobile());
  475. $contactPoint->setMobilPhone($mobileNumber);
  476. }
  477. $this->entityManager->persist($contactPoint);
  478. return $contactPoint;
  479. }
  480. protected function makeSubdomain(OrganizationCreationRequest $organizationCreationRequest): Subdomain
  481. {
  482. $subdomain = new Subdomain();
  483. $subdomain->setSubdomain($organizationCreationRequest->getSubdomain());
  484. $subdomain->setActive(true);
  485. $this->entityManager->persist($subdomain);
  486. return $subdomain;
  487. }
  488. /**
  489. * Créé le site Typo3 et retourne l'id de la page racine du site nouvellement créé, ou null en cas d'erreur
  490. *
  491. * @throws RedirectionExceptionInterface
  492. * @throws ClientExceptionInterface
  493. * @throws TransportExceptionInterface
  494. * @throws ServerExceptionInterface
  495. */
  496. protected function createTypo3Website(Organization $organization): ?int {
  497. $response = $this->typo3Service->createSite($organization->getId());
  498. $rootPageUid = json_decode($response->getContent(), true);
  499. if ($response->getStatusCode() === Response::HTTP_OK && $rootPageUid > 0) {
  500. // TODO: revoir l'utilité du champs cmsId
  501. $organization->setCmsId($rootPageUid);
  502. $this->entityManager->persist($organization);
  503. $this->entityManager->flush();
  504. return $rootPageUid;
  505. } else {
  506. $this->logger->critical("/!\ A critical error happened while creating the Typo3 website");
  507. $this->logger->debug($response->getContent());
  508. }
  509. return null;
  510. }
  511. /**
  512. * /!\ Danger zone /!\
  513. *
  514. * Supprime définitivement une organisation, ses données, ses fichiers, son site internet, et son profil Dolibarr.
  515. *
  516. * Pour éviter une suppression accidentelle, cette méthode ne doit pouvoir être exécutée que si la requête a été
  517. * envoyée depuis le localhost.
  518. *
  519. * @param OrganizationDeletionRequest $organizationDeletionRequest
  520. * @return OrganizationDeletionRequest
  521. * @throws Exception
  522. */
  523. public function delete(OrganizationDeletionRequest $organizationDeletionRequest): OrganizationDeletionRequest
  524. {
  525. $this->preventIfNotLocalhost();
  526. $organization = $this->organizationRepository->find($organizationDeletionRequest->getOrganizationId());
  527. $this->logger->info(
  528. "Start the deletion of organization '" . $organization->getName() . "' [" . $organization->getId() . "]"
  529. );
  530. $this->entityManager->beginTransaction();
  531. $withError = false;
  532. try {
  533. // On doit gérer à part la suppression des Access afin de supprimer au passage les Person devenues 'orphelines'
  534. $this->deleteOrganizationAccesses($organization);
  535. // On est obligé de supprimer manuellement les paramètres, car c'est l'entité Parameters qui est
  536. // propriétaire de la relation Organization ↔ Parameters.
  537. $this->entityManager->remove($organization->getParameters());
  538. // Toutes les autres entités liées seront supprimées en cascade
  539. $this->entityManager->remove($organization);
  540. $this->entityManager->flush();
  541. $this->entityManager->commit();
  542. } catch (\Exception $e) {
  543. $this->logger->critical("An error happened, operation cancelled\n" . $e);
  544. $this->entityManager->rollback();
  545. throw $e;
  546. }
  547. try {
  548. $this->deleteTypo3Website($organization);
  549. } catch (\Exception $e) {
  550. $this->logger->critical("An error happened while deleting the Typo3 website, please proceed manually.");
  551. $this->logger->debug($e);
  552. $withError = true;
  553. }
  554. try {
  555. $this->deleteDolibarrSociety($organization);
  556. } catch (\Exception $e) {
  557. $this->logger->critical("An error happened while deleting the Dolibarr society, please proceed manually.");
  558. $this->logger->debug($e);
  559. $withError = true;
  560. }
  561. try {
  562. $this->deleteLocalDirectories($organization);
  563. } catch (\Exception $e) {
  564. $this->logger->critical("An error happened while deleting the local directories, please proceed manually.");
  565. $this->logger->debug($e);
  566. $withError = true;
  567. }
  568. try {
  569. $this->deleteDirectoriesV1($organization);
  570. } catch (\Exception $e) {
  571. $this->logger->critical("An error happened while deleting the V1 directories, please proceed manually.");
  572. $this->logger->debug($e);
  573. $withError = true;
  574. }
  575. try {
  576. $this->deleteDirectories59($organization);
  577. } catch (\Exception $e) {
  578. $this->logger->critical("An error happened while deleting the 5.9 directories, please proceed manually.");
  579. $this->logger->debug($e);
  580. $withError = true;
  581. }
  582. if ($withError) {
  583. $organizationDeletionRequest->setStatus(OrganizationDeletionRequest::STATUS_OK_WITH_ERRORS);
  584. $this->logger->warning("-- Operation ended with errors, check the logs for more information --");
  585. } else {
  586. $organizationDeletionRequest->setStatus(OrganizationDeletionRequest::STATUS_OK);
  587. }
  588. return $organizationDeletionRequest;
  589. }
  590. /**
  591. * Lève une exception si la méthode a été appelée dans le cadre d'un appel API originaire d'un hôte
  592. * différent de localhost.
  593. *
  594. * @return void
  595. */
  596. protected function preventIfNotLocalhost(): void
  597. {
  598. if (
  599. $_SERVER &&
  600. $_SERVER['APP_ENV'] !== 'docker' &&
  601. $_SERVER['SERVER_ADDR'] !== $_SERVER['REMOTE_ADDR']
  602. ) {
  603. throw new \RuntimeException("This operation is restricted to localhost");
  604. }
  605. }
  606. /**
  607. * Supprime tous les Access d'une organisation, ainsi que la Person
  608. * rattachée (si celle-ci n'est pas liée à d'autres Access)
  609. *
  610. * @param Organization $organization
  611. * @return void
  612. */
  613. protected function deleteOrganizationAccesses(Organization $organization): void
  614. {
  615. foreach ($organization->getAccesses() as $access) {
  616. $person = $access->getPerson();
  617. if ($person->getAccesses()->count() === 1) {
  618. $this->entityManager->remove($person);
  619. }
  620. $this->entityManager->remove($access);
  621. }
  622. }
  623. // TODO: à revoir, c'est du many to many
  624. // protected function removeTypeOfPractices(Organization $organization): void {
  625. // foreach ($organization->getTypeOfPractices() as $typeOfPractice) {
  626. // $organization->removeTypeOfPractice($typeOfPractice);
  627. // }
  628. // }
  629. // TODO: à revoir, c'est du many to many
  630. // protected function deleteContactPoints(Organization $organization): void
  631. // {
  632. // foreach ($organization->getContactPoints() as $contactPoint) {
  633. // $this->entityManager->remove($contactPoint);
  634. // }
  635. // }
  636. // TODO: à revoir, c'est du many to many
  637. // protected function deleteBankAccounts(Organization $organization): void {
  638. // foreach ($organization->getBankAccounts() as $bankAccount) {
  639. // $this->entityManager->remove($bankAccount);
  640. // }
  641. // }
  642. protected function deleteTypo3Website(Organization $organization): void
  643. {
  644. // TODO: implement
  645. // $this->typo3Service->deleteSite($organization->getId());
  646. }
  647. protected function deleteDolibarrSociety(Organization $organization): void
  648. {
  649. // TODO: implement
  650. }
  651. protected function deleteLocalDirectories(Organization $organization): void
  652. {
  653. // TODO: implement
  654. }
  655. protected function deleteDirectoriesV1(Organization $organization): void
  656. {
  657. // TODO: implement
  658. }
  659. protected function deleteDirectories59(Organization $organization): void
  660. {
  661. // TODO: implement
  662. }
  663. }