src/Service/ModulrSyncService.php line 213

Open in your IDE?
  1. <?php
  2. namespace App\Service;
  3. use App\Entity\Claim;
  4. use App\Entity\Customer;
  5. use App\Entity\Firm;
  6. use App\Entity\Guarantee;
  7. use App\Entity\Policy;
  8. use App\Entity\Vehicle;
  9. use App\Entity\VehicleBrand;
  10. use App\Entity\VehicleEnergy;
  11. use App\Entity\VehicleEngine;
  12. use App\Entity\VehicleModel;
  13. use App\Entity\VehicleType;
  14. use App\Service\Modulr\ClaimSyncService;
  15. use Doctrine\ORM\EntityManagerInterface;
  16. class ModulrSyncService
  17. {
  18. /** @var ModulrApiService */
  19. private $modulr;
  20. /** @var EntityManagerInterface */
  21. private $em;
  22. private $customerRepository;
  23. private $firmRepository;
  24. private $policyRepository;
  25. private $vehicleRepository;
  26. private $vehicleTypeRepository;
  27. private $vehicleBrandRepository;
  28. private $vehicleModelRepository;
  29. public function __construct(
  30. ModulrApiService $modulr,
  31. private readonly ClaimSyncService $claimSync,
  32. EntityManagerInterface $em
  33. ) {
  34. $this->modulr = $modulr;
  35. $this->em = $em;
  36. $this->customerRepository = $em->getRepository(Customer::class);
  37. $this->firmRepository = $em->getRepository(Firm::class);
  38. $this->policyRepository = $em->getRepository(Policy::class);
  39. $this->vehicleRepository = $em->getRepository(Vehicle::class);
  40. $this->vehicleTypeRepository = $em->getRepository(VehicleType::class);
  41. $this->vehicleBrandRepository = $em->getRepository(VehicleBrand::class);
  42. $this->vehicleModelRepository = $em->getRepository(VehicleModel::class);
  43. }
  44. /**
  45. * Crée ou met à jour une Firm locale à partir d'une firm Modulr.
  46. *
  47. * @param int $firmId
  48. *
  49. * @return Firm
  50. */
  51. public function syncFirmFromModulr($firmId)
  52. {
  53. $response = $this->modulr->getFirm((int) $firmId);
  54. if (
  55. !$response
  56. || !isset($response['status'])
  57. || $response['status'] !== 'success'
  58. || !isset($response['data'])
  59. || !is_array($response['data'])
  60. ) {
  61. throw new \RuntimeException('Impossible de récupérer la firm Modulr #' . $firmId);
  62. }
  63. $data = $response['data'];
  64. // On cherche par modulrId (string)
  65. $firm = $this->firmRepository->findOneBy(array(
  66. 'modulrId' => (string) $data['firm_id'],
  67. ));
  68. if (!$firm) {
  69. $firm = new Firm();
  70. $firm->setModulrId((string) $data['firm_id']);
  71. }
  72. // Mapping minimal : name = firm_name
  73. if (isset($data['firm_name'])) {
  74. $firm->setName($data['firm_name']);
  75. }
  76. // Les autres champs (siret, naf, legalNotice, VATNumber...) ne sont pas dans l’API /firms
  77. // -> on les laisse tels quels. Tu pourras les hydrater par un autre flux si besoin.
  78. $this->em->persist($firm);
  79. $this->em->flush();
  80. return $firm;
  81. }
  82. /**
  83. * Crée ou met à jour un Customer local à partir d'un client Modulr.
  84. *
  85. * @param int $clientId
  86. *
  87. * @return Customer
  88. */
  89. public function syncCustomerFromModulr($clientId)
  90. {
  91. $response = $this->modulr->getClient((int) $clientId);
  92. if (!$response || $response['status'] !== 'success' || !is_array($response['data'])) {
  93. throw new \RuntimeException('Impossible de récupérer le client Modulr #' . $clientId);
  94. }
  95. $c = $response['data'];
  96. $customer = $this->customerRepository->findOneBy(array(
  97. 'modulrId' => (string) $c['client_id'],
  98. ));
  99. if (!$customer) {
  100. $customer = new Customer();
  101. $customer->setModulrId((string) $c['client_id']);
  102. }
  103. $name = !empty($c['company']) ? $c['company'] :
  104. (!empty($c['business_name']) ? $c['business_name'] : 'Client ' . $c['client_id']);
  105. $customer->setName($name);
  106. $number = !empty($c['reference']) ? $c['reference'] : (string) $c['client_id'];
  107. $customer->setNumber($number);
  108. $customer->setAccountantNumber($number);
  109. $customer->setState(!empty($c['client_status']) ? $c['client_status'] : 'imported');
  110. // autres champs optionnels (legalForm, revenues, siret, ...)
  111. $this->em->persist($customer);
  112. $this->em->flush();
  113. return $customer;
  114. }
  115. /**
  116. * Crée ou met à jour une Policy locale à partir d'une police Modulr.
  117. *
  118. * @param int $policyId
  119. *
  120. * @return Policy
  121. */
  122. public function syncPolicyFromModulr($policyId)
  123. {
  124. $response = $this->modulr->getPolicy((int) $policyId);
  125. if (!$response || $response['status'] !== 'success' || !is_array($response['data'])) {
  126. throw new \RuntimeException('Impossible de récupérer la police Modulr #' . $policyId);
  127. }
  128. $data = $response['data'];
  129. $policy = $this->policyRepository->findOneBy(array(
  130. 'modulrId' => (string) $data['policy_id'],
  131. ));
  132. if (!$policy) {
  133. $policy = new Policy();
  134. $policy->setModulrId((string) $data['policy_id']);
  135. }
  136. // Firm et Customer via les méthodes ci-dessus
  137. $firm = $this->syncFirmFromModulr($data['firm_id']);
  138. $customer = $this->syncCustomerFromModulr($data['client_id']);
  139. $policy->setFirm($firm);
  140. $policy->setCustomer($customer);
  141. // number : ref ou policy_id
  142. $number = !empty($data['ref']) ? $data['ref'] : (string) $data['policy_id'];
  143. $policy->setNumber($number);
  144. $policy->setProduct(isset($data['product_type_id']) ? (string) $data['product_type_id'] : '');
  145. $policy->setCompany($this->modulr->getCompany($data['company_id'])['data']['name']);
  146. if (!empty($data['first_effect_date'])) {
  147. $policy->setEffectiveDate(new \DateTime($data['first_effect_date']));
  148. } else {
  149. $policy->setEffectiveDate(new \DateTime());
  150. }
  151. if (!empty($data['creation_date'])) {
  152. $policy->setCreatedAt(new \DateTime($data['creation_date']));
  153. }
  154. if (!empty($data['expiration_date'])) {
  155. $policy->setDeadline(new \DateTime($data['expiration_date']));
  156. } else {
  157. $policy->setDeadline(clone $policy->getEffectiveDate());
  158. }
  159. $policy->setNoticePeriod(0);
  160. $policy->setLastSync(new \DateTime('now'));
  161. $this->em->persist($policy);
  162. $this->em->flush();
  163. return $policy;
  164. }
  165. /**
  166. * Synchronise un véhicule Modulr vers l'entité Vehicle locale.
  167. *
  168. * @param int $modulrId ID du véhicule côté Modulr (vehicles/{id})
  169. * @param ?Policy $policy Police à rattacher au véhicule (obligatoire dans ton entity)
  170. *
  171. * @return Vehicle|null L'entité synchronisée, ou null en cas d'erreur
  172. */
  173. public function syncVehicle(int $modulrId, Policy $policy = null): ?Vehicle
  174. {
  175. $response = $this->modulr->getVehicle($modulrId);
  176. if (!isset($response['status']) || $response['status'] !== 'success' || empty($response['data'])) {
  177. // ici tu peux logger proprement
  178. // $this->logger?->error('Erreur lors de la récupération du véhicule Modulr', ['id' => $modulrId, 'response' => $response]);
  179. return null;
  180. }
  181. $data = $response['data'];
  182. // On cherche un véhicule existant par modulrId
  183. $vehicle = $this->vehicleRepository->findOneBy([
  184. 'modulrId' => $data['vehicle_id'] ?? $modulrId,
  185. ]);
  186. $isNew = false;
  187. if (!$vehicle) {
  188. $vehicle = new Vehicle();
  189. $isNew = true;
  190. // Clé Modulr
  191. $vehicle->setModulrId((string) ($data['vehicle_id'] ?? $modulrId));
  192. // Policy obligatoire (nullable=false)
  193. $vehicle->setPolicy($policy);
  194. // Date d’effet par défaut (à adapter si tu as un champ côté API)
  195. $vehicle->setEffectiveDate(new \DateTimeImmutable('now'));
  196. }
  197. if (!empty($data['vehicle_type_id'])) {
  198. $type = $this->syncVehicleType((int) $data['vehicle_type_id']);
  199. $vehicle->setType($type);
  200. }
  201. // Matriculation
  202. if (!empty($data['registration'])) {
  203. $vehicle->setMatriculation($data['registration']);
  204. }
  205. //Marque
  206. if (!empty($data['brand'])) {
  207. $brand = $this->vehicleBrandRepository->findOneBy(['name' => strtoupper($data['brand'])]);
  208. if (!$brand) {
  209. $brand = new VehicleBrand();
  210. }
  211. $brand->setName(strtoupper($data['brand']));
  212. $brand->setDeleted(false);
  213. $this->em->persist($brand);
  214. $vehicle->setBrand($brand);
  215. }
  216. //Modèle
  217. if (!empty($data['model'])) {
  218. $model = $this->vehicleModelRepository->findOneBy(['name' => strtoupper($data['model'])]);
  219. if (!$model) {
  220. $model = new VehicleModel();
  221. }
  222. $model->setName(strtoupper($data['model']));
  223. $model->setDeleted(false);
  224. if (!empty($data['brand']))
  225. $model->setBrand($brand);
  226. $this->em->persist($model);
  227. $vehicle->setModel($model);
  228. }
  229. //Energie
  230. if (!empty($data['power'])) {
  231. $power = $this->em->getRepository(VehicleEnergy::class)->findOneBy(['name' => $data['power']]);
  232. if (!$power) {
  233. $power = new VehicleEnergy();
  234. }
  235. $power->setName(strtoupper($data['power']));
  236. $power->setDeleted(false);
  237. $this->em->persist($power);
  238. $vehicle->setEnergy($power);
  239. }
  240. //Engine
  241. if (!empty($data['fuel'])) {
  242. $fuel = $this->em->getRepository(VehicleEngine::class)->findOneBy(['name' => $data['fuel']]);
  243. if (!$fuel) {
  244. $fuel = new VehicleEngine();
  245. }
  246. $fuel->setName(strtoupper($data['fuel']));
  247. $fuel->setDeleted(false);
  248. $this->em->persist($fuel);
  249. $vehicle->setEngine($fuel);
  250. }
  251. // Label simple : "Brand Model"
  252. $labelParts = [];
  253. if (!empty($data['brand'])) {
  254. $labelParts[] = $data['brand'];
  255. }
  256. if (!empty($data['model'])) {
  257. $labelParts[] = $data['model'];
  258. }
  259. $vehicle->setLabel($labelParts ? implode(' ', $labelParts) : null);
  260. // Valeur déclarée
  261. if (array_key_exists('value', $data)) {
  262. $vehicle->setDeclaredValue($data['value']);
  263. }
  264. // Société de leasing / propriétaire
  265. if (!empty($data['owner_name'])) {
  266. $vehicle->setLeasingCompany($data['owner_name']);
  267. }
  268. // Date de mise en circulation (registration_date)
  269. if (!empty($data['registration_date'])) {
  270. $vehicle->setFirstRelease($this->createDateFromString($data['registration_date']));
  271. }
  272. // Date de mise en service (commissioning_date)
  273. if (!empty($data['commissioning_date'])) {
  274. $vehicle->setReleaseDate($this->createDateFromString($data['commissioning_date']));
  275. }
  276. // Exemple : mapping du type de véhicule si tu as un champ modulrId là-dessus
  277. // if (!empty($data['vehicle_type_id'])) {
  278. // $type = $this->vehicleTypeRepository->findOneBy(['modulrId' => $data['vehicle_type_id']]);
  279. // if ($type) {
  280. // $vehicle->setType($type);
  281. // }
  282. // }
  283. // Exemple : mapping de l’énergie sur un label (fuel)
  284. // if (!empty($data['fuel'])) {
  285. // $energy = $this->vehicleEnergyRepository->findOneBy(['label' => $data['fuel']]);
  286. // if ($energy) {
  287. // $vehicle->setEnergy($energy);
  288. // }
  289. // }
  290. // Dernière sync
  291. $vehicle->setLastSync(new \DateTime('now'));
  292. $this->em->persist($vehicle);
  293. $this->em->flush();
  294. return $vehicle;
  295. }
  296. /**
  297. * Transforme une chaîne "Y-m-d H:i:s" ou "Y-m-d" en \DateTimeImmutable (ou null si vide/invalide).
  298. */
  299. private function createDateFromString(?string $value): ?\DateTimeImmutable
  300. {
  301. if (!$value) {
  302. return null;
  303. }
  304. try {
  305. // l'API renvoie visiblement "YYYY-MM-DD HH:MM:SS"
  306. return new \DateTimeImmutable($value);
  307. } catch (\Exception $e) {
  308. return null;
  309. }
  310. }
  311. /**
  312. * Synchronise tous les véhicules "fleet_vehicles" d'une police Modulr donnée.
  313. *
  314. * @param int $policyModulrId ID de la police côté Modulr (champ policy_id dans beneficiaries)
  315. * @param Policy $policy Entité Policy locale à rattacher aux véhicules
  316. *
  317. * @return Vehicle[] Tableau des véhicules synchronisés
  318. */
  319. public function syncFleet(int $policyModulrId, Policy $policy, int $perPage = 100): array
  320. {
  321. $page = 1;
  322. $vehicles = [];
  323. $totalPages = 1;
  324. do {
  325. $response = $this->modulr->searchBeneficiaries(
  326. $page,
  327. $perPage,
  328. [
  329. 'policy_id' => ['equal' => $policyModulrId],
  330. 'type' => ['equal' => 'fleet_vehicles'],
  331. ]
  332. );
  333. if (!isset($response['status']) || $response['status'] !== 'success' || empty($response['data'])) {
  334. // logger si besoin
  335. // $this->logger?->error('Erreur searchBeneficiaries', ['policyModulrId' => $policyModulrId, 'response' => $response]);
  336. break;
  337. }
  338. $data = $response['data'];
  339. $beneficiaries = $data['beneficiaries'] ?? [];
  340. $currentPage = $data['current_page'] ?? $page;
  341. $totalPages = $data['number_of_pages'] ?? 1;
  342. foreach ($beneficiaries as $beneficiary) {
  343. // On s'assure qu'on est bien sur une flotte véhicule + qu'on a un vehicle_id
  344. if (
  345. ($beneficiary['type'] ?? null) !== 'fleet_vehicles' ||
  346. empty($beneficiary['vehicle_id'])
  347. ) {
  348. continue;
  349. }
  350. $vehicleId = (int) $beneficiary['vehicle_id'];
  351. // Synchronisation du véhicule individuel
  352. $vehicle = $this->syncVehicle($vehicleId, $policy);
  353. if ($vehicle instanceof Vehicle) {
  354. $vehicles[] = $vehicle;
  355. // Si tu veux enrichir avec la plaque qui vient des beneficiaries
  356. if (!empty($beneficiary['vehicle_registration'])) {
  357. $vehicle->setMatriculation($beneficiary['vehicle_registration']);
  358. }
  359. }
  360. }
  361. $page++;
  362. } while ($currentPage < $totalPages);
  363. return $vehicles;
  364. }
  365. /**
  366. * Synchronise un type de véhicule Modulr vers VehicleType local.
  367. *
  368. * @param int $modulrTypeId ID du type côté Modulr (vehicle_type_id)
  369. *
  370. * @return VehicleType
  371. */
  372. public function syncVehicleType(int $modulrTypeId): VehicleType
  373. {
  374. $response = $this->modulr->getVehicleType($modulrTypeId);
  375. if (!$response || $response['status'] !== 'success' || empty($response['data'])) {
  376. throw new \RuntimeException(sprintf('Impossible de récupérer le type de véhicule Modulr #%d', $modulrTypeId));
  377. }
  378. $data = $response['data'];
  379. // On cherche d'abord par modulrId
  380. $vehicleType = $this->vehicleTypeRepository->findOneBy([
  381. 'modulrId' => (int) $data['vehicle_type_id'],
  382. ]);
  383. if (!$vehicleType) {
  384. $vehicleType = $this->vehicleTypeRepository->findOneBy([
  385. 'name' => $data['name'],
  386. ]);
  387. }
  388. if (!$vehicleType) {
  389. $vehicleType = new VehicleType();
  390. $vehicleType->setModulrId((int) $data['vehicle_type_id']);
  391. }
  392. // Mise à jour des champs de base
  393. $vehicleType->setName($data['name'] ?? ''); // "vp", "engin", etc.
  394. $vehicleType->setDeleted(false); // la synchro le réactive au besoin
  395. $this->em->persist($vehicleType);
  396. $this->em->flush();
  397. return $vehicleType;
  398. }
  399. /**
  400. * Synchronise tous les sinistres "claims" d'une police Modulr donnée.
  401. *
  402. * @param int $policyModulrId ID de la police côté Modulr (champ policy_id dans beneficiaries)
  403. * @param Policy $policy Entité Policy locale à rattacher aux véhicules
  404. *
  405. * @return Claim[] Tableau des sinistres synchronisés
  406. */
  407. public function syncClaims(int $policyModulrId, Policy $policy, int $perPage = 100): array
  408. {
  409. $page = 1;
  410. $claims = [];
  411. $totalPages = 1;
  412. do {
  413. $response = $this->modulr->searchClaims(
  414. $page,
  415. $perPage,
  416. [
  417. 'policy_id' => ['equal' => $policyModulrId]
  418. ]
  419. );
  420. if (!isset($response['status']) || $response['status'] !== 'success' || empty($response['data'])) {
  421. // logger si besoin
  422. // $this->logger?->error('Erreur searchBeneficiaries', ['policyModulrId' => $policyModulrId, 'response' => $response]);
  423. break;
  424. }
  425. $data = $response['data'];
  426. $claims = $data['claims'] ?? [];
  427. $currentPage = $data['current_page'] ?? $page;
  428. $totalPages = $data['number_of_pages'] ?? 1;
  429. foreach ($claims as $claim) {
  430. $claimId = (int) $claim['claim_id'];
  431. // Synchronisation du véhicule individuel
  432. $claim = $this->claimSync->syncFromModulr($claimId);
  433. }
  434. $page++;
  435. } while ($currentPage < $totalPages);
  436. return $claims;
  437. }
  438. public function syncGuarantee($data): ?Guarantee
  439. {
  440. // Appel API
  441. //$response = $this->modulr->getGuarantee($modulrId);
  442. /*if (!\is_array($response) || ($response['status'] ?? null) !== 'success') {
  443. // ici tu peux logger l'erreur si besoin
  444. return null;
  445. }*/
  446. /*$data = $response['data'] ?? null;
  447. if (!$data) {
  448. return null;
  449. }*/
  450. // On essaie de retrouver un sinistre existant
  451. $guarantee = $this->em->getRepository(Guarantee::class)->findOneBy([
  452. 'modulrId' => $data['guarantee_id']
  453. ]);
  454. if (!$guarantee) {
  455. $guarantee = new Guarantee();
  456. $guarantee->setModulrId($data['guarantee_id']);
  457. $guarantee->setDeleted(false);
  458. $this->em->persist($guarantee);
  459. }
  460. $guarantee->setName($data['name']);
  461. $guarantee->setLastSync(new \DateTime('now'));
  462. $this->em->flush();
  463. return $guarantee;
  464. }
  465. /**
  466. * Synchronise toutes les "garanties".
  467. *
  468. *
  469. * @return Guarantee[] Tableau des garanties synchronisés
  470. */
  471. public function syncGuarantees(int $perPage = 100): array
  472. {
  473. $page = 1;
  474. $guarantees = [];
  475. $totalPages = 1;
  476. do {
  477. $response = $this->modulr->searchGuarantees(
  478. $page,
  479. $perPage
  480. );
  481. if (!isset($response['status']) || $response['status'] !== 'success' || empty($response['data'])) {
  482. // logger si besoin
  483. // $this->logger?->error('Erreur searchBeneficiaries', ['policyModulrId' => $policyModulrId, 'response' => $response]);
  484. break;
  485. }
  486. $data = $response['data'];
  487. $guarantees = $data['guarantees'] ?? [];
  488. $currentPage = $data['current_page'] ?? $page;
  489. $totalPages = $data['number_of_pages'] ?? 1;
  490. foreach ($guarantees as $guarantee) {
  491. $guaranteeId = (int) $guarantee['guarantee_id'];
  492. // Synchronisation du véhicule individuel
  493. $guarantee = $this->syncGuarantee($guarantee);
  494. }
  495. $page++;
  496. } while ($currentPage < $totalPages);
  497. return $guarantees;
  498. }
  499. }