import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:provider/provider.dart'; import '../../models/models.dart'; import '../../viewmodels/viewmodels.dart'; import 'package:printing/printing.dart'; import 'package:flutter/services.dart' show rootBundle; Future imprimirTicketsJuntos(BuildContext context, Pedido pedido) async { bool ticketCocinaActivo = await Provider.of(context, listen: false) .isVariableActive('ticket_cocina'); bool ticketVentaActivo = await Provider.of(context, listen: false) .isVariableActive('ticket_venta'); final pdf = pw.Document(); final image = pw.MemoryImage( (await rootBundle.load('assets/icono-BN.png')).buffer.asUint8List(), ); if (ticketVentaActivo) { pdf.addPage( generarPaginaPrimerTicket(pedido, image), ); } if (ticketCocinaActivo) { pdf.addPage( generarPaginaSegundoTicket(pedido), ); } await printPdf(Uint8List.fromList(await pdf.save())); } pw.Page generarPaginaPrimerTicket(Pedido pedido, pw.MemoryImage image) { final numberFormat = NumberFormat('#,##0.00', 'es_MX'); double totalSinDescuento = 0; double totalConDescuento = 0; double descuento = pedido.descuento?.toDouble() ?? 0.0; double precioDescuento = 0; final productList = pedido.productos .map( (producto) { final productPrice = double.parse(producto.producto?.precio ?? '0'); final productTotal = productPrice * (producto.cantidad ?? 1); totalSinDescuento += productTotal; final toppingsList = producto.toppings.where((topping) { final toppingPrice = double.parse(topping.topping?.precio ?? '0'); return toppingPrice > 0; }).map((topping) { final toppingPrice = double.parse(topping.topping?.precio ?? '0'); totalSinDescuento += toppingPrice * (producto.cantidad ?? 1); return pw.Row( children: [ pw.Text( '- ${topping.topping?.nombre ?? "Topping no especificado"}', style: const pw.TextStyle(fontSize: 7)), pw.Spacer(), pw.Text('\$${numberFormat.format(toppingPrice)}', style: const pw.TextStyle(fontSize: 7)), ], ); }).toList(); return [ pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Expanded( flex: 2, child: pw.Text( producto.producto?.nombre ?? "Producto no especificado", style: const pw.TextStyle(fontSize: 7)), ), pw.Expanded( flex: 1, child: pw.Text('x${producto.cantidad}', style: const pw.TextStyle(fontSize: 7)), ), pw.Padding( padding: pw.EdgeInsets.only(right: 2), child: pw.Expanded( flex: 1, child: pw.Text('\$${numberFormat.format(productPrice)}', style: const pw.TextStyle(fontSize: 8)), ), ) ], ), ...toppingsList, ]; }, ) .expand((e) => e) .toList(); precioDescuento = totalSinDescuento * (descuento / 100); totalConDescuento = totalSinDescuento - precioDescuento; return pw.Page( pageFormat: PdfPageFormat.roll57, build: (pw.Context context) { return pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.center, children: [ pw.Padding( padding: const pw.EdgeInsets.only(right: 20), child: pw.Center(child: pw.Image(image, width: 50, height: 50))), pw.SizedBox(height: 10), pw.Padding( padding: const pw.EdgeInsets.only(right: 15), child: pw.Column(children: [ pw.SizedBox(height: 5), pw.Text('Fecha: ${pedido.peticion}', style: const pw.TextStyle(fontSize: 9)), pw.Text('Conalep', style: const pw.TextStyle(fontSize: 9)), pw.Text('Hermosillo', style: const pw.TextStyle(fontSize: 9)), ])), pw.SizedBox(height: 10), pw.Text('Pedido: ${pedido.folio}', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: 10)), pw.SizedBox(height: 10), pw.Padding( padding: const pw.EdgeInsets.only(right: 20), child: pw.Column(children: productList)), pw.Divider(), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Subtotal:', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: 9)), pw.Padding( padding: const pw.EdgeInsets.only(right: 30), child: pw.Text( '\$${numberFormat.format(totalSinDescuento)}', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: 9)), ), ], ), if (descuento > 0) ...[ pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Descuento:', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: 9)), pw.Padding( padding: const pw.EdgeInsets.only(right: 30), child: pw.Text( '-\$${numberFormat.format(precioDescuento)}', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: 9)), ), ], ), ], pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Total:', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: 9)), pw.Padding( padding: const pw.EdgeInsets.only(right: 30), child: pw.Text( '\$${numberFormat.format(totalConDescuento)}', style: pw.TextStyle( fontWeight: pw.FontWeight.bold, fontSize: 9)), ), ], ), pw.SizedBox(height: 5), pw.Padding( padding: const pw.EdgeInsets.only(right: 15), child: pw.Text('¡GRACIAS POR SU COMPRA!', style: pw.TextStyle( fontSize: 8, fontWeight: pw.FontWeight.bold))), pw.Divider(), pw.SizedBox(height: 20), pw.Text('.', style: pw.TextStyle(fontSize: 1)), ]); }); } pw.Page generarPaginaSegundoTicket(Pedido pedido) { final numberFormat = NumberFormat('#,##0.00', 'es_MX'); double subtotal = 0; double totalConDescuento = 0; double descuento = pedido.descuento?.toDouble() ?? 0.0; double precioDescuento = 0; List content = [ pw.SizedBox(height: 20), pw.Text('.', style: pw.TextStyle(fontSize: 1)), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceAround, children: [ pw.Text('${pedido.folio}', style: pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold)), pw.Text('${pedido.peticion}', style: pw.TextStyle(fontSize: 12, fontWeight: pw.FontWeight.bold)), ], ), pw.SizedBox(height: 5), if (pedido.nombreCliente != null && pedido.nombreCliente!.isNotEmpty) pw.Text('Cliente: ${pedido.nombreCliente}', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 12)), pw.SizedBox(height: 10), ]; // Mostrar los productos con la cantidad, el precio total y el precio de los toppings content.addAll(pedido.productos .map((producto) { final productPrice = double.parse(producto.producto?.precio ?? '0'); final productTotal = productPrice * (producto.cantidad ?? 1); subtotal += productTotal; final toppingsList = producto.toppings.map((topping) { final toppingPrice = double.parse(topping.topping?.precio ?? '0'); final toppingTotal = toppingPrice * (producto.cantidad ?? 1); subtotal += toppingTotal; return pw.Row( mainAxisAlignment: pw.MainAxisAlignment.start, children: [ pw.Expanded( flex: 3, child: pw.Text( '-${topping.topping?.nombre ?? "Topping no especificado"}', style: const pw.TextStyle(fontSize: 9)), ), if (toppingPrice > 0) pw.Expanded( flex: 1, child: pw.Text( '\$${numberFormat.format(toppingTotal)}', style: const pw.TextStyle(fontSize: 9), textAlign: pw.TextAlign.right, ), ), ], ); }).toList(); return [ pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Expanded( flex: 1, child: pw.Text('${producto.cantidad}', style: const pw.TextStyle(fontSize: 12)), ), pw.Expanded( flex: 3, child: pw.Text( producto.producto?.nombre ?? "Producto no especificado", style: const pw.TextStyle(fontSize: 11)), ), pw.Expanded( flex: 2, child: pw.Text( '\$${numberFormat.format(productTotal)}', style: const pw.TextStyle(fontSize: 12), textAlign: pw.TextAlign.right, ), ), ], ), ...toppingsList, ]; }) .expand((e) => e) .toList()); // Calcular el descuento y el total final precioDescuento = subtotal * (descuento / 100); totalConDescuento = subtotal - precioDescuento; content.add(pw.Divider()); if (descuento > 0) { content.addAll([ pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Subtotal:', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 12)), pw.Text('\$${numberFormat.format(subtotal)}', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 12)), ], ), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Descuento:', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 12)), pw.Text('-\$${numberFormat.format(precioDescuento)}', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 12)), ], ), ]); } content.add( pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Total:', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 12)), pw.Text( '\$${numberFormat.format(descuento > 0 ? totalConDescuento : subtotal)}', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 12)), ], ), ); if (pedido.comentarios != null && pedido.comentarios!.isNotEmpty) { content.add(pw.SizedBox(height: 10)); content.add(pw.Text('Comentarios:', style: pw.TextStyle(fontWeight: pw.FontWeight.bold, fontSize: 12))); content.add(pw.Padding( padding: const pw.EdgeInsets.only(right: 15), child: pw.Text(pedido.comentarios!, style: const pw.TextStyle(fontSize: 12)), )); content.add(pw.Text('.', style: pw.TextStyle(fontSize: 1))); content.add(pw.Text('.', style: pw.TextStyle(fontSize: 1))); } content.add(pw.SizedBox(height: 20)); content.add(pw.Text('.', style: pw.TextStyle(fontSize: 1))); return pw.Page( pageFormat: PdfPageFormat.roll57, build: (pw.Context context) { return pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.center, children: content); }, ); } Future printPdf(Uint8List pdfBytes) async { await Printing.layoutPdf( onLayout: (PdfPageFormat format) => pdfBytes, ); }