OrganizationFactory.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <?php
  2. namespace App\Service\Organization;
  3. use App\ApiResources\Organization\OrganizationCreationRequest;
  4. use App\ApiResources\Organization\OrganizationMemberCreationRequest;
  5. use App\Entity\Access\Access;
  6. use App\Entity\Core\AddressPostal;
  7. use App\Entity\Core\ContactPoint;
  8. use App\Entity\Education\Cycle;
  9. use App\Entity\Network\NetworkOrganization;
  10. use App\Entity\Organization\Organization;
  11. use App\Entity\Organization\OrganizationAddressPostal;
  12. use App\Entity\Organization\Parameters;
  13. use App\Entity\Organization\Settings;
  14. use App\Entity\Organization\Subdomain;
  15. use App\Entity\Person\Person;
  16. use App\Entity\Person\PersonAddressPostal;
  17. use App\Enum\Core\ContactPointTypeEnum;
  18. use App\Enum\Education\CycleEnum;
  19. use App\Enum\Organization\AddressPostalOrganizationTypeEnum;
  20. use App\Enum\Person\AddressPostalPersonTypeEnum;
  21. use App\Repository\Core\CountryRepository;
  22. use App\Repository\Network\NetworkRepository;
  23. use App\Repository\Organization\OrganizationRepository;
  24. use App\Repository\Person\PersonRepository;
  25. use App\Service\Dolibarr\DolibarrApiService;
  26. use App\Service\Typo3\BindFileService;
  27. use App\Service\Typo3\SubdomainService;
  28. use App\Service\Typo3\Typo3Service;
  29. use App\Service\Utils\DatesUtils;
  30. use Doctrine\ORM\EntityManagerInterface;
  31. use Elastica\Param;
  32. use libphonenumber\NumberParseException;
  33. use libphonenumber\PhoneNumberUtil;
  34. use Psr\Log\LoggerInterface;
  35. use Symfony\Component\String\ByteString;
  36. use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
  37. use Symfony\Contracts\Service\Attribute\Required;
  38. use Throwable;
  39. class OrganizationFactory
  40. {
  41. private LoggerInterface $logger;
  42. public function __construct(
  43. private readonly SubdomainService $subdomainService,
  44. private readonly OrganizationRepository $organizationRepository,
  45. private readonly CountryRepository $countryRepository,
  46. private readonly NetworkRepository $networkRepository,
  47. private readonly Typo3Service $typo3Service,
  48. private readonly DolibarrApiService $dolibarrApiService,
  49. private readonly EntityManagerInterface $entityManager,
  50. private readonly PersonRepository $personRepository,
  51. private readonly BindFileService $bindFileService,
  52. ) {}
  53. #[Required]
  54. /** @see https://symfony.com/doc/current/logging/channels_handlers.html#how-to-autowire-logger-channels */
  55. public function setLoggerInterface(LoggerInterface $adminLogger): void
  56. {
  57. $this->logger = $adminLogger;
  58. }
  59. /**
  60. * Créé une nouvelle organisation à partir des données contenues dans une OrganizationCreationRequest
  61. *
  62. * @param OrganizationCreationRequest $organizationCreationRequest
  63. * @return Organization
  64. * @throws TransportExceptionInterface
  65. * @throws Throwable
  66. */
  67. public function create(OrganizationCreationRequest $organizationCreationRequest): Organization
  68. {
  69. $this->logger->info(
  70. 'Start the creation of a new organization named ' . $organizationCreationRequest->getName()
  71. );
  72. $this->entityManager->beginTransaction();
  73. try {
  74. if ($this->isExistingOrganization($organizationCreationRequest)) {
  75. throw new \RuntimeException('An organization named ' . $organizationCreationRequest->getName() . ' already exists.');
  76. }
  77. $this->validateSubdomain($organizationCreationRequest->getSubdomain());
  78. $this->logger->info("Subdomain is valid and available : " . $organizationCreationRequest->getSubdomain());
  79. // Création de l'organisation
  80. $organization = $this->makeOrganization($organizationCreationRequest);
  81. $this->logger->debug(" - Organization created");
  82. // Création des Parameters
  83. $parameters = $this->makeParameters($organizationCreationRequest);
  84. $organization->setParameters($parameters);
  85. $this->logger->debug(" - Parameters created");
  86. // Création des Settings
  87. $settings = $this->makeSettings($organizationCreationRequest);
  88. $organization->setSettings($settings);
  89. $this->logger->debug(" - Settings created");
  90. // Création de l'adresse postale
  91. $organizationAddressPostal = $this->makePostalAddress($organizationCreationRequest);
  92. $organization->addOrganizationAddressPostal($organizationAddressPostal);
  93. $this->logger->debug(" - OrganizationAddressPostal created");
  94. // Création du point de contact
  95. $contactPoint = $this->makeContactPoint($organizationCreationRequest);
  96. $organization->addContactPoint($contactPoint);
  97. $this->logger->debug(" - ContactPoint created");
  98. // Rattachement au réseau
  99. $networkOrganization = $this->makeNetworkOrganization($organizationCreationRequest);
  100. $organization->addNetworkOrganization($networkOrganization);
  101. $this->logger->debug(" - NetworkOrganization created");
  102. // Créé l'admin
  103. $adminAccess = $this->makeAdminAccess($organizationCreationRequest);
  104. $organization->addAccess($adminAccess);
  105. $this->logger->debug(" - Admin access created");
  106. // Création des cycles
  107. foreach ($this->makeCycles() as $cycle) {
  108. $organization->addCycle($cycle);
  109. }
  110. $this->logger->debug(" - Cycles created");
  111. // Création du président (si renseigné)
  112. $presidentCreationRequest = $organizationCreationRequest->getPresident();
  113. if ($presidentCreationRequest !== null) {
  114. $presidentAccess = $this->makeAccess($presidentCreationRequest);
  115. $organization->addAccess($presidentAccess);
  116. $this->logger->debug(" - President access created");
  117. }
  118. // Création du directeur (si renseigné)
  119. $directorCreationRequest = $organizationCreationRequest->getDirector();
  120. if ($directorCreationRequest !== null) {
  121. $directorAccess = $this->makeAccess($directorCreationRequest);
  122. $organization->addAccess($directorAccess);
  123. $this->logger->debug(" - Director access created");
  124. }
  125. $subdomain = new Subdomain();
  126. $subdomain->setSubdomain($organizationCreationRequest->getSubdomain());
  127. $subdomain->setOrganization($organization);
  128. $subdomain->setActive(true);
  129. $this->entityManager->persist($subdomain);
  130. // <--- Pour la rétrocompatibilité avec la v1 ; pourra être supprimé lorsque la migration sera achevée
  131. $parameters = $organization->getParameters();
  132. $parameters->setSubDomain($organizationCreationRequest->getSubdomain());
  133. $parameters->setOtherWebsite('https://' . $organizationCreationRequest->getSubdomain() . '.opentalent.fr');
  134. $this->entityManager->persist($parameters);
  135. // --->
  136. // Création de la société Dolibarr
  137. $dolibarrId = $this->dolibarrApiService->createSociety($organization);
  138. $this->logger->info("New dolibarr structure created (uid : " . $dolibarrId . ")");
  139. $this->entityManager->persist($organization);
  140. $this->entityManager->flush();
  141. $this->entityManager->commit();
  142. $this->logger->debug(" - New records commited");
  143. $this->logger->info("Organization created in the DB");
  144. } catch (\Exception $e) {
  145. $this->logger->critical("An error happened, operation cancelled : " . $e);
  146. $this->entityManager->rollback();
  147. throw $e;
  148. }
  149. // Création du site typo3
  150. if ($organizationCreationRequest->getCreateWebsite()) {
  151. $response = $this->typo3Service->createSite($organization->getId());
  152. // TODO: revoir l'utilité du champs cmsId
  153. $rootPageUid = json_decode($response->getContent(), true);
  154. $organization->setCmsId($rootPageUid);
  155. $this->entityManager->persist($organization);
  156. $this->entityManager->flush();
  157. $this->logger->info("New typo3 website created (root uid : " . $rootPageUid . ")");
  158. } else {
  159. $this->logger->warning("Typo3 website creation was not required");
  160. }
  161. // Register the subdomain into the BindFile (takes up to 5min to take effect)
  162. $this->bindFileService->registerSubdomain($subdomain->getSubdomain());
  163. $this->logger->info("Subdomain registered");
  164. return $organization;
  165. }
  166. /**
  167. * Une organisation du même nom existe-t-elle déjà à la même adresse ?
  168. *
  169. * @param OrganizationCreationRequest $organizationCreationRequest
  170. * @return bool
  171. */
  172. protected function isExistingOrganization(OrganizationCreationRequest $organizationCreationRequest): bool
  173. {
  174. return $this
  175. ->organizationRepository
  176. ->count(
  177. [
  178. 'name' => $organizationCreationRequest->getName(),
  179. ]
  180. ) > 0;
  181. }
  182. /**
  183. * Vérifie la disponibilité et la validité d'un sous domaine
  184. *
  185. * @param string $subdomainValue
  186. * @return void
  187. * @throws \Exception
  188. */
  189. protected function validateSubdomain(string $subdomainValue): void
  190. {
  191. if (!$this->subdomainService->isValidSubdomain($subdomainValue)) {
  192. throw new \RuntimeException("Not a valid subdomain :" . $subdomainValue);
  193. }
  194. if ($this->subdomainService->isReservedSubdomain($subdomainValue)) {
  195. throw new \RuntimeException("This subdomain is not available :" . $subdomainValue);
  196. }
  197. if ($this->subdomainService->isRegistered($subdomainValue)) {
  198. throw new \RuntimeException("This subdomain is already registered :" . $subdomainValue);
  199. }
  200. }
  201. /**
  202. * Créé une nouvelle instance d'organisation
  203. *
  204. * @param OrganizationCreationRequest $organizationCreationRequest
  205. * @return Organization
  206. */
  207. protected function makeOrganization(OrganizationCreationRequest $organizationCreationRequest): Organization
  208. {
  209. // Création de l'organisation
  210. $organization = new Organization();
  211. $organization->setName($organizationCreationRequest->getName());
  212. $organization->setLegalStatus($organizationCreationRequest->getLegalStatus());
  213. $organization->setPrincipalType($organizationCreationRequest->getPrincipalType());
  214. $this->entityManager->persist($organization);
  215. return $organization;
  216. }
  217. /**
  218. * Create a new Parameters object from the data in an OrganizationCreationRequest
  219. *
  220. * @param OrganizationCreationRequest $organizationCreationRequest The organization creation request
  221. * @return Parameters The created Parameters object
  222. * @throws Throwable If there is an error
  223. */
  224. protected function makeParameters(OrganizationCreationRequest $organizationCreationRequest): Parameters
  225. {
  226. $parameters = new Parameters();
  227. $this->entityManager->persist($parameters);
  228. return $parameters;
  229. }
  230. /**
  231. * Creates a new instance of the Settings class based on the given OrganizationCreationRequest object.
  232. *
  233. * @param OrganizationCreationRequest $organizationCreationRequest The OrganizationCreationRequest object containing the required data.
  234. *
  235. * @return Settings The newly created instance of the Settings class.
  236. */
  237. protected function makeSettings(OrganizationCreationRequest $organizationCreationRequest): Settings
  238. {
  239. $settings = new Settings();
  240. $settings->setProduct($organizationCreationRequest->getProduct());
  241. $this->entityManager->persist($settings);
  242. return $settings;
  243. }
  244. /**
  245. * Creates a new instance of the OrganizationAddressPostal class based on the given OrganizationCreationRequest object.
  246. *
  247. * @param OrganizationCreationRequest $organizationCreationRequest The OrganizationCreationRequest object containing the required data.
  248. *
  249. * @return OrganizationAddressPostal The newly created instance of the OrganizationAddressPostal class.
  250. */
  251. protected function makePostalAddress(OrganizationCreationRequest $organizationCreationRequest): OrganizationAddressPostal
  252. {
  253. $addressPostal = new AddressPostal();
  254. $addressPostal->setStreetAddress($organizationCreationRequest->getStreetAddress1());
  255. $addressPostal->setStreetAddressSecond($organizationCreationRequest->getStreetAddress2());
  256. $addressPostal->setStreetAddressThird($organizationCreationRequest->getStreetAddress3());
  257. $addressPostal->setPostalCode($organizationCreationRequest->getPostalCode());
  258. $addressPostal->setAddressCity($organizationCreationRequest->getCity());
  259. $country = $this->countryRepository->find($organizationCreationRequest->getCountryId());
  260. $addressPostal->setAddressCountry($country);
  261. $this->entityManager->persist($addressPostal);
  262. $organizationAddressPostal = new OrganizationAddressPostal();
  263. $organizationAddressPostal->setAddressPostal($addressPostal);
  264. $organizationAddressPostal->setType(AddressPostalOrganizationTypeEnum::ADDRESS_HEAD_OFFICE);
  265. $this->entityManager->persist($organizationAddressPostal);
  266. return $organizationAddressPostal;
  267. }
  268. /**
  269. * Creates a new instance of the ContactPoint class based on the given OrganizationCreationRequest object.
  270. *
  271. * @param OrganizationCreationRequest $organizationCreationRequest The OrganizationCreationRequest object containing the required data.
  272. *
  273. * @return ContactPoint The newly created instance of the ContactPoint class.
  274. */
  275. protected function makeContactPoint(OrganizationCreationRequest $organizationCreationRequest): ContactPoint
  276. {
  277. $phoneUtil = PhoneNumberUtil::getInstance();
  278. $phoneNumber = $phoneUtil->parse($organizationCreationRequest->getPhoneNumber());
  279. $contactPoint = new ContactPoint();
  280. $contactPoint->setContactType(ContactPointTypeEnum::PRINCIPAL);
  281. $contactPoint->setEmail($organizationCreationRequest->getEmail());
  282. $contactPoint->setTelphone($phoneNumber);
  283. $this->entityManager->persist($contactPoint);
  284. return $contactPoint;
  285. }
  286. /**
  287. * Creates a new instance of the NetworkOrganization class based on the given OrganizationCreationRequest object.
  288. *
  289. * @param OrganizationCreationRequest $organizationCreationRequest The OrganizationCreationRequest object containing the required data.
  290. *
  291. * @return NetworkOrganization The newly created instance of the NetworkOrganization class.
  292. *
  293. * @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.
  294. */
  295. protected function makeNetworkOrganization(OrganizationCreationRequest $organizationCreationRequest): NetworkOrganization
  296. {
  297. $parent = $this->organizationRepository->find($organizationCreationRequest->getParentId());
  298. if (!$parent) {
  299. throw new \RuntimeException('No parent organization found for id ' . $organizationCreationRequest->getParentId());
  300. }
  301. $network = $this->networkRepository->find($organizationCreationRequest->getNetworkId());
  302. if (!$network) {
  303. throw new \RuntimeException('No network found for id ' . $organizationCreationRequest->getNetworkId());
  304. }
  305. $networkOrganization = new NetworkOrganization();
  306. $networkOrganization->setParent($parent);
  307. $networkOrganization->setNetwork($network);
  308. $networkOrganization->setStartDate(DatesUtils::new());
  309. $this->entityManager->persist($networkOrganization);
  310. return $networkOrganization;
  311. }
  312. /**
  313. * Creates a new instance of the Access class with admin access based on the given OrganizationCreationRequest object.
  314. *
  315. * @param OrganizationCreationRequest $organizationCreationRequest The OrganizationCreationRequest object containing the required data.
  316. *
  317. * @return Access The newly created instance of the Access class with admin access.
  318. */
  319. protected function makeAdminAccess(OrganizationCreationRequest $organizationCreationRequest): Access
  320. {
  321. $admin = new Person();
  322. $admin->setUsername('admin' . strtolower($organizationCreationRequest->getSubdomain()));
  323. $randomString = ByteString::fromRandom(32)->toString();
  324. $admin->setPassword($randomString);
  325. $this->entityManager->persist($admin);
  326. $adminAccess = new Access();
  327. $adminAccess->setAdminAccess(true);
  328. $adminAccess->setPerson($admin);
  329. $this->entityManager->persist($adminAccess);
  330. return $adminAccess;
  331. }
  332. /**
  333. * Creates an array of Cycle objects based on a predefined set of data.
  334. *
  335. * @return Cycle[] An array of Cycle objects.
  336. */
  337. protected function makeCycles(): array
  338. {
  339. $cyclesData = [
  340. ['Cycle initiation', 10, CycleEnum::INITIATION_CYCLE],
  341. ['Cycle 1', 20, CycleEnum::CYCLE_1],
  342. ['Cycle 2', 30, CycleEnum::CYCLE_2],
  343. ['Cycle 3', 40, CycleEnum::CYCLE_3],
  344. ['Cycle 4', 50, CycleEnum::CYCLE_4],
  345. ['Hors cycle', 60, CycleEnum::OUT_CYCLE],
  346. ];
  347. $cycles = [];
  348. foreach ($cyclesData as $cycleData) {
  349. $cycle = new Cycle();
  350. $cycle->setLabel($cycleData[0]);
  351. $cycle->setOrder($cycleData[1]);
  352. $cycle->setCycleEnum($cycleData[2]);
  353. $cycle->setIsSystem(false);
  354. $this->entityManager->persist($cycle);
  355. $cycles[] = $cycle;
  356. }
  357. return $cycles;
  358. }
  359. /**
  360. * Creates an Access object based on the given OrganizationMemberCreationRequest.
  361. *
  362. * @param int|OrganizationMemberCreationRequest $organizationMemberCreationRequest The request object containing the
  363. * necessary data for creating a
  364. * Person object, or the id of an
  365. * existing one.
  366. * @return Access The created Access object.
  367. */
  368. protected function makeAccess(int|OrganizationMemberCreationRequest $organizationMemberCreationRequest): Access
  369. {
  370. if (is_int($organizationMemberCreationRequest)) {
  371. $person = $this->personRepository->find($organizationMemberCreationRequest);
  372. } else {
  373. $person = new Person();
  374. $person->setUsername(
  375. strtolower(str_replace(' ', '-', $organizationMemberCreationRequest->getName()))
  376. );
  377. $person->setPassword(ByteString::fromRandom(32)->toString());
  378. $person->setGender($organizationMemberCreationRequest->getGender());
  379. $person->setName($organizationMemberCreationRequest->getName());
  380. $person->setGivenName($organizationMemberCreationRequest->getGivenName());
  381. $personPostalAddress = $this->makeAccessPostalAddress($organizationMemberCreationRequest);
  382. $person->addPersonAddressPostal($personPostalAddress);
  383. $contactPoint = $this->makeAccessContactPoint($organizationMemberCreationRequest);
  384. $person->addContactPoint($contactPoint);
  385. $this->entityManager->persist($person);
  386. }
  387. $access = new Access();
  388. $access->setPerson($person);
  389. $this->entityManager->persist($access);
  390. return $access;
  391. }
  392. /**
  393. * Creates a PersonAddressPostal object based on the given OrganizationMemberCreationRequest.
  394. *
  395. * @param OrganizationMemberCreationRequest $organizationMemberCreationRequest The request object containing the
  396. * necessary data for creating a
  397. * PersonAddressPostal object.
  398. * @return PersonAddressPostal The created PersonAddressPostal object.
  399. */
  400. protected function makeAccessPostalAddress(OrganizationMemberCreationRequest $organizationMemberCreationRequest): PersonAddressPostal
  401. {
  402. $addressPostal = new AddressPostal();
  403. $addressPostal->setStreetAddress($organizationMemberCreationRequest->getStreetAddress1());
  404. $addressPostal->setStreetAddressSecond($organizationMemberCreationRequest->getStreetAddress2());
  405. $addressPostal->setStreetAddressThird($organizationMemberCreationRequest->getStreetAddress3());
  406. $addressPostal->setPostalCode($organizationMemberCreationRequest->getPostalCode());
  407. $addressPostal->setAddressCity($organizationMemberCreationRequest->getCity());
  408. $country = $this->countryRepository->find($organizationMemberCreationRequest->getCountryId());
  409. $addressPostal->setAddressCountry($country);
  410. $this->entityManager->persist($addressPostal);
  411. $personAddressPostal = new PersonAddressPostal();
  412. $personAddressPostal->setAddressPostal($addressPostal);
  413. $personAddressPostal->setType(AddressPostalPersonTypeEnum::ADDRESS_PRINCIPAL);
  414. $this->entityManager->persist($personAddressPostal);
  415. return $personAddressPostal;
  416. }
  417. /**
  418. * Creates a new instance of the ContactPoint class based on the given OrganizationCreationRequest object.
  419. *
  420. * @param OrganizationMemberCreationRequest $organizationMemberCreationRequest The OrganizationMemberCreationRequest object containing the required data.
  421. *
  422. * @return ContactPoint The newly created instance of the ContactPoint class.
  423. * @throws NumberParseException
  424. */
  425. protected function makeAccessContactPoint(OrganizationMemberCreationRequest $organizationMemberCreationRequest): ContactPoint
  426. {
  427. $phoneUtil = PhoneNumberUtil::getInstance();
  428. $phoneNumber = $phoneUtil->parse($organizationMemberCreationRequest->getPhone());
  429. $contactPoint = new ContactPoint();
  430. $contactPoint->setContactType(ContactPointTypeEnum::PRINCIPAL);
  431. $contactPoint->setEmail($organizationMemberCreationRequest->getEmail());
  432. $contactPoint->setTelphone($phoneNumber);
  433. if ($organizationMemberCreationRequest->getMobile() !== null) {
  434. $mobileNumber = $phoneUtil->parse($organizationMemberCreationRequest->getMobile());
  435. $contactPoint->setMobilPhone($mobileNumber);
  436. }
  437. $this->entityManager->persist($contactPoint);
  438. return $contactPoint;
  439. }
  440. }