src/Controller/ApiDaController.php line 662

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Bailleur;
  4. use App\Entity\Catalogue;
  5. use App\Entity\CentreCout;
  6. use App\Entity\DemAchatTemp;
  7. use App\Entity\NumDA;
  8. use App\Entity\RapportageDA;
  9. use App\Entity\demAchat;
  10. use App\Service\ContratCadreService;
  11. use App\Service\TranslationWriter;
  12. use Doctrine\ORM\EntityManagerInterface;
  13. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  14. use Symfony\Component\HttpFoundation\JsonResponse;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use Symfony\Component\Routing\Annotation\Route;
  17. use Symfony\Component\Security\Core\Security;
  18. use Symfony\Component\HttpFoundation\Response;
  19. /**
  20. * Contrôleur API JSON pour le mode offline des Demandes d'Achat.
  21. *
  22. * Ces endpoints sont des versions JSON des routes existantes.
  23. * Ils sont appelés par da-offline.js en mode online,
  24. * et par le Service Worker lors de la synchronisation offline.
  25. *
  26. * Routes à ajouter dans config/routes.yaml (ou annotations) :
  27. * api_da_ajouter_temp : POST /api/da/ajouter-temp
  28. * api_da_ajout_design : POST /api/da/ajout-design-temp
  29. * api_da_envoyer : POST /api/da/envoyer
  30. * api_da_import_excel : POST /api/da/import-excel
  31. * api_da_liste_temp : GET /api/da/liste-temp/{num}
  32. */
  33. class ApiDaController extends AbstractController
  34. {
  35. public function __construct(
  36. private EntityManagerInterface $em,
  37. private Security $security,
  38. private ContratCadreService $contratCadreService,
  39. private TranslationWriter $translationWriter
  40. ) {}
  41. // =========================================================
  42. // POST /api/da/ajouter-temp
  43. // Équivalent JSON de ajouter_achat_temp
  44. // Crée le premier article du brouillon (nouveau numDemandeAchat)
  45. // =========================================================
  46. public function apiAjouterTemp(Request $request): JsonResponse
  47. {
  48. $user = $this->security->getUser();
  49. if (!$user) {
  50. return new JsonResponse(['success' => false, 'message' => 'Non authentifié'], 401);
  51. }
  52. $data = $this->decodeRequest($request);
  53. // Champs obligatoires
  54. $idCentreCout = $data['centreCout'] ?? null;
  55. $idBailleur = $data['bailleur'] ?? null;
  56. $quantDemand = $data['quantDemand'] ?? null;
  57. $prixUnit = $data['prixUnit'] ?? null;
  58. $obje = $data['obje'] ?? '';
  59. $delais = $data['delais'] ?? null;
  60. $region = $data['region'] ?? null;
  61. $descrip = $data['descrip'] ?? null;
  62. $idDesignation = $data['iddesignrech'] ?? null;
  63. if (!$idCentreCout || !$idBailleur) {
  64. return new JsonResponse(['success' => false, 'message' => 'Centre de coût et projet obligatoires'], 400);
  65. }
  66. // Résolution des entités liées
  67. $cecout = $this->em->getRepository(CentreCout::class)->find($idCentreCout);
  68. $nomprojet = $this->em->getRepository(Bailleur::class)->find($idBailleur);
  69. if (!$cecout || !$nomprojet) {
  70. return new JsonResponse(['success' => false, 'message' => 'Centre de coût ou projet introuvable'], 404);
  71. }
  72. $designrech = $idDesignation
  73. ? $this->em->getRepository(Catalogue::class)->findOneByDesignation($idDesignation)
  74. : null;
  75. // Calcul du prochain numDemandeAchat
  76. $num = $this->getNextNumDemande();
  77. // Conversion région
  78. $regionLabel = $this->convertRegion($region);
  79. // Création de l'entité brouillon
  80. $demAch = new DemAchatTemp();
  81. $demAch->setCentreCout($cecout->getLibele());
  82. $demAch->setCostcenter($cecout);
  83. $demAch->setNombaille($nomprojet);
  84. $demAch->setNomProjet($nomprojet->getLibele());
  85. $demAch->setQuantDemande((int)$quantDemand);
  86. $demAch->setObjet($obje);
  87. $demAch->setRegion($regionLabel);
  88. $demAch->setDescription($descrip);
  89. $demAch->setDelais($delais);
  90. $demAch->setDateSoumis(new \DateTime());
  91. $demAch->setUser($user);
  92. $demAch->setNumDemandeAchat($num);
  93. if ($designrech) {
  94. $demAch->setDesignation($designrech->getDesignation());
  95. $demAch->setCatalogue($designrech);
  96. $demAch->setPrixU((float)$prixUnit);
  97. } else {
  98. $demAch->setDesignation('Article non trouvé dans le catalogue');
  99. }
  100. $this->em->persist($demAch);
  101. $this->em->flush();
  102. return new JsonResponse([
  103. 'success' => true,
  104. 'message' => 'Article ajouté avec succès',
  105. 'numDemandeAchat' => $num,
  106. 'id' => $demAch->getId(),
  107. ]);
  108. }
  109. // =========================================================
  110. // POST /api/da/ajout-design-temp
  111. // Équivalent JSON de ajout_design_da_temp
  112. // Ajoute un article supplémentaire à un brouillon existant
  113. // =========================================================
  114. public function apiAjoutDesignTemp(Request $request): JsonResponse
  115. {
  116. $user = $this->security->getUser();
  117. if (!$user) {
  118. return new JsonResponse(['success' => false, 'message' => 'Non authentifié'], 401);
  119. }
  120. $data = $this->decodeRequest($request);
  121. $idBailleur = $data['bailleur'] ?? null;
  122. $idCentreCout = $data['centreCout'] ?? null;
  123. $quantDemand = $data['quantDemand'] ?? null;
  124. $prixUnit = $data['prixUnit'] ?? null;
  125. $descrip = $data['descrip'] ?? null;
  126. $idDesignation = $data['iddesignrech'] ?? null;
  127. // Récupération du dernier brouillon de cet utilisateur
  128. $demtempuses = $this->em->getRepository(DemAchatTemp::class)->findByUser($user);
  129. $demtemp = end($demtempuses);
  130. if (!$demtemp) {
  131. return new JsonResponse(['success' => false, 'message' => 'Aucun brouillon en cours'], 404);
  132. }
  133. $nomprojet = $this->em->getRepository(Bailleur::class)->find($idBailleur);
  134. $centrecout = $this->em->getRepository(CentreCout::class)->find($idCentreCout);
  135. $designrech = $idDesignation
  136. ? $this->em->getRepository(Catalogue::class)->findOneByDesignation($idDesignation)
  137. : null;
  138. $num = $demtemp->getNumDemandeAchat();
  139. $demAch = new DemAchatTemp();
  140. $demAch->setCentreCout($centrecout ? $centrecout->getLibele() : '');
  141. $demAch->setCostcenter($centrecout);
  142. $demAch->setNomProjet($nomprojet ? $nomprojet->getLibele() : '');
  143. $demAch->setNombaille($nomprojet);
  144. $demAch->setRegion($demtemp->getRegion());
  145. $demAch->setQuantDemande((int)$quantDemand);
  146. $demAch->setObjet($demtemp->getObjet());
  147. $demAch->setDescription($descrip);
  148. $demAch->setDelais($demtemp->getDelais());
  149. $demAch->setDateSoumis(new \DateTime());
  150. $demAch->setUser($user);
  151. $demAch->setNumDemandeAchat($num);
  152. $demAch->setNumOrdre(1);
  153. if ($designrech) {
  154. $demAch->setDesignation($designrech->getDesignation());
  155. $demAch->setCatalogue($designrech);
  156. $demAch->setPrixU((float)$prixUnit);
  157. } else {
  158. $demAch->setDesignation('Article non trouvé dans le catalogue');
  159. }
  160. $this->em->persist($demAch);
  161. $this->em->flush();
  162. return new JsonResponse([
  163. 'success' => true,
  164. 'message' => 'Article ajouté avec succès',
  165. 'numDemandeAchat' => $num,
  166. 'id' => $demAch->getId(),
  167. ]);
  168. }
  169. // =========================================================
  170. // POST /api/da/envoyer
  171. // Équivalent JSON de daenvoyer
  172. // Convertit les DemAchatTemp en demAchat définitifs
  173. // =========================================================
  174. public function apiDaEnvoyer(Request $request): JsonResponse
  175. {
  176. $user = $this->security->getUser();
  177. if (!$user) {
  178. return new JsonResponse(['success' => false, 'message' => 'Non authentifié'], 401);
  179. }
  180. $data = $this->decodeRequest($request);
  181. $numero = $data['num'] ?? null;
  182. if (!$numero) {
  183. return new JsonResponse(['success' => false, 'message' => 'Numéro de brouillon manquant'], 400);
  184. }
  185. $datemps = $this->em->getRepository(DemAchatTemp::class)->findDemandeAchatPerso((int)$numero);
  186. if (empty($datemps)) {
  187. return new JsonResponse(['success' => false, 'message' => 'Aucun brouillon trouvé pour ce numéro'], 404);
  188. }
  189. $numda = $this->em->getRepository(NumDA::class)->find(1);
  190. $num = $numda->getNum() + 1;
  191. $dat = date('Y-m-d H:i:s');
  192. $achat = null;
  193. foreach ($datemps as $datemp) {
  194. $achat = new demAchat();
  195. $raport = new RapportageDA();
  196. $achat->setDesignation($datemp->getDesignation());
  197. $achat->setPrixU($datemp->getPrixU());
  198. $achat->setCentreCout($datemp->getCentreCout());
  199. $achat->setNomProjet($datemp->getNomProjet());
  200. $achat->setNombaille($datemp->getNombaille());
  201. $achat->setCostcenter($datemp->getCostcenter());
  202. $achat->setCatalogue($datemp->getCatalogue());
  203. $achat->setObjet($datemp->getObjet());
  204. $achat->setRegion($datemp->getRegion());
  205. $achat->setDescription($datemp->getDescription());
  206. $achat->setUser($datemp->getUser());
  207. $achat->setQuantDemande($datemp->getQuantDemande());
  208. $achat->setResteACommander($datemp->getQuantDemande());
  209. $achat->setDelais($datemp->getDelais());
  210. $achat->setNumOrdre(1);
  211. $achat->setDateSoumis(new \DateTime($dat));
  212. // Gestion contrat-cadre (même logique que daenvoyer existant)
  213. $montantLigne = (float)$datemp->getPrixU() * (float)$datemp->getQuantDemande();
  214. $catalogue = $datemp->getCatalogue();
  215. $categorieCatalogue = $catalogue ? $catalogue->getCategorycatalogue() : null;
  216. $contratCadre = null;
  217. if ($categorieCatalogue) {
  218. foreach ($categorieCatalogue->getCategoriesMarche() as $categorieMarche) {
  219. $contrats = $this->contratCadreService->listerDisponiblesPourDA(
  220. $categorieMarche->getId(),
  221. $montantLigne
  222. );
  223. if (!empty($contrats)) {
  224. $contratCadre = $contrats[0];
  225. break;
  226. }
  227. }
  228. }
  229. if ($contratCadre) {
  230. $achat->setContratCadre($contratCadre);
  231. $achat->setStatutContratCadree(demAchat::CC_SUGGERE);
  232. $contratCadre->setMontantReserve(
  233. (string)((float)$contratCadre->getMontantReserve() + $montantLigne)
  234. );
  235. $this->em->persist($contratCadre);
  236. }
  237. $numda->setNum($num);
  238. $achat->setNumDemandeAchat($num);
  239. $this->em->persist($achat);
  240. $this->em->remove($datemp);
  241. $raport->setDateSoumis(new \DateTime($dat));
  242. $raport->setDemAchat($achat);
  243. $this->em->persist($raport);
  244. $this->em->flush();
  245. // Traductions
  246. if (!empty($datemp->getDescription()) && trim($datemp->getDescription()) !== '') {
  247. $this->translationWriter->create('demAchat', $achat->getId(), 'description', $achat->getDescription());
  248. }
  249. }
  250. if ($achat) {
  251. $this->em->refresh($achat);
  252. $this->translationWriter->create('demAchat', $achat->getId(), 'objet', $achat->getObjet());
  253. $this->em->flush();
  254. }
  255. return new JsonResponse([
  256. 'success' => true,
  257. 'message' => 'DA créée avec succès',
  258. 'numDemandeAchat' => $num,
  259. ]);
  260. }
  261. // =========================================================
  262. // POST /api/da/import-excel
  263. // Traitement du fichier Excel en mode offline
  264. // Le fichier est envoyé en base64 depuis IndexedDB
  265. // =========================================================
  266. public function apiImportExcel(Request $request): JsonResponse
  267. {
  268. $user = $this->security->getUser();
  269. if (!$user) {
  270. return new JsonResponse(['success' => false, 'message' => 'Non authentifié'], 401);
  271. }
  272. // Cas 1 : envoi normal (formulaire online)
  273. $uploadedFile = $request->files->get('fiche');
  274. // Cas 2 : envoi offline (base64 depuis IndexedDB via Service Worker)
  275. if (!$uploadedFile) {
  276. $data = json_decode($request->getContent(), true);
  277. $base64File = $data['fileBase64'] ?? null;
  278. $fileName = $data['fileName'] ?? 'import.xlsx';
  279. if (!$base64File) {
  280. return new JsonResponse(['success' => false, 'message' => 'Aucun fichier reçu'], 400);
  281. }
  282. // Reconstruire un fichier temporaire depuis le base64
  283. $fileContent = base64_decode($base64File);
  284. $tmpPath = sys_get_temp_dir() . '/' . uniqid('da_excel_') . '_' . $fileName;
  285. file_put_contents($tmpPath, $fileContent);
  286. // Créer un SplFileInfo pour le passer au service
  287. $uploadedFile = new \SplFileInfo($tmpPath);
  288. }
  289. try {
  290. // Réutilise le service existant ExcelImportService
  291. // Injectez-le dans le constructeur si nécessaire
  292. // $this->excelImportService->importDemandesAchat($uploadedFile);
  293. // Nettoyage fichier temporaire si besoin
  294. if (isset($tmpPath) && file_exists($tmpPath)) {
  295. unlink($tmpPath);
  296. }
  297. return new JsonResponse([
  298. 'success' => true,
  299. 'message' => 'Import Excel traité avec succès',
  300. ]);
  301. } catch (\Exception $e) {
  302. return new JsonResponse([
  303. 'success' => false,
  304. 'message' => 'Erreur lors de l\'import : ' . $e->getMessage(),
  305. ], 500);
  306. }
  307. }
  308. // =========================================================
  309. // GET /api/da/liste-temp/{num}
  310. // Retourne les lignes DemAchatTemp d'un brouillon (pour UI offline)
  311. // =========================================================
  312. public function apiListeTemp(int $num): JsonResponse
  313. {
  314. $user = $this->security->getUser();
  315. if (!$user) {
  316. return new JsonResponse(['success' => false, 'message' => 'Non authentifié'], 401);
  317. }
  318. $datemps = $this->em->getRepository(DemAchatTemp::class)->findDemandeAchatPerso($num);
  319. $lignes = [];
  320. foreach ($datemps as $d) {
  321. $lignes[] = [
  322. 'id' => $d->getId(),
  323. 'designation' => $d->getDesignation(),
  324. 'quantDemande' => $d->getQuantDemande(),
  325. 'prixU' => $d->getPrixU(),
  326. 'description' => $d->getDescription(),
  327. 'centreCout' => $d->getCentreCout(),
  328. 'nomProjet' => $d->getNomProjet(),
  329. 'objet' => $d->getObjet(),
  330. 'region' => $d->getRegion(),
  331. 'delais' => $d->getDelais(),
  332. 'numDemandeAchat' => $d->getNumDemandeAchat(),
  333. ];
  334. }
  335. return new JsonResponse([
  336. 'success' => true,
  337. 'lignes' => $lignes,
  338. 'total' => count($lignes),
  339. ]);
  340. }
  341. // =========================================================
  342. // Helpers privés
  343. // =========================================================
  344. private function decodeRequest(Request $request): array
  345. {
  346. // Supporte JSON body et form-data
  347. $contentType = $request->headers->get('Content-Type', '');
  348. if (str_contains($contentType, 'application/json')) {
  349. return json_decode($request->getContent(), true) ?? [];
  350. }
  351. return $request->request->all();
  352. }
  353. private function getNextNumDemande(): int
  354. {
  355. $demachas = $this->em->getRepository(DemAchatTemp::class)->findAll();
  356. $num = 1;
  357. foreach ($demachas as $demacha) {
  358. if ($demacha->getNumDemandeAchat() > $num) {
  359. $num = $demacha->getNumDemandeAchat();
  360. }
  361. }
  362. return $num + 1;
  363. }
  364. private function convertRegion(?string $region): string
  365. {
  366. $map = [
  367. '1' => 'Bamako',
  368. '2' => 'Segou',
  369. '3' => 'Mopti',
  370. '4' => 'Sikasso',
  371. '5' => 'Koulikoro',
  372. ];
  373. return $map[$region] ?? ($region ?? '');
  374. }
  375. public function checkSession(): JsonResponse
  376. {
  377. $user = $this->security->getUser();
  378. if (!$user) {
  379. return new JsonResponse([
  380. 'authenticated' => false,
  381. 'message' => 'Session expirée ou non authentifié',
  382. ], 401);
  383. }
  384. return new JsonResponse([
  385. 'authenticated' => true,
  386. 'username' => $user->getNom() . ' ' . $user->getPrenom(),
  387. 'userId' => $user->getId(),
  388. ]);
  389. }
  390. public function soumettrebrouillonComplet(Request $request): JsonResponse
  391. {
  392. $user = $this->security->getUser();
  393. if (!$user) {
  394. return new JsonResponse(['success' => false, 'message' => 'Session expirée'], 401);
  395. }
  396. $data = json_decode($request->getContent(), true);
  397. if (!$data) {
  398. return new JsonResponse(['success' => false, 'message' => 'Données invalides'], 400);
  399. }
  400. $objet = $data['objet'] ?? '';
  401. $region = $data['region'] ?? '';
  402. $centreCoutId = $data['centreCoutId'] ?? null;
  403. $bailleurId = $data['bailleurId'] ?? null;
  404. $delais = $data['delais'] ?? null;
  405. $articles = $data['articles'] ?? [];
  406. if (empty($articles)) {
  407. return new JsonResponse(['success' => false, 'message' => 'Aucun article'], 400);
  408. }
  409. $cecout = $centreCoutId ? $this->em->getRepository(CentreCout::class)->find($centreCoutId) : null;
  410. $nomprojet = $bailleurId ? $this->em->getRepository(Bailleur::class)->find($bailleurId) : null;
  411. if (!$cecout || !$nomprojet) {
  412. return new JsonResponse(['success' => false, 'message' => 'Centre de coût ou projet introuvable'], 404);
  413. }
  414. // ── Calcul du prochain numéro de brouillon ───────────────────────────
  415. $demachas = $this->em->getRepository(DemAchatTemp::class)->findAll();
  416. $num = 1;
  417. foreach ($demachas as $d) {
  418. if ($d->getNumDemandeAchat() > $num) $num = $d->getNumDemandeAchat();
  419. }
  420. $num++;
  421. // ── Création des DemAchatTemp pour chaque article ───────────────────
  422. foreach ($articles as $article) {
  423. $designation = $article['designation'] ?? '';
  424. $quantite = (int)($article['quantite'] ?? 1);
  425. $prixU = (float)($article['prixU'] ?? 0);
  426. $description = $article['description'] ?? '';
  427. $designrech = $this->em->getRepository(Catalogue::class)
  428. ->findOneByDesignation($designation);
  429. $demAch = new DemAchatTemp();
  430. $demAch->setCentreCout($cecout->getLibele());
  431. $demAch->setCostcenter($cecout);
  432. $demAch->setNombaille($nomprojet);
  433. $demAch->setNomProjet($nomprojet->getLibele());
  434. $demAch->setRegion($region);
  435. $demAch->setObjet($objet);
  436. $demAch->setQuantDemande($quantite);
  437. $demAch->setDescription($description);
  438. $demAch->setDelais($delais);
  439. $demAch->setDateSoumis(new \DateTime());
  440. $demAch->setUser($user);
  441. $demAch->setNumDemandeAchat($num);
  442. $demAch->setNumOrdre(1);
  443. if ($designrech) {
  444. $demAch->setDesignation($designrech->getDesignation());
  445. $demAch->setCatalogue($designrech);
  446. $demAch->setPrixU($prixU);
  447. } else {
  448. $demAch->setDesignation($designation . ' (catalogue à vérifier)');
  449. $demAch->setPrixU($prixU);
  450. }
  451. $this->em->persist($demAch);
  452. }
  453. $this->em->flush();
  454. // ── Conversion immédiate en DA définitive (daenvoyer) ────────────────
  455. $datemps = $this->em->getRepository(DemAchatTemp::class)->findDemandeAchatPerso($num);
  456. $numda = $this->em->getRepository(NumDA::class)->find(1);
  457. $numDA = $numda->getNum() + 1;
  458. $dat = date('Y-m-d H:i:s');
  459. $achat = null;
  460. foreach ($datemps as $datemp) {
  461. $achat = new demAchat();
  462. $raport = new RapportageDA();
  463. $achat->setDesignation($datemp->getDesignation());
  464. $achat->setPrixU($datemp->getPrixU());
  465. $achat->setCentreCout($datemp->getCentreCout());
  466. $achat->setNomProjet($datemp->getNomProjet());
  467. $achat->setNombaille($datemp->getNombaille());
  468. $achat->setCostcenter($datemp->getCostcenter());
  469. $achat->setCatalogue($datemp->getCatalogue());
  470. $achat->setObjet($datemp->getObjet());
  471. $achat->setRegion($datemp->getRegion());
  472. $achat->setDescription($datemp->getDescription());
  473. $achat->setUser($datemp->getUser());
  474. $achat->setQuantDemande($datemp->getQuantDemande());
  475. $achat->setResteACommander($datemp->getQuantDemande());
  476. $achat->setDelais($datemp->getDelais());
  477. $achat->setNumOrdre(1);
  478. $achat->setDateSoumis(new \DateTime($dat));
  479. // Gestion contrat-cadre
  480. $montantLigne = (float)$datemp->getPrixU() * (float)$datemp->getQuantDemande();
  481. $catalogue = $datemp->getCatalogue();
  482. $categorieCatalogue = $catalogue ? $catalogue->getCategorycatalogue() : null;
  483. $contratCadre = null;
  484. if ($categorieCatalogue) {
  485. foreach ($categorieCatalogue->getCategoriesMarche() as $categorieMarche) {
  486. $contrats = $this->contratCadreService->listerDisponiblesPourDA(
  487. $categorieMarche->getId(), $montantLigne
  488. );
  489. if (!empty($contrats)) { $contratCadre = $contrats[0]; break; }
  490. }
  491. }
  492. if ($contratCadre) {
  493. $achat->setContratCadre($contratCadre);
  494. $achat->setStatutContratCadree(demAchat::CC_SUGGERE);
  495. $contratCadre->setMontantReserve(
  496. (string)((float)$contratCadre->getMontantReserve() + $montantLigne)
  497. );
  498. $this->em->persist($contratCadre);
  499. }
  500. $numda->setNum($numDA);
  501. $achat->setNumDemandeAchat($numDA);
  502. $this->em->persist($achat);
  503. $this->em->remove($datemp);
  504. $raport->setDateSoumis(new \DateTime($dat));
  505. $raport->setDemAchat($achat);
  506. $this->em->persist($raport);
  507. $this->em->flush();
  508. if (!empty($datemp->getDescription()) && trim($datemp->getDescription()) !== '') {
  509. $this->translationWriter->create(
  510. 'demAchat', $achat->getId(), 'description', $achat->getDescription()
  511. );
  512. }
  513. }
  514. if ($achat) {
  515. $this->em->refresh($achat);
  516. $this->translationWriter->create('demAchat', $achat->getId(), 'objet', $achat->getObjet());
  517. $this->em->flush();
  518. }
  519. return new JsonResponse([
  520. 'success' => true,
  521. 'message' => 'DA créée avec succès',
  522. 'numDemandeAchat' => $numDA,
  523. ]);
  524. }
  525. // =========================================================
  526. // GET /api/da/references
  527. // Retourne catalogue + centres de coût + bailleurs en JSON
  528. // Appelé par da-offline.js lors de la visite online pour
  529. // mettre en cache ces données dans IndexedDB (da-shell.html)
  530. // =========================================================
  531. public function getReferences(): JsonResponse
  532. {
  533. $user = $this->security->getUser();
  534. if (!$user) {
  535. return new JsonResponse(['success' => false], 401);
  536. }
  537. $catalogues = $this->em->getRepository(Catalogue::class)
  538. ->createQueryBuilder('c')
  539. ->select('c.id, c.designation') // ← prixUnitaire retiré
  540. ->getQuery()
  541. ->getArrayResult();
  542. $centres = $this->em->getRepository(CentreCout::class)
  543. ->createQueryBuilder('c')
  544. ->select('c.id, c.libele')
  545. ->getQuery()
  546. ->getArrayResult();
  547. $bailleurs = $this->em->getRepository(Bailleur::class)
  548. ->createQueryBuilder('b')
  549. ->select('b.id, b.libele')
  550. ->getQuery()
  551. ->getArrayResult();
  552. return new JsonResponse([
  553. 'success' => true,
  554. 'catalogue' => $catalogues,
  555. 'centres' => $centres,
  556. 'bailleurs' => $bailleurs,
  557. 'cachedAt' => (new \DateTime())->format('Y-m-d H:i:s'),
  558. ]);
  559. }
  560. // =========================================================
  561. // GET /da-offline
  562. // Sert da-shell.html depuis Symfony (avec headers corrects)
  563. // Permet d'être mis en cache par le SW même si le fichier
  564. // est dans /public/da-shell.html
  565. // =========================================================
  566. public function daShell(): Response
  567. {
  568. return new Response(
  569. file_get_contents($this->getParameter('kernel.project_dir') . '/public/da-shell.html'),
  570. 200,
  571. ['Content-Type' => 'text/html; charset=utf-8']
  572. );
  573. }
  574. }