src/Controller/Api/Catalog/GetCatalogPdfControllerV3.php line 25

Open in your IDE?
  1. <?php
  2. // src/Controller/Api/Catalog/GetCatalogPdfControllerV3.php
  3. namespace App\Controller\Api\Catalog;
  4. /**
  5. * @author rbellorin@sumagroups.com
  6. * @version 2026-03-24
  7. */
  8. use App\Catalog\Application\DTO\CatalogPage;
  9. use App\Catalog\Application\UseCase\BuildCatalogPagesV2;
  10. use App\Catalog\Application\UseCase\GenerateCatalogPdf;
  11. use App\Catalog\Application\UseCase\GetMakesList;
  12. use App\Shared\Pdf\CatalogBrandConfig;
  13. use App\Shared\Utils\UrlHelper;
  14. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  15. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  16. use Symfony\Component\HttpFoundation\JsonResponse;
  17. use Symfony\Component\HttpFoundation\Request;
  18. use Symfony\Component\HttpFoundation\ResponseHeaderBag;
  19. use Symfony\Component\Routing\Annotation\Route;
  20. use Symfony\Component\Security\Core\Security;
  21. final class GetCatalogPdfControllerV3 extends AbstractController
  22. {
  23. public function __construct(
  24. private Security $security,
  25. private GenerateCatalogPdf $generatePdf,
  26. private CatalogBrandConfig $catalogBrandConfig,
  27. private readonly GetMakesList $getMakesList
  28. ) {}
  29. /**
  30. * @Route("/api/catalogo/v3/catalogo.pdf", name="catalog_v3_pdf", methods={"GET"})
  31. */
  32. public function __invoke(
  33. Request $request,
  34. BuildCatalogPagesV2 $useCase
  35. ): JsonResponse|BinaryFileResponse {
  36. ini_set('memory_limit', '2048M');
  37. if (function_exists('set_time_limit')) {
  38. @set_time_limit(180);
  39. }
  40. $params = $request->query->all();
  41. $data = ($useCase)($params);
  42. $marcaList = ($this->getMakesList)($params);
  43. // $marcaList = ($this->getMakesList)($params);
  44. // dd($params, $marcaList);
  45. $marcaList = array_map(
  46. fn($e) => (object)[
  47. 'id' => $e->id,
  48. 'name' => $e->name,
  49. 'categories' => $e->categories,
  50. 'links' => $this->catalogBrandConfig->getByMarca($e->name),
  51. ],
  52. $marcaList
  53. );
  54. $marcaList = array_map(function ($e) use ($data) {
  55. // 1. Filtrar productos por marca
  56. $e->products = array_filter(
  57. $data,
  58. fn($f) => $f->marca === $e->name
  59. );
  60. $e->products = array_values($e->products);
  61. // 2. Filtrar productos por categorías válidas de la marca
  62. $e->products = array_filter(
  63. $e->products,
  64. fn($f) => in_array($f->categoria, $e->categories, true)
  65. );
  66. $e->products = array_values($e->products);
  67. // 3. Ordenar productos según el orden de categorías definido en la marca
  68. usort($e->products, function ($a, $b) use ($e) {
  69. $posA = array_search($a->categoria, $e->categories, true);
  70. $posB = array_search($b->categoria, $e->categories, true);
  71. $posA = $posA === false ? PHP_INT_MAX : $posA;
  72. $posB = $posB === false ? PHP_INT_MAX : $posB;
  73. return $posA <=> $posB;
  74. });
  75. // 4. Crear páginas de catálogo por categoría, agrupando productos de 2 en 2
  76. $e->grouped = [];
  77. foreach ($e->categories as $categoriaNombre) {
  78. $productosDeCategoria = array_filter(
  79. $e->products,
  80. fn($f) => $f->categoria === $categoriaNombre
  81. );
  82. $productosDeCategoria = array_values($productosDeCategoria);
  83. usort(
  84. $productosDeCategoria,
  85. fn($a, $b) => strcmp((string) $a->comercialName, (string) $b->comercialName)
  86. );
  87. $agrupados = array_chunk($productosDeCategoria, 2);
  88. foreach ($agrupados as $chunk) {
  89. $chunkArray = [];
  90. foreach($chunk as $item) {
  91. $chunkArray[] = [
  92. 'm_product_id' => $item->m_product_id
  93. ,'code' => $item->code
  94. ,'sku' => $item->sku
  95. ,'name' => $item->name
  96. ,'comercialName' => $item->comercialName ?? '*'
  97. ,'allegationHtml' => $item->allegationHtml ?? ''
  98. ,'promo' => $item->promo
  99. ,'new' => $item->new
  100. ,'image' => UrlHelper::deprefixSite($item->image)
  101. ,'price' => $item->price
  102. ,'categoria' => $item->categoria
  103. ,'marca' => $item->marca
  104. ,'sm_marca_id' => $item->sm_marca_id
  105. ,'m_product_category_id' => $item->m_product_category_id
  106. ];
  107. }
  108. $e->grouped[] = new CatalogPage(
  109. $categoriaNombre,
  110. $e->name,
  111. $chunkArray
  112. );
  113. }
  114. }
  115. return $e;
  116. }, $marcaList);
  117. // dd($marcaList[0]);
  118. // Armar un único arreglo de páginas multimarca
  119. $catalogPages = [];
  120. foreach ($marcaList as $marcaItem) {
  121. foreach ($marcaItem->grouped as $page) {
  122. $catalogPages[] = $page;
  123. }
  124. }
  125. if (empty($catalogPages)) {
  126. return new JsonResponse([
  127. 'message' => 'No hay productos para generar el catálogo multimarca.'
  128. ], 404);
  129. }
  130. $disp = ResponseHeaderBag::DISPOSITION_ATTACHMENT;
  131. // Un solo PDF final multimarca
  132. $path = $this->generatePdf->handle($catalogPages, [
  133. 'title' => 'Catálogo Multimarca',
  134. 'brand' => 'viotto',
  135. 'sm_marca' => null,
  136. 'output' => 'F',
  137. 'linkPage' => null,
  138. 'linkSocial' => null,
  139. 'with_prices' => $this->toBool($params['with_prices'] ?? true),
  140. ]);
  141. $resp = new BinaryFileResponse($path);
  142. $resp->headers->set('Content-Type', 'application/pdf');
  143. $resp->setContentDisposition($disp, 'catalogo-multimarca.pdf');
  144. $resp->headers->set('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0');
  145. $resp->headers->set('Pragma', 'no-cache');
  146. $resp->headers->set('Expires', '0');
  147. $resp->setPrivate();
  148. $resp->headers->remove('ETag');
  149. if (@is_file($path)) {
  150. $resp->headers->set('Content-Length', (string) @filesize($path));
  151. }
  152. $resp->deleteFileAfterSend(true);
  153. return $resp;
  154. }
  155. private function toBool(mixed $value): bool
  156. {
  157. if (is_bool($value)) return $value;
  158. if (is_int($value)) return $value !== 0;
  159. return !in_array(strtolower((string) $value), ['0', 'false', 'no', 'n'], true);
  160. }
  161. }