src/Controller/PresupuestoController.php line 45

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\Budgetproduct;
  4. use App\Entity\Estadosfactu;
  5. use App\Entity\Estadospresu;
  6. use App\Entity\Info;
  7. use App\Entity\Presupuesto;
  8. use App\Entity\Productos;
  9. use App\Entity\Proveedores;
  10. use App\Entity\Tickettofactura;
  11. use App\Entity\TypeIva;
  12. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\Routing\Annotation\Route;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use Symfony\UX\Turbo\TurboBundle;
  17. use Doctrine\ORM\EntityManagerInterface;
  18. use DateTime;
  19. use TCPDF;
  20. use Symfony\Component\HttpFoundation\JsonResponse;
  21. use Symfony\Component\Filesystem\Filesystem;
  22. use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
  23. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  24. class PresupuestoController extends AbstractController
  25. {
  26. private array $products = [];
  27. private $entityManager;
  28. private $filesystem;
  29. public function __construct(EntityManagerInterface $entityManager, Filesystem $filesystem)
  30. {
  31. $this->entityManager = $entityManager;
  32. $this->filesystem = $filesystem;
  33. }
  34. /**
  35. * @Route("/", name="presupuesto")
  36. * @IsGranted("ROLE_USER")
  37. */
  38. public function index(): Response
  39. {
  40. $presupuestos = $this->entityManager->getRepository(Presupuesto::class)->findAll();
  41. $productos = $this->entityManager->getRepository(Productos::class)->findAll();
  42. $info = $this->entityManager->getRepository(Info::class)->findOneBy(['id' => 1]);
  43. $infoNotFill = false;
  44. if (empty($info)) {
  45. $infoNotFill = true;
  46. }
  47. $data = array();
  48. foreach ($presupuestos as $presupuesto) {
  49. $data_elements = array();
  50. $presuxproducts = $presupuesto->getBudgetproducts();
  51. foreach ($presuxproducts as $element) {
  52. $temp = array(
  53. 'id' => $element->getId(),
  54. 'name' => $element->getProduct()->getName(),
  55. 'quantity' => $element->getQuantity(),
  56. 'unit_price' => $element->getPrice() / $element->getQuantity(),
  57. 'price' => $element->getPrice(),
  58. );
  59. array_push($data_elements, $temp);
  60. }
  61. $temp = array(
  62. 'id' => $presupuesto->getId(),
  63. 'name' => $presupuesto->getName(),
  64. 'concept' => $presupuesto->getConcept(),
  65. 'date' => $presupuesto->getDate()->format('d/m/Y'),
  66. 'customer' => $presupuesto->getCustomer()->getName(),
  67. 'ruta' => $presupuesto->getPathPdf(),
  68. 'statuspresu' => $presupuesto->getEstado()->getName(),
  69. 'elements' => $data_elements
  70. );
  71. array_push($data, $temp);
  72. }
  73. $data_productos = array();
  74. foreach ($productos as $producto) {
  75. $temp = array(
  76. 'id' => $producto->getId(),
  77. 'name' => $producto->getName(),
  78. 'price' => $producto->getPrice(),
  79. );
  80. array_push($data_productos, $temp);
  81. }
  82. $clientes = $this->entityManager->getRepository(Proveedores::class)->findAll();
  83. $data_clientes = array();
  84. foreach ($clientes as $cliente) {
  85. $temp = array(
  86. 'id' => $cliente->getId(),
  87. 'name' => $cliente->getName(),
  88. );
  89. array_push($data_clientes, $temp);
  90. }
  91. $ivas = $this->entityManager->getRepository(TypeIva::class)->findAll();
  92. $data_ivas = array();
  93. foreach ($ivas as $iva) {
  94. $temp = array(
  95. 'id' => $iva->getId(),
  96. 'quantity' => $iva->getQuantity() . '%',
  97. );
  98. array_push($data_ivas, $temp);
  99. };
  100. return $this->render('presupuesto/index.html.twig', [
  101. 'controller_name' => 'PresupuestoController',
  102. 'products' => $this->products,
  103. 'data' => array_reverse($data),
  104. 'data_productos' => $data_productos,
  105. 'clientes' => $data_clientes,
  106. 'infoNotFill' => $infoNotFill,
  107. 'ivas' => $data_ivas,
  108. ]);
  109. }
  110. /**
  111. * @Route("/budget/add_product", name="budget_add_product", methods={"POST"})
  112. */
  113. public function addProduct(Request $request): Response
  114. {
  115. $id = $request->request->get('id');
  116. $name = $request->request->get('name');
  117. $price = (float) $request->request->get('price');
  118. $quantity = (int) $request->request->get('quantity');
  119. $iva = (int) $request->request->get('iva');
  120. $total = $price * $quantity;
  121. $this->products[] = ['name' => $name, 'price' => $total, 'quantity' => $quantity, 'iva' => $iva];
  122. $uniqueId = uniqid('', true);
  123. // Si es una petición Turbo Stream, enviamos solo el stream de respuesta
  124. if (TurboBundle::STREAM_FORMAT === $request->getPreferredFormat()) {
  125. return new Response(
  126. $this->renderView('presupuesto/_product_stream.html.twig', ['product' => ['id' => $id, 'name' => $name, 'price' => $total, 'quantity' => $quantity, 'uniqueId' => $uniqueId, 'iva' => $iva]]),
  127. 200,
  128. ['Content-Type' => 'text/vnd.turbo-stream.html']
  129. );
  130. }
  131. return $this->redirectToRoute('budget');
  132. }
  133. /**
  134. * @Route("/budget/save", name="budget_save", methods={"POST"})
  135. */
  136. public function saveBudget(Request $request): Response
  137. {
  138. // Aquí manejarás el presupuesto enviado por el usuario
  139. $products = json_decode($request->request->get('products'));
  140. $concepto = $request->request->get('concepto');
  141. $cliente = $this->entityManager->getRepository(Proveedores::class)->findOneBy(['id' => $request->request->get('cliente')]);
  142. $datosEmpresa = $this->entityManager->getRepository(Info::class)->findOneBy(['id' => 1]);
  143. $pdfBase64 = $request->request->get('pdf');
  144. // Decodificar el contenido en base64 y guardar el archivo en el servidor
  145. $pdfContent = base64_decode($pdfBase64);
  146. $currentYear = (new DateTime())->format('Y'); // Obtener el año actual
  147. $currentDate = (new DateTime())->format('d/m/Y');
  148. $nextMonthDate = (new DateTime())->modify('+1 month')->format('d/m/Y'); // Fecha con un mes más
  149. $totalPresupuestos = $this->entityManager->getRepository(Presupuesto::class)->findAll();
  150. // Directorio donde guardar el PDF
  151. $budgetsDirectory = $this->getParameter('budgets_directory');
  152. $filesystem = new Filesystem();
  153. // Asegúrate de que el directorio tiene permisos de escritura
  154. if (!$filesystem->exists($budgetsDirectory)) {
  155. try {
  156. $filesystem->mkdir($budgetsDirectory, 0755
  157. );
  158. } catch (IOExceptionInterface $exception) {
  159. return new JsonResponse(['error' => 'Error al crear la carpeta de subidas.'], Response::HTTP_INTERNAL_SERVER_ERROR);
  160. }
  161. }
  162. $pdfFile = 'P' . $currentYear . (count($totalPresupuestos) + 1) . '.pdf';
  163. $nameToBadget = 'P' . $currentYear . (count($totalPresupuestos) + 1);
  164. $pdfPathAbsolute = $budgetsDirectory . '/' . $pdfFile; // Ruta absoluta para guardar el archivo
  165. $pdfPathRelative = 'presupuestos/' . $pdfFile; // Ruta relativa para el frontend o base de datos
  166. $newPresupuesto = new Presupuesto();
  167. $newPresupuesto->setName('P' . $currentYear . (count($totalPresupuestos) + 1));
  168. $newPresupuesto->setDate(new \DateTime());
  169. $newPresupuesto->setConcept($concepto);
  170. $newPresupuesto->setCustomer($cliente);
  171. $newPresupuesto->setEstado($this->entityManager->getRepository(Estadospresu::class)->find(1));
  172. $this->entityManager->persist($newPresupuesto);
  173. // $this->entityManager->flush();
  174. $data_productos = [];
  175. $data_ivas = [];
  176. foreach ($products as $product) {
  177. $budgetProduct = new Budgetproduct();
  178. $producto = $this->entityManager->getRepository(Productos::class)->findOneBy(['id' => $product->id]);
  179. $budgetProduct->setBudget($newPresupuesto);
  180. $budgetProduct->setProduct($producto);
  181. $budgetProduct->setQuantity($product->quantity);
  182. $budgetProduct->setPrice($product->price);
  183. $budgetProduct->setIva($product->iva);
  184. if (!array_filter($data_ivas, fn($iva) => $iva['id'] === $product->iva)) {
  185. $temp = array(
  186. 'id' => $product->iva,
  187. 'valor' => 0,
  188. );
  189. array_push($data_ivas, $temp);
  190. }
  191. $temp = array(
  192. 'name' => $producto->getName(),
  193. 'price' => $product->price / $product->quantity,
  194. 'quantity' => $product->quantity,
  195. 'iva' => $product->iva,
  196. );
  197. array_push($data_productos, $temp);
  198. $this->entityManager->persist($budgetProduct);
  199. // $this->entityManager->flush();
  200. }
  201. // Crear una instancia de TCPDF
  202. $pdf = new TCPDF();
  203. // Configurar el documento
  204. $pdf->SetCreator(PDF_CREATOR);
  205. $pdf->SetAuthor('W3Barcelona SL');
  206. $pdf->SetTitle('Factura');
  207. $pdf->SetSubject('Asunto del Documento');
  208. $pdf->SetKeywords('TCPDF, PDF, ejemplo, tutorial');
  209. $imagePath = $datosEmpresa->getLogo();
  210. $imageFullPath = $this->getParameter('public') . $imagePath; // Construir ruta absoluta
  211. // Asegúrate de que el archivo exista
  212. if (!file_exists($imageFullPath)) {
  213. throw new \Exception("La imagen no se encuentra en la ruta: " . $imageFullPath);
  214. }
  215. // Desactiva el encabezado automático
  216. $pdf->setPrintHeader(false);
  217. // Configurar los márgenes y pie de página
  218. $pdf->setFooterMargin(10);
  219. $pdf->SetMargins(15, 27, 15);
  220. $pdf->setFooterData([0, 64, 0], [0, 64, 128]);
  221. // Añadir la página
  222. $pdf->AddPage();
  223. // Añadir manualmente el encabezado con la imagen y texto
  224. $pdf->Image($imageFullPath, 170, 5, 30, 30); // Ancho = 30 mm, Alto = 20 mm
  225. $pdf->SetFont('helvetica', 'B', 16);
  226. $pdf->SetXY(15, 10); // Coordenadas iniciales para el texto
  227. $pdf->Cell(0, 10, 'Presupuesto', 0, 1, 'L');
  228. $pdf->SetFont('helvetica', '', 12);
  229. $pdf->SetXY(15, 18);
  230. $pdf->Cell(0, 10, 'Nº ' . $nameToBadget, 0, 1, 'L');
  231. // Añadir una línea para dividir el encabezado del contenido
  232. $pdf->SetDrawColor(169, 169, 169); // Color negro
  233. $pdf->SetLineWidth(0.3); // Grosor de la línea
  234. $pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
  235. // Continúa con el contenido del PDF...
  236. $pdf->SetFont('helvetica', '', 10);
  237. $pdf->SetXY(135, 35);
  238. $pdf->MultiCell(120, 5, $datosEmpresa->getName() . "\n" .
  239. $datosEmpresa->getDir() . "\n" .
  240. $datosEmpresa->getCif(), 0, 'J', false, 1);
  241. // Configuración inicial
  242. $pdf->SetFont('helvetica', '', 10);
  243. $pdf->SetDrawColor(169, 169, 169); // Gris claro para la línea debajo de los títulos
  244. // Dividir el ancho de la página para tres columnas con márgenes
  245. $pageWidth = $pdf->GetPageWidth() - 20; // Ancho de la página menos márgenes
  246. $colWidth = $pageWidth / 3; // Ancho de cada columna
  247. $startX = 10; // Margen izquierdo
  248. $startY = $pdf->GetY(); // Posición inicial vertical
  249. // ----------------------------
  250. // Primera columna: FACTURAR A
  251. // ----------------------------
  252. $pdf->SetXY($startX, $startY); // Posición inicial
  253. $pdf->SetFont('helvetica', 'B', 10);
  254. $pdf->Cell($colWidth, 5, 'PRESUPUESTO A', 0, 1, 'L'); // Título
  255. $pdf->Line($startX, $pdf->GetY(), $startX + $colWidth, $pdf->GetY()); // Línea horizontal
  256. $pdf->Ln(3); // Espacio después de la línea
  257. // Datos del cliente
  258. $pdf->SetFont('helvetica', '', 10);
  259. $pdf->SetX($startX); // Mantenerse en la misma columna
  260. $pdf->MultiCell(
  261. $colWidth,
  262. 5,
  263. $cliente->getName() . "\n" .
  264. $cliente->getDir() . "\n" .
  265. $cliente->getNif(),
  266. 0,
  267. 'L'
  268. );
  269. // ----------------------------
  270. // Segunda columna: DETALLES
  271. // ----------------------------
  272. $pdf->SetXY($startX + $colWidth, $startY); // Moverse a la segunda columna
  273. $pdf->SetFont('helvetica', 'B', 10);
  274. $pdf->Cell($colWidth, 5, 'DETALLES', 0, 1, 'L'); // Título
  275. $pdf->Line($startX + $colWidth, $pdf->GetY(), $startX + (2 * $colWidth), $pdf->GetY()); // Línea horizontal
  276. $pdf->Ln(3); // Espacio después de la línea
  277. // Detalles del proyecto
  278. $pdf->SetFont('helvetica', '', 10);
  279. $pdf->SetX($startX + $colWidth); // Mantenerse en la columna
  280. $pdf->MultiCell($colWidth, 5, $concepto, 0, 'L');
  281. // ----------------------------
  282. // Tercera columna: PAGO
  283. // ----------------------------
  284. $pdf->SetXY($startX + (2 * $colWidth), $startY); // Moverse a la tercera columna
  285. $pdf->SetFont('helvetica', 'B', 10);
  286. $pdf->Cell($colWidth, 5, 'PLAZO', 0, 1, 'L'); // Título
  287. $pdf->Line($startX + (2 * $colWidth), $pdf->GetY(), $startX + (3 * $colWidth), $pdf->GetY()); // Línea horizontal
  288. $pdf->Ln(3); // Espacio después de la línea
  289. // Información de pago
  290. $pdf->SetFont('helvetica', '', 10);
  291. $pdf->SetX($startX + (2 * $colWidth)); // Mantenerse en la columna
  292. $pdf->MultiCell(
  293. $colWidth,
  294. 5,
  295. "Fecha de emisión: " . $currentDate . "\n" .
  296. "Fecha de vencimiento: " . $nextMonthDate,
  297. 0,
  298. 'L'
  299. );
  300. // ----------------------------
  301. // Salto de línea después de las columnas
  302. // ----------------------------
  303. $pdf->Ln(20);
  304. // Establecer color gris para las líneas
  305. $pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
  306. // Encabezados de la tabla con fondo y texto en negrita
  307. $pdf->SetFont('helvetica', 'B', 12);
  308. $pdf->Cell(55, 10, 'CONCEPTO', 'T', 0, 'L'); // Solo línea superior
  309. $pdf->Cell(25, 10, 'CANT.', 'T', 0, 'C'); // Solo línea superior
  310. $pdf->Cell(35, 10, 'PRECIO UNITARIO', 'T', 0, 'C'); // Solo línea superior
  311. $pdf->Cell(30, 10, 'IVA (%)', 'T', 0, 'C'); // Solo línea superior
  312. $pdf->Cell(30, 10, 'IMPORTE', 'T', 1, 'C'); // Solo línea superior y salto de línea
  313. // Recorrer el array y añadir contenido al PDF
  314. $totalBase = 0;
  315. $totalIVA = 0;
  316. foreach ($data_productos as $producto) {
  317. $subtotal = $producto['price'] * $producto['quantity'];
  318. $totalBase += $subtotal;
  319. // Actualizar el array de IVAs
  320. foreach ($data_ivas as &$iva) { // Usamos referencia para modificar directamente
  321. if ($iva['id'] === $producto['iva']) {
  322. $iva['valor'] += $subtotal * ($producto['iva'] / 100); // Sumar el IVA calculado
  323. break;
  324. }
  325. }
  326. unset($iva);
  327. // Nombre del artículo
  328. $pdf->SetFont('helvetica', '', 10); // Fuente normal para los productos
  329. // Guardar la posición Y antes de la celda MultiCell
  330. $yPos = $pdf->GetY();
  331. // MultiCell para el concepto, de modo que el texto se ajuste automáticamente a varias líneas
  332. $pdf->MultiCell(55, 10, $producto['name'], 'T', 'L');
  333. // Ajustamos la posición Y después del MultiCell
  334. $height = $pdf->GetY() - $yPos; // Obtener la altura del contenido generado por MultiCell
  335. $pdf->SetY($yPos); // Restaurar la posición Y al inicio de la fila
  336. // Añadir las otras celdas normalmente con la altura ajustada
  337. $pdf->Cell(55, $height, '', 0, 0); // Espacio vacío para la celda del concepto
  338. $pdf->Cell(25, 10, $producto['quantity'], 'T', 0, 'C');
  339. $pdf->Cell(35, 10, number_format($producto['price'], 2, ',', '.') . ' €', 'T', 0, 'C');
  340. $pdf->Cell(30, 10, $producto['iva'] . ' %', 'T', 0, 'C');
  341. $pdf->Cell(30, 10, number_format($subtotal, 2, ',', '.') . ' €', 'T', 1, 'C'); // Línea superior solo
  342. }
  343. $pdf->Ln();
  344. // Calcular IVA y Total
  345. foreach($data_ivas as $iva){
  346. $totalIVA += floatval($iva['valor']);
  347. }
  348. $totalFinal = $totalBase + $totalIVA;
  349. // Establecer color gris para las líneas
  350. $pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
  351. // Mostrar resumen de precios al final del PDF (sin líneas verticales)
  352. $pdf->SetFont('helvetica', '', 10);
  353. // Establecer color gris para las líneas horizontales
  354. $pdf->SetDrawColor(169, 169, 169); // Gris claro
  355. // Reducir la altura de las celdas
  356. $cellHeight = 8; // Altura de las celdas ajustada
  357. // Ancho total restringido para las líneas horizontales
  358. $lineWidth = 80; // Define el ancho total de las celdas restringido a la derecha
  359. // Subtotal
  360. $pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
  361. $pdf->Cell($lineWidth, $cellHeight, 'Subtotal', 'T', 0, 'R'); // Línea superior ajustada al ancho
  362. $pdf->Cell(30, $cellHeight, number_format($totalBase, 2) . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
  363. // IVA
  364. foreach ($data_ivas as $iva) {
  365. $pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
  366. $pdf->Cell($lineWidth, $cellHeight, 'IVA (' . $iva['id'] . '%)', 'T', 0, 'R'); // Mostrar el porcentaje de IVA
  367. $pdf->Cell(30, $cellHeight, number_format($iva['valor'], 2, ',', '.') . ' €', 'T', 1, 'R'); // Mostrar el valor del IVA
  368. }
  369. // Total a pagar
  370. $pdf->SetFont('helvetica', 'B', 12); // Negrita para el total
  371. $pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
  372. $pdf->Cell($lineWidth, $cellHeight, 'Total a pagar', 'T', 0, 'R'); // Línea superior ajustada al ancho
  373. $pdf->Cell(30, $cellHeight, number_format($totalFinal, 2, ',', '.') . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
  374. $pdf->Ln(20);
  375. // Establecer color gris para las líneas
  376. $pdf->SetDrawColor(169, 169, 169); // Color negro
  377. $pdf->SetLineWidth(0.3); // Grosor de la línea
  378. $pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
  379. $pieFactura = $datosEmpresa->getPiePresu();
  380. $pdf->SetFont('helvetica', '', 8);
  381. $pdf->Ln(20);
  382. $pdf->MultiCell(
  383. 190, // Ancho de la celda (ajústalo según el tamaño de tu página)
  384. 10, // Altura mínima de la celda
  385. $pieFactura, // Contenido del pie
  386. 'T', // Borde superior (T) para marcar el inicio del pie
  387. 'L', // Alineación a la izquierda
  388. false // No rellenar la celda
  389. );
  390. $budgetsDirectory = $this->getParameter('budgets_directory');
  391. // Guardar el PDF en el servidor
  392. $pdf->Output($pdfPathAbsolute, 'F');
  393. $newPresupuesto->setPathPdf($pdfPathRelative);
  394. $newPresupuesto->setBase($totalBase);
  395. $newPresupuesto->setIva($totalIVA);
  396. $newPresupuesto->setTotal($totalFinal);
  397. $this->entityManager->persist($budgetProduct);
  398. $this->entityManager->flush();
  399. // Asegurar que el archivo se haya creado correctamente
  400. if (file_exists($pdfPathRelative)) {
  401. // Guardar el mensaje de éxito en la sesión
  402. $this->addFlash('success', 'Presupuesto guardado correctamente.');
  403. // Redireccionar a la página de presupuestos (index)
  404. return $this->redirectToRoute('presupuesto');
  405. } else {
  406. return new Response("Hubo un error al guardar el PDF.", Response::HTTP_INTERNAL_SERVER_ERROR);
  407. }
  408. return new Response('Presupuesto guardado correctamente', 200);
  409. }
  410. /**
  411. * @Route("/createinvoice", name="create_invoice")
  412. */
  413. public function createInvoice(Request $request): Response
  414. {
  415. // Aquí manejarás el presupuesto enviado por el usuario
  416. $id = $request->query->get('id');
  417. $presupuesto = $this->entityManager->getRepository(Presupuesto::class)->findOneBy(['id' => $id]);
  418. if (!$presupuesto->isFactura()) {
  419. $currentYear = (int)(new DateTime())->format('Y'); // Obtener el año actual como entero
  420. $presupuesto->setFactura(1);
  421. $cliente = $presupuesto->getCustomer();
  422. $concepto = $presupuesto->getConcept();
  423. $datosEmpresa = $this->entityManager->getRepository(Info::class)->findOneBy(['id' => 1]);
  424. if (!$datosEmpresa) {
  425. $this->addFlash('error', 'No se encontraron los datos de la empresa');
  426. return $this->redirectToRoute('budget');
  427. }
  428. $budgetAndProducts = $presupuesto->getBudgetproducts();
  429. $invoiceDirectory = $this->getParameter('invoice_directory');
  430. $currentDate = (new DateTime())->format('d/m/Y');
  431. // Obtener el siguiente número de factura de forma segura
  432. $ticketRepository = $this->entityManager->getRepository(Tickettofactura::class);
  433. $invoiceNumber = $ticketRepository->getNextInvoiceNumber($currentYear);
  434. // Generar la referencia
  435. $invoiceRef = 'F' . $currentYear . $invoiceNumber;
  436. // Verificar que la referencia no exista (doble verificación para evitar condiciones de carrera)
  437. $maxAttempts = 10; // Máximo de intentos para evitar loop infinito
  438. $attempts = 0;
  439. while ($ticketRepository->refExists($invoiceRef) && $attempts < $maxAttempts) {
  440. $invoiceNumber++;
  441. $invoiceRef = 'F' . $currentYear . $invoiceNumber;
  442. $attempts++;
  443. }
  444. if ($attempts >= $maxAttempts) {
  445. $this->addFlash('error', 'Error al generar la referencia de la factura. Por favor, intente nuevamente.');
  446. return $this->redirectToRoute('budget');
  447. }
  448. $pdfFile = $invoiceRef . '.pdf';
  449. $nameToBadget = $invoiceRef;
  450. $pdfPathAbsolute = $invoiceDirectory . '/' . $pdfFile; // Ruta absoluta para guardar el archivo
  451. $pdfPathRelative = 'facturas/' . $pdfFile; // Ruta relativa para el frontend o base de datos
  452. $data_productos = [];
  453. $data_ivas = [];
  454. foreach ($budgetAndProducts as $bproduct) {
  455. if (!array_filter($data_ivas, fn($iva) => $iva['id'] === $bproduct->getIva())) {
  456. $temp = array(
  457. 'id' => $bproduct->getIva(),
  458. 'valor' => 0,
  459. );
  460. array_push($data_ivas, $temp);
  461. }
  462. $temp = array(
  463. 'name' => $bproduct->getProduct()->getName(),
  464. 'price' => $bproduct->getPrice() / $bproduct->getQuantity(),
  465. 'total' => $bproduct->getPrice(),
  466. 'quantity' => $bproduct->getQuantity(),
  467. 'iva' => $bproduct->getIva(),
  468. );
  469. array_push($data_productos, $temp);
  470. }
  471. // Crear una instancia de TCPDF
  472. $pdf = new TCPDF();
  473. // Configurar el documento
  474. $pdf->SetCreator(PDF_CREATOR);
  475. $pdf->SetAuthor('Tu Nombre');
  476. $pdf->SetTitle('Título del Documento');
  477. $pdf->SetSubject('Asunto del Documento');
  478. $pdf->SetKeywords('TCPDF, PDF, ejemplo, tutorial');
  479. $imagePath = $datosEmpresa->getLogo();
  480. $imageFullPath = $this->getParameter('public') . $imagePath; // Construir ruta absoluta
  481. // Asegúrate de que el archivo exista
  482. if (!file_exists($imageFullPath)) {
  483. throw new \Exception("La imagen no se encuentra en la ruta: " . $imageFullPath);
  484. }
  485. // Desactiva el encabezado automático
  486. $pdf->setPrintHeader(false);
  487. // Configurar los márgenes y pie de página
  488. $pdf->setFooterMargin(10);
  489. $pdf->SetMargins(15, 27, 15);
  490. $pdf->setFooterData([0, 64, 0], [0, 64, 128]);
  491. // Añadir la página
  492. $pdf->AddPage();
  493. // Añadir manualmente el encabezado con la imagen y texto
  494. $pdf->Image($imageFullPath, 170, 5, 30, 30); // Ancho = 30 mm, Alto = 20 mm
  495. $pdf->SetFont('helvetica', 'B', 16);
  496. $pdf->SetXY(15, 10); // Coordenadas iniciales para el texto
  497. $pdf->Cell(0, 10, 'Factura', 0, 1, 'L');
  498. $pdf->SetFont('helvetica', '', 12);
  499. $pdf->SetXY(15, 18);
  500. $pdf->Cell(0, 10, 'Nº ' . $nameToBadget, 0, 1, 'L');
  501. // Añadir una línea para dividir el encabezado del contenido
  502. $pdf->SetDrawColor(169, 169, 169); // Color negro
  503. $pdf->SetLineWidth(0.3); // Grosor de la línea
  504. $pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
  505. // Continúa con el contenido del PDF...
  506. $pdf->SetFont('helvetica', '', 10);
  507. $pdf->SetXY(135, 35);
  508. $pdf->MultiCell(120, 5, $datosEmpresa->getName() . "\n" .
  509. $datosEmpresa->getDir() . "\n" .
  510. $datosEmpresa->getCif(), 0, 'J', false, 1);
  511. // Configuración inicial
  512. $pdf->SetFont('helvetica', '', 10);
  513. $pdf->SetDrawColor(169, 169, 169); // Gris claro para la línea debajo de los títulos
  514. // Dividir el ancho de la página para tres columnas con márgenes
  515. $pageWidth = $pdf->GetPageWidth() - 20; // Ancho de la página menos márgenes
  516. $colWidth = $pageWidth / 3; // Ancho de cada columna
  517. $startX = 10; // Margen izquierdo
  518. $startY = $pdf->GetY(); // Posición inicial vertical
  519. // ----------------------------
  520. // Primera columna: FACTURAR A
  521. // ----------------------------
  522. $pdf->SetXY($startX, $startY); // Posición inicial
  523. $pdf->SetFont('helvetica', 'B', 10);
  524. $pdf->Cell($colWidth, 5, 'FACTURA A', 0, 1, 'L'); // Título
  525. $pdf->Line($startX, $pdf->GetY(), $startX + $colWidth, $pdf->GetY()); // Línea horizontal
  526. $pdf->Ln(3); // Espacio después de la línea
  527. // Datos del cliente
  528. $pdf->SetFont('helvetica', '', 10);
  529. $pdf->SetX($startX); // Mantenerse en la misma columna
  530. $pdf->MultiCell(
  531. $colWidth,
  532. 5,
  533. $cliente->getName() . "\n" .
  534. $cliente->getDir() . "\n" .
  535. $cliente->getTelf() . "\n" .
  536. $cliente->getEmail() . "\n" .
  537. $cliente->getNif(),
  538. 0,
  539. 'L'
  540. );
  541. // ----------------------------
  542. // Segunda columna: DETALLES
  543. // ----------------------------
  544. $pdf->SetXY($startX + $colWidth, $startY); // Moverse a la segunda columna
  545. $pdf->SetFont('helvetica', 'B', 10);
  546. $pdf->Cell($colWidth, 5, 'DETALLES', 0, 1, 'L'); // Título
  547. $pdf->Line($startX + $colWidth, $pdf->GetY(), $startX + (2 * $colWidth), $pdf->GetY()); // Línea horizontal
  548. $pdf->Ln(3); // Espacio después de la línea
  549. // Detalles del proyecto
  550. $pdf->SetFont('helvetica', '', 10);
  551. $pdf->SetX($startX + $colWidth); // Mantenerse en la columna
  552. $pdf->MultiCell($colWidth, 5, $concepto, 0, 'L');
  553. // ----------------------------
  554. // Tercera columna: PAGO
  555. // ----------------------------
  556. $pdf->SetXY($startX + (2 * $colWidth), $startY); // Moverse a la tercera columna
  557. $pdf->SetFont('helvetica', 'B', 10);
  558. $pdf->Cell($colWidth, 5, 'FECHA', 0, 1, 'L'); // Título
  559. $pdf->Line($startX + (2 * $colWidth), $pdf->GetY(), $startX + (3 * $colWidth), $pdf->GetY()); // Línea horizontal
  560. $pdf->Ln(3); // Espacio después de la línea
  561. // Información de pago
  562. $pdf->SetFont('helvetica', '', 10);
  563. $pdf->SetX($startX + (2 * $colWidth)); // Mantenerse en la columna
  564. $pdf->MultiCell(
  565. $colWidth,
  566. 5,
  567. "Fecha de emisión: " . $currentDate,
  568. 0,
  569. 'L'
  570. );
  571. // ----------------------------
  572. // Salto de línea después de las columnas
  573. // ----------------------------
  574. $pdf->Ln(20);
  575. // Establecer color gris para las líneas
  576. $pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
  577. // Encabezados de la tabla con fondo y texto en negrita
  578. $pdf->SetFont('helvetica', 'B', 12);
  579. $pdf->Cell(80, 10, 'CONCEPTO', 'T', 0, 'L'); // Solo línea superior
  580. $pdf->Cell(25, 10, 'CANT.', 'T', 0, 'C'); // Solo línea superior
  581. $pdf->Cell(30, 10, 'PRECIO', 'T', 0, 'C'); // Solo línea superior
  582. $pdf->Cell(30, 10, 'IMPORTE', 'T', 1, 'C'); // Solo línea superior y salto de línea
  583. // Recorrer el array y añadir contenido al PDF
  584. $totalBase = 0;
  585. $totalIVA = 0;
  586. foreach ($data_productos as $producto) {
  587. $subtotal = $producto['price'] * $producto['quantity'];
  588. $totalBase += $subtotal;
  589. // Actualizar el array de IVAs
  590. foreach ($data_ivas as &$iva) { // Usamos referencia para modificar directamente
  591. if ($iva['id'] === $producto['iva']) {
  592. $iva['valor'] += $subtotal * ($producto['iva'] / 100); // Sumar el IVA calculado
  593. break;
  594. }
  595. }
  596. unset($iva);
  597. // Nombre del artículo
  598. $pdf->SetFont('helvetica', '', 10); // Fuente normal para los productos
  599. $pdf->Cell(80, 10, $producto['name'], 'T', 0, 'L');
  600. $pdf->Cell(25, 10, $producto['quantity'], 'T', 0, 'C');
  601. $pdf->Cell(30, 10, number_format($producto['price'], 2, ',', '.') . ' €', 'T', 0, 'C');
  602. $pdf->Cell(30, 10, number_format($producto['total'], 2, ',', '.') . ' €', 'T', 1, 'C'); // Línea superior solo
  603. }
  604. $pdf->Ln();
  605. // Calcular IVA y Total
  606. foreach ($data_ivas as $iva) {
  607. $totalIVA += floatval($iva['valor']);
  608. }
  609. // Calcular IVA y Total
  610. $totalFinal = $totalBase + $totalIVA;
  611. // Establecer color gris para las líneas
  612. $pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
  613. // Mostrar resumen de precios al final del PDF (sin líneas verticales)
  614. $pdf->SetFont('helvetica', '', 10);
  615. // Establecer color gris para las líneas horizontales
  616. $pdf->SetDrawColor(169, 169, 169); // Gris claro
  617. // Reducir la altura de las celdas
  618. $cellHeight = 8; // Altura de las celdas ajustada
  619. // Ancho total restringido para las líneas horizontales
  620. $lineWidth = 80; // Define el ancho total de las celdas restringido a la derecha
  621. // Subtotal
  622. $pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
  623. $pdf->Cell($lineWidth, $cellHeight, 'Subtotal', 'T', 0, 'R'); // Línea superior ajustada al ancho
  624. $pdf->Cell(30, $cellHeight, number_format($totalBase, 2) . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
  625. // IVA
  626. foreach ($data_ivas as $iva) {
  627. $pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
  628. $pdf->Cell($lineWidth, $cellHeight, 'IVA (' . $iva['id'] . '%)', 'T', 0, 'R'); // Mostrar el porcentaje de IVA
  629. $pdf->Cell(30, $cellHeight, number_format($iva['valor'], 2, ',', '.') . ' €', 'T', 1, 'R'); // Mostrar el valor del IVA
  630. }
  631. // Total a pagar
  632. $pdf->SetFont('helvetica', 'B', 12); // Negrita para el total
  633. $pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
  634. $pdf->Cell($lineWidth, $cellHeight, 'Total a pagar', 'T', 0, 'R'); // Línea superior ajustada al ancho
  635. $pdf->Cell(30, $cellHeight, number_format($totalFinal, 2) . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
  636. $pdf->Ln(20);
  637. // Establecer color gris para las líneas
  638. $pdf->SetDrawColor(169, 169, 169); // Color negro
  639. $pdf->SetLineWidth(0.3); // Grosor de la línea
  640. $pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
  641. $pieFactura = $datosEmpresa->getPieFactu();
  642. $pdf->SetFont('helvetica', '', 8);
  643. $pdf->Ln(20);
  644. $pdf->MultiCell(
  645. 190, // Ancho de la celda (ajústalo según el tamaño de tu página)
  646. 10, // Altura mínima de la celda
  647. $pieFactura, // Contenido del pie
  648. 'T', // Borde superior (T) para marcar el inicio del pie
  649. 'L', // Alineación a la izquierda
  650. false // No rellenar la celda
  651. );
  652. $budgetsDirectory = $this->getParameter('budgets_directory');
  653. // Guardar el PDF en el servidor
  654. $pdf->Output($pdfPathAbsolute, 'F');
  655. $newFactura = new Tickettofactura();
  656. $newFactura->setRuta($pdfPathRelative);
  657. $newFactura->setRef($invoiceRef); // Usar la referencia generada de forma segura
  658. $newFactura->setFecha(new \DateTime());
  659. $newFactura->setCustomer($presupuesto->getCustomer());
  660. $newFactura->setEstado($this->entityManager->getRepository(Estadosfactu::class)->find(2));
  661. $newFactura->setBase($totalBase);
  662. $newFactura->setIva($totalIVA);
  663. $newFactura->setTotal($totalFinal);
  664. $this->entityManager->persist($newFactura);
  665. $this->entityManager->flush();
  666. // Asegurar que el archivo se haya creado correctamente
  667. if (file_exists($pdfPathRelative)) {
  668. // Guarda el mensaje de éxito en la sesión (opcional, si aún necesitas flashes para redirección en otras partes)
  669. // $this->addFlash('success', 'Presupuesto guardado correctamente.');
  670. // Devuelve un JSON en lugar de redirigir
  671. return new JsonResponse([
  672. 'success' => true,
  673. 'message' => 'Factura generada correctamente.',
  674. 'redirectUrl' => $this->generateUrl('presupuesto'), // Si necesitas una URL para redirección manual
  675. ], Response::HTTP_OK);
  676. } else {
  677. return new JsonResponse([
  678. 'success' => false,
  679. 'message' => 'Hubo un error al guardar el PDF.',
  680. ], Response::HTTP_INTERNAL_SERVER_ERROR);
  681. }
  682. // Este return nunca se ejecutará, puede ser eliminado
  683. return new JsonResponse(['success' => true, 'message' => 'Presupuesto guardado correctamente'], 200);
  684. } else {
  685. return new JsonResponse(['success' => false, 'message' => 'Este presupuesto ya tiene una Factura asignada'], 409); // 409 Conflict
  686. }
  687. }
  688. /**
  689. * @Route("/presupuesto/{id}", name="presupuesto_show")
  690. */
  691. public function show($id): Response
  692. {
  693. // Buscar el presupuesto en la base de datos usando el ID
  694. $presupuesto = $this->entityManager->getRepository(Presupuesto::class)->find($id);
  695. // Verificar si el presupuesto fue encontrado
  696. if (!$presupuesto) {
  697. throw $this->createNotFoundException('Presupuesto no encontrado');
  698. }
  699. $array_presu = array(
  700. "id" => $presupuesto->getId(),
  701. "name" => $presupuesto->getName(),
  702. "ruta" => $presupuesto->getPathPdf(),
  703. "estado" => $presupuesto->getEstado()->getName()
  704. );
  705. return new Response(
  706. $this->renderView('presupuesto/show.html.twig', [
  707. 'presupuesto' => $array_presu
  708. ]),
  709. 200,
  710. ['Content-Type' => 'text/vnd.turbo-stream.html']
  711. );
  712. }
  713. /**
  714. * @Route("/setstatepresu", name="set_state_presu", methods={"POST"})
  715. */
  716. public function setState(Request $request)
  717. {
  718. $data = json_decode($request->getContent(), true);
  719. $id = $data['id'];
  720. $state = $data['value'];
  721. // Buscar el usuario por el correo electrónico
  722. $presupuesto = $this->entityManager->getRepository(Presupuesto::class)->find($id);
  723. $estado = $this->entityManager->getRepository(Estadospresu::class)->findOneBy(['name' => $state]);
  724. $presupuesto->setEstado($estado);
  725. $this->entityManager->persist($presupuesto);
  726. $this->entityManager->flush();
  727. // return $this->redirectToRoute('tickettofactura');
  728. return new JsonResponse([
  729. 'texto' => 'Actualizado con éxito',
  730. ], 200);
  731. }
  732. /**
  733. * @Route("/updatepresupuesto", name="update_presupuesto", methods={"POST"})
  734. */
  735. public function updatePresupuesto(Request $request): Response
  736. {
  737. // Recuperamos datos para modificar el presupuesto
  738. $datos = json_decode($request->getContent(), true);
  739. $presupuestoId = $datos['presupuestoId']; // ID del presupuesto
  740. $elementos = $datos['elementos'];
  741. $idPresu = $datos['idpresu'];
  742. $fecha = $datos['fecha'];
  743. $currentDate = (new DateTime($fecha))->format('d/m/Y');
  744. $nextMonthDate = (new DateTime($fecha))->modify('+1 month')->format('d/m/Y');
  745. $concepto = $datos['concepto'];
  746. $datosEmpresa = $this->entityManager->getRepository(Info::class)->findOneBy(['id' => 1]);
  747. $presupuesto = $this->entityManager->getRepository(Presupuesto::class)->find($idPresu);
  748. $budgetProducts = $this->entityManager->getRepository(Budgetproduct::class)->findBy(['budget' => $presupuesto->getId()]);
  749. $cliente = $this->entityManager->getRepository(Proveedores::class)->findOneBy(['id' => $presupuesto->getCustomer()]);
  750. $data_productos = [];
  751. foreach ($elementos as $index => $elemento) {
  752. $budgetProduct = $budgetProducts[$index];
  753. $producto = $this->entityManager->getRepository(Productos::class)->findOneBy(['name' => $elemento['name']]);
  754. $quantity = $elemento['quantity'];
  755. $price = $elemento['price'];
  756. // Establecemos los nuevos valores en la línea de presupuesto
  757. $budgetProduct->setProduct($producto); // Asignamos el producto relacionado
  758. $budgetProduct->setQuantity($quantity); // Asignamos la cantidad
  759. $budgetProduct->setPrice($price);
  760. $temp = array(
  761. 'name' => $producto->getName(),
  762. 'price' => $price,
  763. 'quantity' => $quantity,
  764. );
  765. array_push($data_productos, $temp);
  766. }
  767. // Ruta completa al archivo en la carpeta "public/presupuesto"
  768. $rutaArchivo = $this->getParameter('kernel.project_dir') . '/public/' . $presupuesto->getPathPdf();
  769. // Instancia del componente Filesystem de Symfony
  770. $filesystem = new Filesystem();
  771. try {
  772. // Verifica si el archivo existe
  773. if ($filesystem->exists($rutaArchivo)) {
  774. // Elimina el archivo
  775. $filesystem->remove($rutaArchivo);
  776. } else {
  777. // Devuelve una respuesta indicando que el archivo no existe
  778. return new JsonResponse([
  779. 'error' => 'El archivo no existe.',
  780. ], Response::HTTP_NOT_FOUND);
  781. }
  782. } catch (IOExceptionInterface $exception) {
  783. // Maneja errores de entrada/salida al manipular el archivo
  784. return new JsonResponse([
  785. 'error' => 'Error al intentar eliminar el archivo: ' . $exception->getMessage(),
  786. ], Response::HTTP_INTERNAL_SERVER_ERROR);
  787. }
  788. $budgetsDirectory = $this->getParameter('budgets_directory');
  789. //Generar El nuevo presupuesto
  790. $pdfFile = $presupuesto->getName() . '.pdf';
  791. $nameToBadget = $presupuesto->getName();
  792. $pdfPathAbsolute = $budgetsDirectory . '/' . $pdfFile; // Ruta absoluta para guardar el archivo
  793. $pdfPathRelative = 'presupuestos/' . $pdfFile; // Ruta relativa para el frontend o base de datos
  794. // Crear una instancia de TCPDF
  795. $pdf = new TCPDF();
  796. // Configurar el documento
  797. $pdf->SetCreator(PDF_CREATOR);
  798. $pdf->SetAuthor('Tu Nombre');
  799. $pdf->SetTitle('Título del Documento');
  800. $pdf->SetSubject('Asunto del Documento');
  801. $pdf->SetKeywords('TCPDF, PDF, ejemplo, tutorial');
  802. $imagePath = $datosEmpresa->getLogo();
  803. $imageFullPath = $this->getParameter('public') . $imagePath; // Construir ruta absoluta
  804. // Asegúrate de que el archivo exista
  805. if (!file_exists($imageFullPath)) {
  806. throw new \Exception("La imagen no se encuentra en la ruta: " . $imageFullPath);
  807. }
  808. // Desactiva el encabezado automático
  809. $pdf->setPrintHeader(false);
  810. // Configurar los márgenes y pie de página
  811. $pdf->setFooterMargin(10);
  812. $pdf->SetMargins(15, 27, 15);
  813. $pdf->setFooterData([0, 64, 0], [0, 64, 128]);
  814. // Añadir la página
  815. $pdf->AddPage();
  816. // Añadir manualmente el encabezado con la imagen y texto
  817. $pdf->Image($imageFullPath, 170, 5, 30, 30); // Ancho = 30 mm, Alto = 20 mm
  818. $pdf->SetFont('helvetica', 'B', 16);
  819. $pdf->SetXY(15, 10); // Coordenadas iniciales para el texto
  820. $pdf->Cell(0, 10, 'Presupuesto', 0, 1, 'L');
  821. $pdf->SetFont('helvetica', '', 12);
  822. $pdf->SetXY(15, 18);
  823. $pdf->Cell(0, 10, 'Nº ' . $nameToBadget, 0, 1, 'L');
  824. // Añadir una línea para dividir el encabezado del contenido
  825. $pdf->SetDrawColor(169, 169, 169); // Color negro
  826. $pdf->SetLineWidth(0.3); // Grosor de la línea
  827. $pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
  828. // Continúa con el contenido del PDF...
  829. $pdf->SetFont('helvetica', '', 10);
  830. $pdf->SetXY(140, 35);
  831. $pdf->MultiCell(120, 5, $datosEmpresa->getName() . "\n" .
  832. $datosEmpresa->getDir() . "\n" .
  833. $datosEmpresa->getEmail() . "\n" .
  834. $datosEmpresa->getTelf() . "\n" .
  835. $datosEmpresa->getCif(), 0, 'J', false, 1);
  836. // Configuración inicial
  837. $pdf->SetFont('helvetica', '', 10);
  838. $pdf->SetDrawColor(169, 169, 169); // Gris claro para la línea debajo de los títulos
  839. // Dividir el ancho de la página para tres columnas con márgenes
  840. $pageWidth = $pdf->GetPageWidth() - 20; // Ancho de la página menos márgenes
  841. $colWidth = $pageWidth / 3; // Ancho de cada columna
  842. $startX = 10; // Margen izquierdo
  843. $startY = $pdf->GetY(); // Posición inicial vertical
  844. // ----------------------------
  845. // Primera columna: FACTURAR A
  846. // ----------------------------
  847. $pdf->SetXY($startX, $startY); // Posición inicial
  848. $pdf->SetFont('helvetica', 'B', 10);
  849. $pdf->Cell($colWidth, 5, 'PRESUPUESTO A', 0, 1, 'L'); // Título
  850. $pdf->Line($startX, $pdf->GetY(), $startX + $colWidth, $pdf->GetY()); // Línea horizontal
  851. $pdf->Ln(3); // Espacio después de la línea
  852. // Datos del cliente
  853. $pdf->SetFont('helvetica', '', 10);
  854. $pdf->SetX($startX); // Mantenerse en la misma columna
  855. $pdf->MultiCell(
  856. $colWidth,
  857. 5,
  858. $cliente->getName() . "\n" .
  859. $cliente->getDir() . "\n" .
  860. $cliente->getTelf() . "\n" .
  861. $cliente->getEmail() . "\n" .
  862. $cliente->getNif(),
  863. 0,
  864. 'L'
  865. );
  866. // ----------------------------
  867. // Segunda columna: DETALLES
  868. // ----------------------------
  869. $pdf->SetXY($startX + $colWidth, $startY); // Moverse a la segunda columna
  870. $pdf->SetFont('helvetica', 'B', 10);
  871. $pdf->Cell($colWidth, 5, 'DETALLES', 0, 1, 'L'); // Título
  872. $pdf->Line($startX + $colWidth, $pdf->GetY(), $startX + (2 * $colWidth), $pdf->GetY()); // Línea horizontal
  873. $pdf->Ln(3); // Espacio después de la línea
  874. // Detalles del proyecto
  875. $pdf->SetFont('helvetica', '', 10);
  876. $pdf->SetX($startX + $colWidth); // Mantenerse en la columna
  877. $pdf->MultiCell($colWidth, 5, $concepto, 0, 'L');
  878. // ----------------------------
  879. // Tercera columna: PAGO
  880. // ----------------------------
  881. $pdf->SetXY($startX + (2 * $colWidth), $startY); // Moverse a la tercera columna
  882. $pdf->SetFont('helvetica', 'B', 10);
  883. $pdf->Cell($colWidth, 5, 'PLAZO', 0, 1, 'L'); // Título
  884. $pdf->Line($startX + (2 * $colWidth), $pdf->GetY(), $startX + (3 * $colWidth), $pdf->GetY()); // Línea horizontal
  885. $pdf->Ln(3); // Espacio después de la línea
  886. // Información de pago
  887. $pdf->SetFont('helvetica', '', 10);
  888. $pdf->SetX($startX + (2 * $colWidth)); // Mantenerse en la columna
  889. $pdf->MultiCell(
  890. $colWidth,
  891. 5,
  892. "Fecha de emisión: " . $currentDate . "\n" .
  893. "Fecha de vencimiento: " . $nextMonthDate,
  894. 0,
  895. 'L'
  896. );
  897. // ----------------------------
  898. // Salto de línea después de las columnas
  899. // ----------------------------
  900. $pdf->Ln(20);
  901. // Establecer color gris para las líneas
  902. $pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
  903. // Encabezados de la tabla con fondo y texto en negrita
  904. $pdf->SetFont('helvetica', 'B', 12);
  905. $pdf->Cell(80, 10, 'CONCEPTO', 'T', 0, 'L'); // Solo línea superior
  906. $pdf->Cell(25, 10, 'CANT.', 'T', 0, 'C'); // Solo línea superior
  907. $pdf->Cell(30, 10, 'PRECIO', 'T', 0, 'C'); // Solo línea superior
  908. $pdf->Cell(30, 10, 'IMPORTE', 'T', 1, 'C'); // Solo línea superior y salto de línea
  909. // Recorrer el array y añadir contenido al PDF
  910. $totalBase = 0;
  911. $totalIVA = 0;
  912. $iva = 0.21; // 21%
  913. foreach ($data_productos as $producto) {
  914. $subtotal = $producto['price'] * $producto['quantity'];
  915. $totalBase += $subtotal;
  916. // Nombre del artículo
  917. $pdf->SetFont('helvetica', '', 10); // Fuente normal para los productos
  918. $pdf->Cell(80, 10, $producto['name'], 'T', 0, 'L');
  919. $pdf->Cell(25, 10, $producto['quantity'], 'T', 0, 'C');
  920. $pdf->Cell(30, 10, number_format($producto['price'], 2, ',', '.') . ' €', 'T', 0, 'C');
  921. $pdf->Cell(30, 10, number_format($subtotal, 2, ',', '.') . ' €', 'T', 1, 'C'); // Línea superior solo
  922. }
  923. $pdf->Ln();
  924. // Calcular IVA y Total
  925. $totalIVA = $totalBase * $iva;
  926. $totalFinal = $totalBase + $totalIVA;
  927. // Establecer color gris para las líneas
  928. $pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
  929. // Mostrar resumen de precios al final del PDF (sin líneas verticales)
  930. $pdf->SetFont('helvetica', '', 10);
  931. // Establecer color gris para las líneas horizontales
  932. $pdf->SetDrawColor(169, 169, 169); // Gris claro
  933. // Reducir la altura de las celdas
  934. $cellHeight = 8; // Altura de las celdas ajustada
  935. // Ancho total restringido para las líneas horizontales
  936. $lineWidth = 80; // Define el ancho total de las celdas restringido a la derecha
  937. // Subtotal
  938. $pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
  939. $pdf->Cell($lineWidth, $cellHeight, 'Subtotal', 'T', 0, 'R'); // Línea superior ajustada al ancho
  940. $pdf->Cell(30, $cellHeight, number_format($totalBase, 2, ',', '.') . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
  941. // IVA
  942. $pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
  943. $pdf->Cell($lineWidth, $cellHeight, 'IVA (21%)', 'T', 0, 'R'); // Línea superior ajustada al ancho
  944. $pdf->Cell(30, $cellHeight, number_format($totalIVA, 2, ',', '.') . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
  945. // Total a pagar
  946. $pdf->SetFont('helvetica', 'B', 12); // Negrita para el total
  947. $pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
  948. $pdf->Cell($lineWidth, $cellHeight, 'Total a pagar', 'T', 0, 'R'); // Línea superior ajustada al ancho
  949. $pdf->Cell(30, $cellHeight, number_format($totalFinal, 2, ',', '.') . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
  950. $pdf->Ln(20);
  951. // Establecer color gris para las líneas
  952. $pdf->SetDrawColor(169, 169, 169); // Color negro
  953. $pdf->SetLineWidth(0.3); // Grosor de la línea
  954. $pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
  955. $pieFactura = $datosEmpresa->getPiePresu();
  956. $pdf->SetFont('helvetica', '', 8);
  957. $pdf->Ln(20);
  958. $pdf->MultiCell(
  959. 190, // Ancho de la celda (ajústalo según el tamaño de tu página)
  960. 10, // Altura mínima de la celda
  961. $pieFactura, // Contenido del pie
  962. 'T', // Borde superior (T) para marcar el inicio del pie
  963. 'L', // Alineación a la izquierda
  964. false // No rellenar la celda
  965. );
  966. // Guardar el PDF en el servidor
  967. $pdf->Output($pdfPathAbsolute, 'F');
  968. // $newPresupuesto->setPathPdf($pdfPathRelative);
  969. $presupuesto->setBase($totalBase);
  970. $presupuesto->setIva($totalIVA);
  971. $presupuesto->setTotal($totalFinal);
  972. $this->entityManager->persist($presupuesto);
  973. $this->entityManager->flush();
  974. // Asegurar que el archivo se haya creado correctamente
  975. if (file_exists($pdfPathRelative)) {
  976. // Guardar el mensaje de éxito en la sesión
  977. $this->addFlash('success', 'Presupuesto guardado correctamente.');
  978. // Redireccionar a la página de presupuestos (index)
  979. return $this->redirectToRoute('presupuesto');
  980. } else {
  981. return new Response("Hubo un error al guardar el PDF.", Response::HTTP_INTERNAL_SERVER_ERROR);
  982. }
  983. return $this->redirectToRoute('presupuesto');
  984. }
  985. /**
  986. * @Route("/deletepresuesto", name="delete_presupuesto")
  987. */
  988. public function deletePresupuesto(Request $request): Response
  989. {
  990. $presupuesto = $this->entityManager->getRepository(Presupuesto::class)->find($request->query->get('id'));
  991. $rutaArchivo = $this->getParameter('budgets_directory') . basename($presupuesto->getPathPdf());
  992. if ($this->filesystem->exists($rutaArchivo)) {
  993. $this->filesystem->remove($rutaArchivo);
  994. $this->entityManager->remove($presupuesto);
  995. $this->entityManager->flush();
  996. // Mensaje de éxito
  997. return $this->json([
  998. 'success' => true,
  999. 'factura' => 'Presupuesto eliminado con éxito.',
  1000. ]);
  1001. }
  1002. return new Response('No se ha podido eliminar el archivo');
  1003. }
  1004. }