<?php
namespace App\Controller;
use App\Entity\Budgetproduct;
use App\Entity\Estadosfactu;
use App\Entity\Estadospresu;
use App\Entity\Info;
use App\Entity\Presupuesto;
use App\Entity\Productos;
use App\Entity\Proveedores;
use App\Entity\Tickettofactura;
use App\Entity\TypeIva;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\UX\Turbo\TurboBundle;
use Doctrine\ORM\EntityManagerInterface;
use DateTime;
use TCPDF;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
class PresupuestoController extends AbstractController
{
private array $products = [];
private $entityManager;
private $filesystem;
public function __construct(EntityManagerInterface $entityManager, Filesystem $filesystem)
{
$this->entityManager = $entityManager;
$this->filesystem = $filesystem;
}
/**
* @Route("/", name="presupuesto")
* @IsGranted("ROLE_USER")
*/
public function index(): Response
{
$presupuestos = $this->entityManager->getRepository(Presupuesto::class)->findAll();
$productos = $this->entityManager->getRepository(Productos::class)->findAll();
$info = $this->entityManager->getRepository(Info::class)->findOneBy(['id' => 1]);
$infoNotFill = false;
if (empty($info)) {
$infoNotFill = true;
}
$data = array();
foreach ($presupuestos as $presupuesto) {
$data_elements = array();
$presuxproducts = $presupuesto->getBudgetproducts();
foreach ($presuxproducts as $element) {
$temp = array(
'id' => $element->getId(),
'name' => $element->getProduct()->getName(),
'quantity' => $element->getQuantity(),
'unit_price' => $element->getPrice() / $element->getQuantity(),
'price' => $element->getPrice(),
);
array_push($data_elements, $temp);
}
$temp = array(
'id' => $presupuesto->getId(),
'name' => $presupuesto->getName(),
'concept' => $presupuesto->getConcept(),
'date' => $presupuesto->getDate()->format('d/m/Y'),
'customer' => $presupuesto->getCustomer()->getName(),
'ruta' => $presupuesto->getPathPdf(),
'statuspresu' => $presupuesto->getEstado()->getName(),
'elements' => $data_elements
);
array_push($data, $temp);
}
$data_productos = array();
foreach ($productos as $producto) {
$temp = array(
'id' => $producto->getId(),
'name' => $producto->getName(),
'price' => $producto->getPrice(),
);
array_push($data_productos, $temp);
}
$clientes = $this->entityManager->getRepository(Proveedores::class)->findAll();
$data_clientes = array();
foreach ($clientes as $cliente) {
$temp = array(
'id' => $cliente->getId(),
'name' => $cliente->getName(),
);
array_push($data_clientes, $temp);
}
$ivas = $this->entityManager->getRepository(TypeIva::class)->findAll();
$data_ivas = array();
foreach ($ivas as $iva) {
$temp = array(
'id' => $iva->getId(),
'quantity' => $iva->getQuantity() . '%',
);
array_push($data_ivas, $temp);
};
return $this->render('presupuesto/index.html.twig', [
'controller_name' => 'PresupuestoController',
'products' => $this->products,
'data' => array_reverse($data),
'data_productos' => $data_productos,
'clientes' => $data_clientes,
'infoNotFill' => $infoNotFill,
'ivas' => $data_ivas,
]);
}
/**
* @Route("/budget/add_product", name="budget_add_product", methods={"POST"})
*/
public function addProduct(Request $request): Response
{
$id = $request->request->get('id');
$name = $request->request->get('name');
$price = (float) $request->request->get('price');
$quantity = (int) $request->request->get('quantity');
$iva = (int) $request->request->get('iva');
$total = $price * $quantity;
$this->products[] = ['name' => $name, 'price' => $total, 'quantity' => $quantity, 'iva' => $iva];
$uniqueId = uniqid('', true);
// Si es una petición Turbo Stream, enviamos solo el stream de respuesta
if (TurboBundle::STREAM_FORMAT === $request->getPreferredFormat()) {
return new Response(
$this->renderView('presupuesto/_product_stream.html.twig', ['product' => ['id' => $id, 'name' => $name, 'price' => $total, 'quantity' => $quantity, 'uniqueId' => $uniqueId, 'iva' => $iva]]),
200,
['Content-Type' => 'text/vnd.turbo-stream.html']
);
}
return $this->redirectToRoute('budget');
}
/**
* @Route("/budget/save", name="budget_save", methods={"POST"})
*/
public function saveBudget(Request $request): Response
{
// Aquí manejarás el presupuesto enviado por el usuario
$products = json_decode($request->request->get('products'));
$concepto = $request->request->get('concepto');
$cliente = $this->entityManager->getRepository(Proveedores::class)->findOneBy(['id' => $request->request->get('cliente')]);
$datosEmpresa = $this->entityManager->getRepository(Info::class)->findOneBy(['id' => 1]);
$pdfBase64 = $request->request->get('pdf');
// Decodificar el contenido en base64 y guardar el archivo en el servidor
$pdfContent = base64_decode($pdfBase64);
$currentYear = (new DateTime())->format('Y'); // Obtener el año actual
$currentDate = (new DateTime())->format('d/m/Y');
$nextMonthDate = (new DateTime())->modify('+1 month')->format('d/m/Y'); // Fecha con un mes más
$totalPresupuestos = $this->entityManager->getRepository(Presupuesto::class)->findAll();
// Directorio donde guardar el PDF
$budgetsDirectory = $this->getParameter('budgets_directory');
$filesystem = new Filesystem();
// Asegúrate de que el directorio tiene permisos de escritura
if (!$filesystem->exists($budgetsDirectory)) {
try {
$filesystem->mkdir($budgetsDirectory, 0755
);
} catch (IOExceptionInterface $exception) {
return new JsonResponse(['error' => 'Error al crear la carpeta de subidas.'], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
$pdfFile = 'P' . $currentYear . (count($totalPresupuestos) + 1) . '.pdf';
$nameToBadget = 'P' . $currentYear . (count($totalPresupuestos) + 1);
$pdfPathAbsolute = $budgetsDirectory . '/' . $pdfFile; // Ruta absoluta para guardar el archivo
$pdfPathRelative = 'presupuestos/' . $pdfFile; // Ruta relativa para el frontend o base de datos
$newPresupuesto = new Presupuesto();
$newPresupuesto->setName('P' . $currentYear . (count($totalPresupuestos) + 1));
$newPresupuesto->setDate(new \DateTime());
$newPresupuesto->setConcept($concepto);
$newPresupuesto->setCustomer($cliente);
$newPresupuesto->setEstado($this->entityManager->getRepository(Estadospresu::class)->find(1));
$this->entityManager->persist($newPresupuesto);
// $this->entityManager->flush();
$data_productos = [];
$data_ivas = [];
foreach ($products as $product) {
$budgetProduct = new Budgetproduct();
$producto = $this->entityManager->getRepository(Productos::class)->findOneBy(['id' => $product->id]);
$budgetProduct->setBudget($newPresupuesto);
$budgetProduct->setProduct($producto);
$budgetProduct->setQuantity($product->quantity);
$budgetProduct->setPrice($product->price);
$budgetProduct->setIva($product->iva);
if (!array_filter($data_ivas, fn($iva) => $iva['id'] === $product->iva)) {
$temp = array(
'id' => $product->iva,
'valor' => 0,
);
array_push($data_ivas, $temp);
}
$temp = array(
'name' => $producto->getName(),
'price' => $product->price / $product->quantity,
'quantity' => $product->quantity,
'iva' => $product->iva,
);
array_push($data_productos, $temp);
$this->entityManager->persist($budgetProduct);
// $this->entityManager->flush();
}
// Crear una instancia de TCPDF
$pdf = new TCPDF();
// Configurar el documento
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('W3Barcelona SL');
$pdf->SetTitle('Factura');
$pdf->SetSubject('Asunto del Documento');
$pdf->SetKeywords('TCPDF, PDF, ejemplo, tutorial');
$imagePath = $datosEmpresa->getLogo();
$imageFullPath = $this->getParameter('public') . $imagePath; // Construir ruta absoluta
// Asegúrate de que el archivo exista
if (!file_exists($imageFullPath)) {
throw new \Exception("La imagen no se encuentra en la ruta: " . $imageFullPath);
}
// Desactiva el encabezado automático
$pdf->setPrintHeader(false);
// Configurar los márgenes y pie de página
$pdf->setFooterMargin(10);
$pdf->SetMargins(15, 27, 15);
$pdf->setFooterData([0, 64, 0], [0, 64, 128]);
// Añadir la página
$pdf->AddPage();
// Añadir manualmente el encabezado con la imagen y texto
$pdf->Image($imageFullPath, 170, 5, 30, 30); // Ancho = 30 mm, Alto = 20 mm
$pdf->SetFont('helvetica', 'B', 16);
$pdf->SetXY(15, 10); // Coordenadas iniciales para el texto
$pdf->Cell(0, 10, 'Presupuesto', 0, 1, 'L');
$pdf->SetFont('helvetica', '', 12);
$pdf->SetXY(15, 18);
$pdf->Cell(0, 10, 'Nº ' . $nameToBadget, 0, 1, 'L');
// Añadir una línea para dividir el encabezado del contenido
$pdf->SetDrawColor(169, 169, 169); // Color negro
$pdf->SetLineWidth(0.3); // Grosor de la línea
$pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
// Continúa con el contenido del PDF...
$pdf->SetFont('helvetica', '', 10);
$pdf->SetXY(135, 35);
$pdf->MultiCell(120, 5, $datosEmpresa->getName() . "\n" .
$datosEmpresa->getDir() . "\n" .
$datosEmpresa->getCif(), 0, 'J', false, 1);
// Configuración inicial
$pdf->SetFont('helvetica', '', 10);
$pdf->SetDrawColor(169, 169, 169); // Gris claro para la línea debajo de los títulos
// Dividir el ancho de la página para tres columnas con márgenes
$pageWidth = $pdf->GetPageWidth() - 20; // Ancho de la página menos márgenes
$colWidth = $pageWidth / 3; // Ancho de cada columna
$startX = 10; // Margen izquierdo
$startY = $pdf->GetY(); // Posición inicial vertical
// ----------------------------
// Primera columna: FACTURAR A
// ----------------------------
$pdf->SetXY($startX, $startY); // Posición inicial
$pdf->SetFont('helvetica', 'B', 10);
$pdf->Cell($colWidth, 5, 'PRESUPUESTO A', 0, 1, 'L'); // Título
$pdf->Line($startX, $pdf->GetY(), $startX + $colWidth, $pdf->GetY()); // Línea horizontal
$pdf->Ln(3); // Espacio después de la línea
// Datos del cliente
$pdf->SetFont('helvetica', '', 10);
$pdf->SetX($startX); // Mantenerse en la misma columna
$pdf->MultiCell(
$colWidth,
5,
$cliente->getName() . "\n" .
$cliente->getDir() . "\n" .
$cliente->getNif(),
0,
'L'
);
// ----------------------------
// Segunda columna: DETALLES
// ----------------------------
$pdf->SetXY($startX + $colWidth, $startY); // Moverse a la segunda columna
$pdf->SetFont('helvetica', 'B', 10);
$pdf->Cell($colWidth, 5, 'DETALLES', 0, 1, 'L'); // Título
$pdf->Line($startX + $colWidth, $pdf->GetY(), $startX + (2 * $colWidth), $pdf->GetY()); // Línea horizontal
$pdf->Ln(3); // Espacio después de la línea
// Detalles del proyecto
$pdf->SetFont('helvetica', '', 10);
$pdf->SetX($startX + $colWidth); // Mantenerse en la columna
$pdf->MultiCell($colWidth, 5, $concepto, 0, 'L');
// ----------------------------
// Tercera columna: PAGO
// ----------------------------
$pdf->SetXY($startX + (2 * $colWidth), $startY); // Moverse a la tercera columna
$pdf->SetFont('helvetica', 'B', 10);
$pdf->Cell($colWidth, 5, 'PLAZO', 0, 1, 'L'); // Título
$pdf->Line($startX + (2 * $colWidth), $pdf->GetY(), $startX + (3 * $colWidth), $pdf->GetY()); // Línea horizontal
$pdf->Ln(3); // Espacio después de la línea
// Información de pago
$pdf->SetFont('helvetica', '', 10);
$pdf->SetX($startX + (2 * $colWidth)); // Mantenerse en la columna
$pdf->MultiCell(
$colWidth,
5,
"Fecha de emisión: " . $currentDate . "\n" .
"Fecha de vencimiento: " . $nextMonthDate,
0,
'L'
);
// ----------------------------
// Salto de línea después de las columnas
// ----------------------------
$pdf->Ln(20);
// Establecer color gris para las líneas
$pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
// Encabezados de la tabla con fondo y texto en negrita
$pdf->SetFont('helvetica', 'B', 12);
$pdf->Cell(55, 10, 'CONCEPTO', 'T', 0, 'L'); // Solo línea superior
$pdf->Cell(25, 10, 'CANT.', 'T', 0, 'C'); // Solo línea superior
$pdf->Cell(35, 10, 'PRECIO UNITARIO', 'T', 0, 'C'); // Solo línea superior
$pdf->Cell(30, 10, 'IVA (%)', 'T', 0, 'C'); // Solo línea superior
$pdf->Cell(30, 10, 'IMPORTE', 'T', 1, 'C'); // Solo línea superior y salto de línea
// Recorrer el array y añadir contenido al PDF
$totalBase = 0;
$totalIVA = 0;
foreach ($data_productos as $producto) {
$subtotal = $producto['price'] * $producto['quantity'];
$totalBase += $subtotal;
// Actualizar el array de IVAs
foreach ($data_ivas as &$iva) { // Usamos referencia para modificar directamente
if ($iva['id'] === $producto['iva']) {
$iva['valor'] += $subtotal * ($producto['iva'] / 100); // Sumar el IVA calculado
break;
}
}
unset($iva);
// Nombre del artículo
$pdf->SetFont('helvetica', '', 10); // Fuente normal para los productos
// Guardar la posición Y antes de la celda MultiCell
$yPos = $pdf->GetY();
// MultiCell para el concepto, de modo que el texto se ajuste automáticamente a varias líneas
$pdf->MultiCell(55, 10, $producto['name'], 'T', 'L');
// Ajustamos la posición Y después del MultiCell
$height = $pdf->GetY() - $yPos; // Obtener la altura del contenido generado por MultiCell
$pdf->SetY($yPos); // Restaurar la posición Y al inicio de la fila
// Añadir las otras celdas normalmente con la altura ajustada
$pdf->Cell(55, $height, '', 0, 0); // Espacio vacío para la celda del concepto
$pdf->Cell(25, 10, $producto['quantity'], 'T', 0, 'C');
$pdf->Cell(35, 10, number_format($producto['price'], 2, ',', '.') . ' €', 'T', 0, 'C');
$pdf->Cell(30, 10, $producto['iva'] . ' %', 'T', 0, 'C');
$pdf->Cell(30, 10, number_format($subtotal, 2, ',', '.') . ' €', 'T', 1, 'C'); // Línea superior solo
}
$pdf->Ln();
// Calcular IVA y Total
foreach($data_ivas as $iva){
$totalIVA += floatval($iva['valor']);
}
$totalFinal = $totalBase + $totalIVA;
// Establecer color gris para las líneas
$pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
// Mostrar resumen de precios al final del PDF (sin líneas verticales)
$pdf->SetFont('helvetica', '', 10);
// Establecer color gris para las líneas horizontales
$pdf->SetDrawColor(169, 169, 169); // Gris claro
// Reducir la altura de las celdas
$cellHeight = 8; // Altura de las celdas ajustada
// Ancho total restringido para las líneas horizontales
$lineWidth = 80; // Define el ancho total de las celdas restringido a la derecha
// Subtotal
$pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
$pdf->Cell($lineWidth, $cellHeight, 'Subtotal', 'T', 0, 'R'); // Línea superior ajustada al ancho
$pdf->Cell(30, $cellHeight, number_format($totalBase, 2) . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
// IVA
foreach ($data_ivas as $iva) {
$pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
$pdf->Cell($lineWidth, $cellHeight, 'IVA (' . $iva['id'] . '%)', 'T', 0, 'R'); // Mostrar el porcentaje de IVA
$pdf->Cell(30, $cellHeight, number_format($iva['valor'], 2, ',', '.') . ' €', 'T', 1, 'R'); // Mostrar el valor del IVA
}
// Total a pagar
$pdf->SetFont('helvetica', 'B', 12); // Negrita para el total
$pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
$pdf->Cell($lineWidth, $cellHeight, 'Total a pagar', 'T', 0, 'R'); // Línea superior ajustada al ancho
$pdf->Cell(30, $cellHeight, number_format($totalFinal, 2, ',', '.') . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
$pdf->Ln(20);
// Establecer color gris para las líneas
$pdf->SetDrawColor(169, 169, 169); // Color negro
$pdf->SetLineWidth(0.3); // Grosor de la línea
$pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
$pieFactura = $datosEmpresa->getPiePresu();
$pdf->SetFont('helvetica', '', 8);
$pdf->Ln(20);
$pdf->MultiCell(
190, // Ancho de la celda (ajústalo según el tamaño de tu página)
10, // Altura mínima de la celda
$pieFactura, // Contenido del pie
'T', // Borde superior (T) para marcar el inicio del pie
'L', // Alineación a la izquierda
false // No rellenar la celda
);
$budgetsDirectory = $this->getParameter('budgets_directory');
// Guardar el PDF en el servidor
$pdf->Output($pdfPathAbsolute, 'F');
$newPresupuesto->setPathPdf($pdfPathRelative);
$newPresupuesto->setBase($totalBase);
$newPresupuesto->setIva($totalIVA);
$newPresupuesto->setTotal($totalFinal);
$this->entityManager->persist($budgetProduct);
$this->entityManager->flush();
// Asegurar que el archivo se haya creado correctamente
if (file_exists($pdfPathRelative)) {
// Guardar el mensaje de éxito en la sesión
$this->addFlash('success', 'Presupuesto guardado correctamente.');
// Redireccionar a la página de presupuestos (index)
return $this->redirectToRoute('presupuesto');
} else {
return new Response("Hubo un error al guardar el PDF.", Response::HTTP_INTERNAL_SERVER_ERROR);
}
return new Response('Presupuesto guardado correctamente', 200);
}
/**
* @Route("/createinvoice", name="create_invoice")
*/
public function createInvoice(Request $request): Response
{
// Aquí manejarás el presupuesto enviado por el usuario
$id = $request->query->get('id');
$presupuesto = $this->entityManager->getRepository(Presupuesto::class)->findOneBy(['id' => $id]);
if (!$presupuesto->isFactura()) {
$currentYear = (int)(new DateTime())->format('Y'); // Obtener el año actual como entero
$presupuesto->setFactura(1);
$cliente = $presupuesto->getCustomer();
$concepto = $presupuesto->getConcept();
$datosEmpresa = $this->entityManager->getRepository(Info::class)->findOneBy(['id' => 1]);
if (!$datosEmpresa) {
$this->addFlash('error', 'No se encontraron los datos de la empresa');
return $this->redirectToRoute('budget');
}
$budgetAndProducts = $presupuesto->getBudgetproducts();
$invoiceDirectory = $this->getParameter('invoice_directory');
$currentDate = (new DateTime())->format('d/m/Y');
// Obtener el siguiente número de factura de forma segura
$ticketRepository = $this->entityManager->getRepository(Tickettofactura::class);
$invoiceNumber = $ticketRepository->getNextInvoiceNumber($currentYear);
// Generar la referencia
$invoiceRef = 'F' . $currentYear . $invoiceNumber;
// Verificar que la referencia no exista (doble verificación para evitar condiciones de carrera)
$maxAttempts = 10; // Máximo de intentos para evitar loop infinito
$attempts = 0;
while ($ticketRepository->refExists($invoiceRef) && $attempts < $maxAttempts) {
$invoiceNumber++;
$invoiceRef = 'F' . $currentYear . $invoiceNumber;
$attempts++;
}
if ($attempts >= $maxAttempts) {
$this->addFlash('error', 'Error al generar la referencia de la factura. Por favor, intente nuevamente.');
return $this->redirectToRoute('budget');
}
$pdfFile = $invoiceRef . '.pdf';
$nameToBadget = $invoiceRef;
$pdfPathAbsolute = $invoiceDirectory . '/' . $pdfFile; // Ruta absoluta para guardar el archivo
$pdfPathRelative = 'facturas/' . $pdfFile; // Ruta relativa para el frontend o base de datos
$data_productos = [];
$data_ivas = [];
foreach ($budgetAndProducts as $bproduct) {
if (!array_filter($data_ivas, fn($iva) => $iva['id'] === $bproduct->getIva())) {
$temp = array(
'id' => $bproduct->getIva(),
'valor' => 0,
);
array_push($data_ivas, $temp);
}
$temp = array(
'name' => $bproduct->getProduct()->getName(),
'price' => $bproduct->getPrice() / $bproduct->getQuantity(),
'total' => $bproduct->getPrice(),
'quantity' => $bproduct->getQuantity(),
'iva' => $bproduct->getIva(),
);
array_push($data_productos, $temp);
}
// Crear una instancia de TCPDF
$pdf = new TCPDF();
// Configurar el documento
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('Tu Nombre');
$pdf->SetTitle('Título del Documento');
$pdf->SetSubject('Asunto del Documento');
$pdf->SetKeywords('TCPDF, PDF, ejemplo, tutorial');
$imagePath = $datosEmpresa->getLogo();
$imageFullPath = $this->getParameter('public') . $imagePath; // Construir ruta absoluta
// Asegúrate de que el archivo exista
if (!file_exists($imageFullPath)) {
throw new \Exception("La imagen no se encuentra en la ruta: " . $imageFullPath);
}
// Desactiva el encabezado automático
$pdf->setPrintHeader(false);
// Configurar los márgenes y pie de página
$pdf->setFooterMargin(10);
$pdf->SetMargins(15, 27, 15);
$pdf->setFooterData([0, 64, 0], [0, 64, 128]);
// Añadir la página
$pdf->AddPage();
// Añadir manualmente el encabezado con la imagen y texto
$pdf->Image($imageFullPath, 170, 5, 30, 30); // Ancho = 30 mm, Alto = 20 mm
$pdf->SetFont('helvetica', 'B', 16);
$pdf->SetXY(15, 10); // Coordenadas iniciales para el texto
$pdf->Cell(0, 10, 'Factura', 0, 1, 'L');
$pdf->SetFont('helvetica', '', 12);
$pdf->SetXY(15, 18);
$pdf->Cell(0, 10, 'Nº ' . $nameToBadget, 0, 1, 'L');
// Añadir una línea para dividir el encabezado del contenido
$pdf->SetDrawColor(169, 169, 169); // Color negro
$pdf->SetLineWidth(0.3); // Grosor de la línea
$pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
// Continúa con el contenido del PDF...
$pdf->SetFont('helvetica', '', 10);
$pdf->SetXY(135, 35);
$pdf->MultiCell(120, 5, $datosEmpresa->getName() . "\n" .
$datosEmpresa->getDir() . "\n" .
$datosEmpresa->getCif(), 0, 'J', false, 1);
// Configuración inicial
$pdf->SetFont('helvetica', '', 10);
$pdf->SetDrawColor(169, 169, 169); // Gris claro para la línea debajo de los títulos
// Dividir el ancho de la página para tres columnas con márgenes
$pageWidth = $pdf->GetPageWidth() - 20; // Ancho de la página menos márgenes
$colWidth = $pageWidth / 3; // Ancho de cada columna
$startX = 10; // Margen izquierdo
$startY = $pdf->GetY(); // Posición inicial vertical
// ----------------------------
// Primera columna: FACTURAR A
// ----------------------------
$pdf->SetXY($startX, $startY); // Posición inicial
$pdf->SetFont('helvetica', 'B', 10);
$pdf->Cell($colWidth, 5, 'FACTURA A', 0, 1, 'L'); // Título
$pdf->Line($startX, $pdf->GetY(), $startX + $colWidth, $pdf->GetY()); // Línea horizontal
$pdf->Ln(3); // Espacio después de la línea
// Datos del cliente
$pdf->SetFont('helvetica', '', 10);
$pdf->SetX($startX); // Mantenerse en la misma columna
$pdf->MultiCell(
$colWidth,
5,
$cliente->getName() . "\n" .
$cliente->getDir() . "\n" .
$cliente->getTelf() . "\n" .
$cliente->getEmail() . "\n" .
$cliente->getNif(),
0,
'L'
);
// ----------------------------
// Segunda columna: DETALLES
// ----------------------------
$pdf->SetXY($startX + $colWidth, $startY); // Moverse a la segunda columna
$pdf->SetFont('helvetica', 'B', 10);
$pdf->Cell($colWidth, 5, 'DETALLES', 0, 1, 'L'); // Título
$pdf->Line($startX + $colWidth, $pdf->GetY(), $startX + (2 * $colWidth), $pdf->GetY()); // Línea horizontal
$pdf->Ln(3); // Espacio después de la línea
// Detalles del proyecto
$pdf->SetFont('helvetica', '', 10);
$pdf->SetX($startX + $colWidth); // Mantenerse en la columna
$pdf->MultiCell($colWidth, 5, $concepto, 0, 'L');
// ----------------------------
// Tercera columna: PAGO
// ----------------------------
$pdf->SetXY($startX + (2 * $colWidth), $startY); // Moverse a la tercera columna
$pdf->SetFont('helvetica', 'B', 10);
$pdf->Cell($colWidth, 5, 'FECHA', 0, 1, 'L'); // Título
$pdf->Line($startX + (2 * $colWidth), $pdf->GetY(), $startX + (3 * $colWidth), $pdf->GetY()); // Línea horizontal
$pdf->Ln(3); // Espacio después de la línea
// Información de pago
$pdf->SetFont('helvetica', '', 10);
$pdf->SetX($startX + (2 * $colWidth)); // Mantenerse en la columna
$pdf->MultiCell(
$colWidth,
5,
"Fecha de emisión: " . $currentDate,
0,
'L'
);
// ----------------------------
// Salto de línea después de las columnas
// ----------------------------
$pdf->Ln(20);
// Establecer color gris para las líneas
$pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
// Encabezados de la tabla con fondo y texto en negrita
$pdf->SetFont('helvetica', 'B', 12);
$pdf->Cell(80, 10, 'CONCEPTO', 'T', 0, 'L'); // Solo línea superior
$pdf->Cell(25, 10, 'CANT.', 'T', 0, 'C'); // Solo línea superior
$pdf->Cell(30, 10, 'PRECIO', 'T', 0, 'C'); // Solo línea superior
$pdf->Cell(30, 10, 'IMPORTE', 'T', 1, 'C'); // Solo línea superior y salto de línea
// Recorrer el array y añadir contenido al PDF
$totalBase = 0;
$totalIVA = 0;
foreach ($data_productos as $producto) {
$subtotal = $producto['price'] * $producto['quantity'];
$totalBase += $subtotal;
// Actualizar el array de IVAs
foreach ($data_ivas as &$iva) { // Usamos referencia para modificar directamente
if ($iva['id'] === $producto['iva']) {
$iva['valor'] += $subtotal * ($producto['iva'] / 100); // Sumar el IVA calculado
break;
}
}
unset($iva);
// Nombre del artículo
$pdf->SetFont('helvetica', '', 10); // Fuente normal para los productos
$pdf->Cell(80, 10, $producto['name'], 'T', 0, 'L');
$pdf->Cell(25, 10, $producto['quantity'], 'T', 0, 'C');
$pdf->Cell(30, 10, number_format($producto['price'], 2, ',', '.') . ' €', 'T', 0, 'C');
$pdf->Cell(30, 10, number_format($producto['total'], 2, ',', '.') . ' €', 'T', 1, 'C'); // Línea superior solo
}
$pdf->Ln();
// Calcular IVA y Total
foreach ($data_ivas as $iva) {
$totalIVA += floatval($iva['valor']);
}
// Calcular IVA y Total
$totalFinal = $totalBase + $totalIVA;
// Establecer color gris para las líneas
$pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
// Mostrar resumen de precios al final del PDF (sin líneas verticales)
$pdf->SetFont('helvetica', '', 10);
// Establecer color gris para las líneas horizontales
$pdf->SetDrawColor(169, 169, 169); // Gris claro
// Reducir la altura de las celdas
$cellHeight = 8; // Altura de las celdas ajustada
// Ancho total restringido para las líneas horizontales
$lineWidth = 80; // Define el ancho total de las celdas restringido a la derecha
// Subtotal
$pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
$pdf->Cell($lineWidth, $cellHeight, 'Subtotal', 'T', 0, 'R'); // Línea superior ajustada al ancho
$pdf->Cell(30, $cellHeight, number_format($totalBase, 2) . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
// IVA
foreach ($data_ivas as $iva) {
$pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
$pdf->Cell($lineWidth, $cellHeight, 'IVA (' . $iva['id'] . '%)', 'T', 0, 'R'); // Mostrar el porcentaje de IVA
$pdf->Cell(30, $cellHeight, number_format($iva['valor'], 2, ',', '.') . ' €', 'T', 1, 'R'); // Mostrar el valor del IVA
}
// Total a pagar
$pdf->SetFont('helvetica', 'B', 12); // Negrita para el total
$pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
$pdf->Cell($lineWidth, $cellHeight, 'Total a pagar', 'T', 0, 'R'); // Línea superior ajustada al ancho
$pdf->Cell(30, $cellHeight, number_format($totalFinal, 2) . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
$pdf->Ln(20);
// Establecer color gris para las líneas
$pdf->SetDrawColor(169, 169, 169); // Color negro
$pdf->SetLineWidth(0.3); // Grosor de la línea
$pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
$pieFactura = $datosEmpresa->getPieFactu();
$pdf->SetFont('helvetica', '', 8);
$pdf->Ln(20);
$pdf->MultiCell(
190, // Ancho de la celda (ajústalo según el tamaño de tu página)
10, // Altura mínima de la celda
$pieFactura, // Contenido del pie
'T', // Borde superior (T) para marcar el inicio del pie
'L', // Alineación a la izquierda
false // No rellenar la celda
);
$budgetsDirectory = $this->getParameter('budgets_directory');
// Guardar el PDF en el servidor
$pdf->Output($pdfPathAbsolute, 'F');
$newFactura = new Tickettofactura();
$newFactura->setRuta($pdfPathRelative);
$newFactura->setRef($invoiceRef); // Usar la referencia generada de forma segura
$newFactura->setFecha(new \DateTime());
$newFactura->setCustomer($presupuesto->getCustomer());
$newFactura->setEstado($this->entityManager->getRepository(Estadosfactu::class)->find(2));
$newFactura->setBase($totalBase);
$newFactura->setIva($totalIVA);
$newFactura->setTotal($totalFinal);
$this->entityManager->persist($newFactura);
$this->entityManager->flush();
// Asegurar que el archivo se haya creado correctamente
if (file_exists($pdfPathRelative)) {
// Guarda el mensaje de éxito en la sesión (opcional, si aún necesitas flashes para redirección en otras partes)
// $this->addFlash('success', 'Presupuesto guardado correctamente.');
// Devuelve un JSON en lugar de redirigir
return new JsonResponse([
'success' => true,
'message' => 'Factura generada correctamente.',
'redirectUrl' => $this->generateUrl('presupuesto'), // Si necesitas una URL para redirección manual
], Response::HTTP_OK);
} else {
return new JsonResponse([
'success' => false,
'message' => 'Hubo un error al guardar el PDF.',
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
// Este return nunca se ejecutará, puede ser eliminado
return new JsonResponse(['success' => true, 'message' => 'Presupuesto guardado correctamente'], 200);
} else {
return new JsonResponse(['success' => false, 'message' => 'Este presupuesto ya tiene una Factura asignada'], 409); // 409 Conflict
}
}
/**
* @Route("/presupuesto/{id}", name="presupuesto_show")
*/
public function show($id): Response
{
// Buscar el presupuesto en la base de datos usando el ID
$presupuesto = $this->entityManager->getRepository(Presupuesto::class)->find($id);
// Verificar si el presupuesto fue encontrado
if (!$presupuesto) {
throw $this->createNotFoundException('Presupuesto no encontrado');
}
$array_presu = array(
"id" => $presupuesto->getId(),
"name" => $presupuesto->getName(),
"ruta" => $presupuesto->getPathPdf(),
"estado" => $presupuesto->getEstado()->getName()
);
return new Response(
$this->renderView('presupuesto/show.html.twig', [
'presupuesto' => $array_presu
]),
200,
['Content-Type' => 'text/vnd.turbo-stream.html']
);
}
/**
* @Route("/setstatepresu", name="set_state_presu", methods={"POST"})
*/
public function setState(Request $request)
{
$data = json_decode($request->getContent(), true);
$id = $data['id'];
$state = $data['value'];
// Buscar el usuario por el correo electrónico
$presupuesto = $this->entityManager->getRepository(Presupuesto::class)->find($id);
$estado = $this->entityManager->getRepository(Estadospresu::class)->findOneBy(['name' => $state]);
$presupuesto->setEstado($estado);
$this->entityManager->persist($presupuesto);
$this->entityManager->flush();
// return $this->redirectToRoute('tickettofactura');
return new JsonResponse([
'texto' => 'Actualizado con éxito',
], 200);
}
/**
* @Route("/updatepresupuesto", name="update_presupuesto", methods={"POST"})
*/
public function updatePresupuesto(Request $request): Response
{
// Recuperamos datos para modificar el presupuesto
$datos = json_decode($request->getContent(), true);
$presupuestoId = $datos['presupuestoId']; // ID del presupuesto
$elementos = $datos['elementos'];
$idPresu = $datos['idpresu'];
$fecha = $datos['fecha'];
$currentDate = (new DateTime($fecha))->format('d/m/Y');
$nextMonthDate = (new DateTime($fecha))->modify('+1 month')->format('d/m/Y');
$concepto = $datos['concepto'];
$datosEmpresa = $this->entityManager->getRepository(Info::class)->findOneBy(['id' => 1]);
$presupuesto = $this->entityManager->getRepository(Presupuesto::class)->find($idPresu);
$budgetProducts = $this->entityManager->getRepository(Budgetproduct::class)->findBy(['budget' => $presupuesto->getId()]);
$cliente = $this->entityManager->getRepository(Proveedores::class)->findOneBy(['id' => $presupuesto->getCustomer()]);
$data_productos = [];
foreach ($elementos as $index => $elemento) {
$budgetProduct = $budgetProducts[$index];
$producto = $this->entityManager->getRepository(Productos::class)->findOneBy(['name' => $elemento['name']]);
$quantity = $elemento['quantity'];
$price = $elemento['price'];
// Establecemos los nuevos valores en la línea de presupuesto
$budgetProduct->setProduct($producto); // Asignamos el producto relacionado
$budgetProduct->setQuantity($quantity); // Asignamos la cantidad
$budgetProduct->setPrice($price);
$temp = array(
'name' => $producto->getName(),
'price' => $price,
'quantity' => $quantity,
);
array_push($data_productos, $temp);
}
// Ruta completa al archivo en la carpeta "public/presupuesto"
$rutaArchivo = $this->getParameter('kernel.project_dir') . '/public/' . $presupuesto->getPathPdf();
// Instancia del componente Filesystem de Symfony
$filesystem = new Filesystem();
try {
// Verifica si el archivo existe
if ($filesystem->exists($rutaArchivo)) {
// Elimina el archivo
$filesystem->remove($rutaArchivo);
} else {
// Devuelve una respuesta indicando que el archivo no existe
return new JsonResponse([
'error' => 'El archivo no existe.',
], Response::HTTP_NOT_FOUND);
}
} catch (IOExceptionInterface $exception) {
// Maneja errores de entrada/salida al manipular el archivo
return new JsonResponse([
'error' => 'Error al intentar eliminar el archivo: ' . $exception->getMessage(),
], Response::HTTP_INTERNAL_SERVER_ERROR);
}
$budgetsDirectory = $this->getParameter('budgets_directory');
//Generar El nuevo presupuesto
$pdfFile = $presupuesto->getName() . '.pdf';
$nameToBadget = $presupuesto->getName();
$pdfPathAbsolute = $budgetsDirectory . '/' . $pdfFile; // Ruta absoluta para guardar el archivo
$pdfPathRelative = 'presupuestos/' . $pdfFile; // Ruta relativa para el frontend o base de datos
// Crear una instancia de TCPDF
$pdf = new TCPDF();
// Configurar el documento
$pdf->SetCreator(PDF_CREATOR);
$pdf->SetAuthor('Tu Nombre');
$pdf->SetTitle('Título del Documento');
$pdf->SetSubject('Asunto del Documento');
$pdf->SetKeywords('TCPDF, PDF, ejemplo, tutorial');
$imagePath = $datosEmpresa->getLogo();
$imageFullPath = $this->getParameter('public') . $imagePath; // Construir ruta absoluta
// Asegúrate de que el archivo exista
if (!file_exists($imageFullPath)) {
throw new \Exception("La imagen no se encuentra en la ruta: " . $imageFullPath);
}
// Desactiva el encabezado automático
$pdf->setPrintHeader(false);
// Configurar los márgenes y pie de página
$pdf->setFooterMargin(10);
$pdf->SetMargins(15, 27, 15);
$pdf->setFooterData([0, 64, 0], [0, 64, 128]);
// Añadir la página
$pdf->AddPage();
// Añadir manualmente el encabezado con la imagen y texto
$pdf->Image($imageFullPath, 170, 5, 30, 30); // Ancho = 30 mm, Alto = 20 mm
$pdf->SetFont('helvetica', 'B', 16);
$pdf->SetXY(15, 10); // Coordenadas iniciales para el texto
$pdf->Cell(0, 10, 'Presupuesto', 0, 1, 'L');
$pdf->SetFont('helvetica', '', 12);
$pdf->SetXY(15, 18);
$pdf->Cell(0, 10, 'Nº ' . $nameToBadget, 0, 1, 'L');
// Añadir una línea para dividir el encabezado del contenido
$pdf->SetDrawColor(169, 169, 169); // Color negro
$pdf->SetLineWidth(0.3); // Grosor de la línea
$pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
// Continúa con el contenido del PDF...
$pdf->SetFont('helvetica', '', 10);
$pdf->SetXY(140, 35);
$pdf->MultiCell(120, 5, $datosEmpresa->getName() . "\n" .
$datosEmpresa->getDir() . "\n" .
$datosEmpresa->getEmail() . "\n" .
$datosEmpresa->getTelf() . "\n" .
$datosEmpresa->getCif(), 0, 'J', false, 1);
// Configuración inicial
$pdf->SetFont('helvetica', '', 10);
$pdf->SetDrawColor(169, 169, 169); // Gris claro para la línea debajo de los títulos
// Dividir el ancho de la página para tres columnas con márgenes
$pageWidth = $pdf->GetPageWidth() - 20; // Ancho de la página menos márgenes
$colWidth = $pageWidth / 3; // Ancho de cada columna
$startX = 10; // Margen izquierdo
$startY = $pdf->GetY(); // Posición inicial vertical
// ----------------------------
// Primera columna: FACTURAR A
// ----------------------------
$pdf->SetXY($startX, $startY); // Posición inicial
$pdf->SetFont('helvetica', 'B', 10);
$pdf->Cell($colWidth, 5, 'PRESUPUESTO A', 0, 1, 'L'); // Título
$pdf->Line($startX, $pdf->GetY(), $startX + $colWidth, $pdf->GetY()); // Línea horizontal
$pdf->Ln(3); // Espacio después de la línea
// Datos del cliente
$pdf->SetFont('helvetica', '', 10);
$pdf->SetX($startX); // Mantenerse en la misma columna
$pdf->MultiCell(
$colWidth,
5,
$cliente->getName() . "\n" .
$cliente->getDir() . "\n" .
$cliente->getTelf() . "\n" .
$cliente->getEmail() . "\n" .
$cliente->getNif(),
0,
'L'
);
// ----------------------------
// Segunda columna: DETALLES
// ----------------------------
$pdf->SetXY($startX + $colWidth, $startY); // Moverse a la segunda columna
$pdf->SetFont('helvetica', 'B', 10);
$pdf->Cell($colWidth, 5, 'DETALLES', 0, 1, 'L'); // Título
$pdf->Line($startX + $colWidth, $pdf->GetY(), $startX + (2 * $colWidth), $pdf->GetY()); // Línea horizontal
$pdf->Ln(3); // Espacio después de la línea
// Detalles del proyecto
$pdf->SetFont('helvetica', '', 10);
$pdf->SetX($startX + $colWidth); // Mantenerse en la columna
$pdf->MultiCell($colWidth, 5, $concepto, 0, 'L');
// ----------------------------
// Tercera columna: PAGO
// ----------------------------
$pdf->SetXY($startX + (2 * $colWidth), $startY); // Moverse a la tercera columna
$pdf->SetFont('helvetica', 'B', 10);
$pdf->Cell($colWidth, 5, 'PLAZO', 0, 1, 'L'); // Título
$pdf->Line($startX + (2 * $colWidth), $pdf->GetY(), $startX + (3 * $colWidth), $pdf->GetY()); // Línea horizontal
$pdf->Ln(3); // Espacio después de la línea
// Información de pago
$pdf->SetFont('helvetica', '', 10);
$pdf->SetX($startX + (2 * $colWidth)); // Mantenerse en la columna
$pdf->MultiCell(
$colWidth,
5,
"Fecha de emisión: " . $currentDate . "\n" .
"Fecha de vencimiento: " . $nextMonthDate,
0,
'L'
);
// ----------------------------
// Salto de línea después de las columnas
// ----------------------------
$pdf->Ln(20);
// Establecer color gris para las líneas
$pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
// Encabezados de la tabla con fondo y texto en negrita
$pdf->SetFont('helvetica', 'B', 12);
$pdf->Cell(80, 10, 'CONCEPTO', 'T', 0, 'L'); // Solo línea superior
$pdf->Cell(25, 10, 'CANT.', 'T', 0, 'C'); // Solo línea superior
$pdf->Cell(30, 10, 'PRECIO', 'T', 0, 'C'); // Solo línea superior
$pdf->Cell(30, 10, 'IMPORTE', 'T', 1, 'C'); // Solo línea superior y salto de línea
// Recorrer el array y añadir contenido al PDF
$totalBase = 0;
$totalIVA = 0;
$iva = 0.21; // 21%
foreach ($data_productos as $producto) {
$subtotal = $producto['price'] * $producto['quantity'];
$totalBase += $subtotal;
// Nombre del artículo
$pdf->SetFont('helvetica', '', 10); // Fuente normal para los productos
$pdf->Cell(80, 10, $producto['name'], 'T', 0, 'L');
$pdf->Cell(25, 10, $producto['quantity'], 'T', 0, 'C');
$pdf->Cell(30, 10, number_format($producto['price'], 2, ',', '.') . ' €', 'T', 0, 'C');
$pdf->Cell(30, 10, number_format($subtotal, 2, ',', '.') . ' €', 'T', 1, 'C'); // Línea superior solo
}
$pdf->Ln();
// Calcular IVA y Total
$totalIVA = $totalBase * $iva;
$totalFinal = $totalBase + $totalIVA;
// Establecer color gris para las líneas
$pdf->SetDrawColor(169, 169, 169); // Gris claro (RGB: 169, 169, 169)
// Mostrar resumen de precios al final del PDF (sin líneas verticales)
$pdf->SetFont('helvetica', '', 10);
// Establecer color gris para las líneas horizontales
$pdf->SetDrawColor(169, 169, 169); // Gris claro
// Reducir la altura de las celdas
$cellHeight = 8; // Altura de las celdas ajustada
// Ancho total restringido para las líneas horizontales
$lineWidth = 80; // Define el ancho total de las celdas restringido a la derecha
// Subtotal
$pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
$pdf->Cell($lineWidth, $cellHeight, 'Subtotal', 'T', 0, 'R'); // Línea superior ajustada al ancho
$pdf->Cell(30, $cellHeight, number_format($totalBase, 2, ',', '.') . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
// IVA
$pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
$pdf->Cell($lineWidth, $cellHeight, 'IVA (21%)', 'T', 0, 'R'); // Línea superior ajustada al ancho
$pdf->Cell(30, $cellHeight, number_format($totalIVA, 2, ',', '.') . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
// Total a pagar
$pdf->SetFont('helvetica', 'B', 12); // Negrita para el total
$pdf->SetX(70); // Ajustar la posición X para alinear las celdas a la derecha
$pdf->Cell($lineWidth, $cellHeight, 'Total a pagar', 'T', 0, 'R'); // Línea superior ajustada al ancho
$pdf->Cell(30, $cellHeight, number_format($totalFinal, 2, ',', '.') . ' €', 'T', 1, 'R'); // Línea superior, valor alineado a la derecha
$pdf->Ln(20);
// Establecer color gris para las líneas
$pdf->SetDrawColor(169, 169, 169); // Color negro
$pdf->SetLineWidth(0.3); // Grosor de la línea
$pdf->Line(15, 30, 195, 30); // X inicial, Y inicial, X final, Y final
$pieFactura = $datosEmpresa->getPiePresu();
$pdf->SetFont('helvetica', '', 8);
$pdf->Ln(20);
$pdf->MultiCell(
190, // Ancho de la celda (ajústalo según el tamaño de tu página)
10, // Altura mínima de la celda
$pieFactura, // Contenido del pie
'T', // Borde superior (T) para marcar el inicio del pie
'L', // Alineación a la izquierda
false // No rellenar la celda
);
// Guardar el PDF en el servidor
$pdf->Output($pdfPathAbsolute, 'F');
// $newPresupuesto->setPathPdf($pdfPathRelative);
$presupuesto->setBase($totalBase);
$presupuesto->setIva($totalIVA);
$presupuesto->setTotal($totalFinal);
$this->entityManager->persist($presupuesto);
$this->entityManager->flush();
// Asegurar que el archivo se haya creado correctamente
if (file_exists($pdfPathRelative)) {
// Guardar el mensaje de éxito en la sesión
$this->addFlash('success', 'Presupuesto guardado correctamente.');
// Redireccionar a la página de presupuestos (index)
return $this->redirectToRoute('presupuesto');
} else {
return new Response("Hubo un error al guardar el PDF.", Response::HTTP_INTERNAL_SERVER_ERROR);
}
return $this->redirectToRoute('presupuesto');
}
/**
* @Route("/deletepresuesto", name="delete_presupuesto")
*/
public function deletePresupuesto(Request $request): Response
{
$presupuesto = $this->entityManager->getRepository(Presupuesto::class)->find($request->query->get('id'));
$rutaArchivo = $this->getParameter('budgets_directory') . basename($presupuesto->getPathPdf());
if ($this->filesystem->exists($rutaArchivo)) {
$this->filesystem->remove($rutaArchivo);
$this->entityManager->remove($presupuesto);
$this->entityManager->flush();
// Mensaje de éxito
return $this->json([
'success' => true,
'factura' => 'Presupuesto eliminado con éxito.',
]);
}
return new Response('No se ha podido eliminar el archivo');
}
}