import 'dart:typed_data'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import 'package:flux/features/tickets/models/ticket_model.dart'; import 'package:flux/features/company/models/company_model.dart'; class TicketPdfService { /// Funzione principale: Genera il PDF A4 con le due metà Future generateTicketReceipt( TicketModel ticket, CompanyModel company, ) async { final pdf = pw.Document(); // Carichiamo il font per essere sicuri che i caratteri siano ok final font = await PdfGoogleFonts.robotoRegular(); final boldFont = await PdfGoogleFonts.robotoBold(); pdf.addPage( pw.Page( pageFormat: PdfPageFormat.a4, margin: const pw.EdgeInsets.all(20), build: (context) { return pw.Column( children: [ // 1. METÀ SUPERIORE: CLIENTE _buildTicketHalf( ticket, company, font, boldFont, isForCustomer: true, ), pw.SizedBox(height: 10), // Linea tratteggiata per il taglio pw.Container( margin: const pw.EdgeInsets.symmetric(vertical: 10), child: pw.Text( '-' * 100, style: const pw.TextStyle(color: PdfColors.grey400), ), ), pw.SizedBox(height: 10), // 2. METÀ INFERIORE: NEGOZIO _buildTicketHalf( ticket, company, font, boldFont, isForCustomer: false, ), ], ); }, ), ); return pdf.save(); } /// Helper per costruire una singola metà (Cliente o Negozio) pw.Widget _buildTicketHalf( TicketModel ticket, CompanyModel company, pw.Font font, pw.Font boldFont, { required bool isForCustomer, }) { return pw.Container( height: 380, // Circa metà A4 meno i margini child: pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ // HEADER: Logo e Dati Azienda (Solo per cliente o ID per negozio) pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ pw.Text( company.name, style: pw.TextStyle(font: boldFont, fontSize: 16), ), if (isForCustomer) ...[ pw.Text( "${company.address}, ${company.city}", style: const pw.TextStyle(fontSize: 10), ), pw.Text( "P.IVA: ${company.vatId}", style: const pw.TextStyle(fontSize: 10), ), ], ], ), pw.Row( children: [ pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.end, children: [ pw.Text( isForCustomer ? "RICEVUTA CLIENTE" : "COPIA INTERNA NEGOZIO", style: pw.TextStyle( font: boldFont, fontSize: 12, color: PdfColors.grey700, ), ), pw.Text( "Rif: ${ticket.referenceId}", style: pw.TextStyle(font: boldFont, fontSize: 14), ), pw.Text( "Data: ${ticket.createdAt?.toString().substring(0, 10) ?? ''}", style: const pw.TextStyle(fontSize: 10), ), ], ), pw.SizedBox(width: 10), // IL NOSTRO QR CODE MAGICO pw.BarcodeWidget( barcode: pw.Barcode.qrCode(), data: ticket.id!, // Salviamo l'ID univoco nel QR! width: 45, height: 45, ), ], ), ], ), pw.Divider(thickness: 1), pw.SizedBox(height: 10), // DATI CLIENTE pw.Row( children: [ pw.Expanded( child: _infoBlock( "CLIENTE", ticket.customerName ?? 'Cliente Sconosciuto', font, boldFont, ), ), pw.Expanded( child: _infoBlock( "CONTATTO ALTERNATIVO", ticket.alternativePhoneNumber ?? 'N/D', font, boldFont, ), ), ], ), pw.SizedBox(height: 15), // DETTAGLI LAVORAZIONE _infoBlock( "DESCRIZIONE PROBLEMA / LAVORAZIONE RICHIESTA", ticket.request, font, boldFont, ), pw.SizedBox(height: 8), pw.Row( children: [ pw.Expanded( child: _infoBlock( "ACCESSORI CONSEGNATI", ticket.includedAccessories ?? 'Nessuno', font, boldFont, ), ), pw.Expanded( child: _infoBlock( "GARANZIA", ticket.warrantyType?.displayValue ?? 'Standard', font, boldFont, ), ), ], ), pw.SizedBox(height: 15), // NOTE (Pubbliche o Private a seconda della copia) if (isForCustomer) _infoBlock("NOTE", ticket.publicNotes ?? '-', font, boldFont) else _infoBlock( "NOTE INTERNE (PRIVATE)", ticket.internalNotes ?? '-', font, boldFont, ), pw.Spacer(), // FOOTER: Disclaimer e Firma if (!isForCustomer) ...[ pw.Text( "CONDIZIONI E LIBERATORIA:", style: pw.TextStyle(font: boldFont, fontSize: 8), ), pw.Text( company.ticketDisclaimer ?? 'Firma per accettazione delle condizioni di riparazione.', style: const pw.TextStyle(fontSize: 7), textAlign: pw.TextAlign.justify, ), pw.SizedBox(height: 20), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Container( width: 150, decoration: pw.BoxDecoration( border: const pw.Border(top: pw.BorderSide(width: 0.5)), ), ), pw.Text( "Firma del Cliente per accettazione", style: const pw.TextStyle(fontSize: 8), ), ], ), ] else pw.Align( alignment: pw.Alignment.centerRight, child: pw.Text( "Grazie per averci scelto!", style: pw.TextStyle( font: font, fontSize: 10, fontStyle: pw.FontStyle.italic, ), ), ), ], ), ); } pw.Widget _infoBlock( String label, String value, pw.Font font, pw.Font boldFont, ) { return pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ pw.Text( label, style: pw.TextStyle( font: boldFont, fontSize: 8, color: PdfColors.grey600, ), ), pw.Text(value, style: pw.TextStyle(font: font, fontSize: 11)), ], ); } Future generateLabelPdf( TicketModel ticket, CompanyModel company, ) async { final pdf = pw.Document(); final font = await PdfGoogleFonts.robotoRegular(); final boldFont = await PdfGoogleFonts.robotoBold(); // Prendiamo le misure salvate (se custom) o usiamo default final widthMm = company.labelWidth ?? 62.0; final heightMm = company.labelHeight ?? 29.0; // Creiamo il formato fisico esatto! final format = company.isLabelVertical ? PdfPageFormat(heightMm * PdfPageFormat.mm, widthMm * PdfPageFormat.mm) : PdfPageFormat( widthMm * PdfPageFormat.mm, heightMm * PdfPageFormat.mm, ); pdf.addPage( pw.Page( pageFormat: format, margin: const pw.EdgeInsets.all(2), // Margini minimi per le etichette build: (context) { return pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Expanded( child: pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, mainAxisAlignment: pw.MainAxisAlignment.center, children: [ pw.Text( ticket.referenceId ?? '', style: pw.TextStyle(font: boldFont, fontSize: 10), ), pw.Text( ticket.customerName ?? 'Cliente sconosciuto', style: pw.TextStyle(font: font, fontSize: 9), ), pw.Text( ticket.createdAt?.toString().substring(0, 10) ?? '', style: const pw.TextStyle(fontSize: 7), ), ], ), ), // QR Code compatto pw.BarcodeWidget( barcode: pw.Barcode.qrCode(), data: ticket.id!, width: 20, height: 20, ), ], ); }, ), ); return pdf.save(); } }