206 lines
7.5 KiB
Dart
206 lines
7.5 KiB
Dart
import 'dart:typed_data';
|
|
import 'package:flux/features/company/models/company_model.dart';
|
|
import 'package:flux/features/documents/models/shipment_document_model.dart';
|
|
import 'package:flux/features/master_data/providers/models/provider_location_model.dart';
|
|
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
|
import 'package:flux/features/tickets/models/ticket_model.dart';
|
|
import 'package:pdf/pdf.dart';
|
|
import 'package:pdf/widgets.dart' as pw;
|
|
import 'package:intl/intl.dart';
|
|
|
|
class TicketShippingPdfService {
|
|
static Future<Uint8List> generateDdt({
|
|
required CompanyModel company,
|
|
required ProviderModel provider,
|
|
required ProviderLocationModel location,
|
|
required ShipmentDocumentModel document,
|
|
required List<TicketModel> tickets,
|
|
}) async {
|
|
final pdf = pw.Document();
|
|
|
|
// Formattatore per le date
|
|
final dateFormat = DateFormat('dd/MM/yyyy');
|
|
|
|
pdf.addPage(
|
|
pw.MultiPage(
|
|
pageFormat: PdfPageFormat.a4,
|
|
margin: const pw.EdgeInsets.all(32),
|
|
// --- INTESTAZIONE (Ripetuta su ogni pagina) ---
|
|
header: (pw.Context context) {
|
|
return pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
pw.Row(
|
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
// Dati Mittente (La tua Company)
|
|
pw.Expanded(
|
|
child: pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
pw.Text(
|
|
company.name,
|
|
style: pw.TextStyle(
|
|
fontWeight: pw.FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
pw.Text(company.address),
|
|
pw.Text(
|
|
'${company.city} (${company.province}) - ${company.zipCode}',
|
|
),
|
|
pw.Text('P.IVA: ${company.vatId}'),
|
|
],
|
|
),
|
|
),
|
|
// Dati Destinatario (Il Laboratorio e la Sede)
|
|
pw.Expanded(
|
|
child: pw.Container(
|
|
padding: const pw.EdgeInsets.all(8),
|
|
decoration: pw.BoxDecoration(
|
|
border: pw.Border.all(color: PdfColors.grey),
|
|
borderRadius: const pw.BorderRadius.all(
|
|
pw.Radius.circular(4),
|
|
),
|
|
),
|
|
child: pw.Column(
|
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
|
children: [
|
|
pw.Text(
|
|
'DESTINATARIO:',
|
|
style: pw.TextStyle(
|
|
fontSize: 10,
|
|
color: PdfColors.grey700,
|
|
),
|
|
),
|
|
pw.Text(
|
|
provider.name,
|
|
style: pw.TextStyle(fontWeight: pw.FontWeight.bold),
|
|
),
|
|
pw.SizedBox(height: 4),
|
|
pw.Text('Destinazione merce:'),
|
|
pw.Text(location.address),
|
|
pw.Text(
|
|
'${location.zipCode} ${location.city} (${location.province})',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
pw.SizedBox(height: 20),
|
|
// Titolo Documento
|
|
pw.Center(
|
|
child: pw.Text(
|
|
'DOCUMENTO DI TRASPORTO (D.D.T.)',
|
|
style: pw.TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: pw.FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
pw.SizedBox(height: 10),
|
|
// Dati Documento
|
|
pw.Row(
|
|
mainAxisAlignment: pw.MainAxisAlignment.spaceAround,
|
|
children: [
|
|
pw.Text(
|
|
'Numero: ${document.docNumber}',
|
|
style: pw.TextStyle(fontWeight: pw.FontWeight.bold),
|
|
),
|
|
pw.Text(
|
|
'Data: ${dateFormat.format(document.docDate)}',
|
|
style: pw.TextStyle(fontWeight: pw.FontWeight.bold),
|
|
),
|
|
pw.Text('Causale: ${document.shippingReason}'),
|
|
],
|
|
),
|
|
pw.SizedBox(height: 20),
|
|
],
|
|
);
|
|
},
|
|
|
|
// --- IL CORPO (La tabella dei ticket che scorre) ---
|
|
build: (pw.Context context) {
|
|
return [
|
|
pw.TableHelper.fromTextArray(
|
|
headers: ['Rif. Ticket', 'Modello', 'Difetto / Note', 'Q.tà'],
|
|
headerStyle: pw.TextStyle(
|
|
fontWeight: pw.FontWeight.bold,
|
|
color: PdfColors.white,
|
|
),
|
|
headerDecoration: const pw.BoxDecoration(
|
|
color: PdfColors.blueGrey800,
|
|
),
|
|
cellAlignment: pw.Alignment.centerLeft,
|
|
data: tickets.map((t) {
|
|
return [
|
|
t.id?.substring(0, 8).toUpperCase() ??
|
|
'-', // Magari hai un ID progressivo migliore
|
|
t.targetModelName ?? 'Sconosciuto',
|
|
t.request,
|
|
'1', // Tipicamente 1 per ogni ticket
|
|
];
|
|
}).toList(),
|
|
),
|
|
];
|
|
},
|
|
|
|
// --- PIÈ DI PAGINA (Ripetuto su ogni pagina, ma con le firme) ---
|
|
footer: (pw.Context context) {
|
|
return pw.Column(
|
|
children: [
|
|
pw.Divider(),
|
|
pw.Row(
|
|
mainAxisAlignment: pw.MainAxisAlignment.spaceAround,
|
|
children: [
|
|
pw.Text('Colli: ${document.packageCount}'),
|
|
pw.Text('Peso: ${document.weight ?? '-'} Kg'),
|
|
pw.Text(
|
|
'Aspetto: ${document.notes ?? 'Scatola'}',
|
|
), // Adatta se hai un campo specifico
|
|
],
|
|
),
|
|
pw.SizedBox(height: 20),
|
|
// Spazio Firme
|
|
pw.Row(
|
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
_buildSignatureBox('Firma Mittente'),
|
|
_buildSignatureBox('Firma Vettore (Corriere)'),
|
|
_buildSignatureBox('Firma Destinatario'),
|
|
],
|
|
),
|
|
pw.SizedBox(height: 10),
|
|
pw.Align(
|
|
alignment: pw.Alignment.centerRight,
|
|
child: pw.Text(
|
|
'Pagina ${context.pageNumber} di ${context.pagesCount}',
|
|
style: const pw.TextStyle(
|
|
fontSize: 10,
|
|
color: PdfColors.grey,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
return pdf.save();
|
|
}
|
|
|
|
static pw.Widget _buildSignatureBox(String title) {
|
|
return pw.Column(
|
|
children: [
|
|
pw.Text(title, style: const pw.TextStyle(fontSize: 10)),
|
|
pw.SizedBox(height: 40),
|
|
pw.Container(width: 120, height: 1, color: PdfColors.black),
|
|
],
|
|
);
|
|
}
|
|
}
|