From 1a21b44bc8c96442291a07dfc671a69fc870c8cf Mon Sep 17 00:00:00 2001 From: mark-cachy Date: Sat, 16 May 2026 19:34:33 +0200 Subject: [PATCH] df --- .../tickets/blocs/ticket_list_cubit.dart | 4 ++ .../tickets/blocs/ticket_shipping_cubit.dart | 48 +++++++++----- .../tickets/blocs/ticket_shipping_state.dart | 11 +++- .../data/tickets_shipment_repository.dart | 47 +++++++++++--- .../tickets/ui/widgets/ticket_list.dart | 64 ++++++------------- .../ui/widgets/ticket_shipping_modal.dart | 1 + 6 files changed, 103 insertions(+), 72 deletions(-) diff --git a/lib/features/tickets/blocs/ticket_list_cubit.dart b/lib/features/tickets/blocs/ticket_list_cubit.dart index 680f80c..a2df448 100644 --- a/lib/features/tickets/blocs/ticket_list_cubit.dart +++ b/lib/features/tickets/blocs/ticket_list_cubit.dart @@ -1,5 +1,9 @@ +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/features/tickets/data/tickets_shipment_repository.dart'; +import 'package:flux/features/tickets/models/shipment_document_model.dart'; import 'package:flux/features/tickets/models/ticket_model.dart'; import 'package:flux/features/tickets/data/ticket_repository.dart'; import 'package:get_it/get_it.dart'; diff --git a/lib/features/tickets/blocs/ticket_shipping_cubit.dart b/lib/features/tickets/blocs/ticket_shipping_cubit.dart index 2eb8d4d..7a06b83 100644 --- a/lib/features/tickets/blocs/ticket_shipping_cubit.dart +++ b/lib/features/tickets/blocs/ticket_shipping_cubit.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; @@ -8,6 +10,7 @@ import 'package:flux/features/master_data/providers/models/provider_model.dart'; import 'package:flux/features/settings/document_sequence/data/document_sequence_repository.dart'; import 'package:flux/features/settings/document_sequence/models/document_sequence_model.dart'; import 'package:flux/features/tickets/models/ticket_model.dart'; +import 'package:flux/features/tickets/utils/ticket_shipping_pdf_service.dart'; import 'package:get_it/get_it.dart'; part 'ticket_shipping_state.dart'; @@ -17,18 +20,19 @@ class TicketShippingCubit extends Cubit { GetIt.I(); final DocumentSequenceRepository _sequenceRepository = GetIt.I(); - TicketShippingCubit({required List ticketIds}) + TicketShippingCubit({required List tickets}) : super( TicketShippingState( // Inizializziamo il modello direttamente nello stato! document: ShipmentDocumentModel( companyId: GetIt.I.get().state.company!.id!, - ticketIds: ticketIds, + ticketIds: tickets.map((t) => t.id!).toList(), providerId: '', // Sarà riempito alla selezione destinationLocationId: '', // Sarà riempito alla selezione docNumber: '', docDate: DateTime.now(), ), + tickets: tickets, ), ); @@ -140,25 +144,35 @@ class TicketShippingCubit extends Cubit { DocumentType.shipment.name, ); updateDocument(docNumber: nextNumber); + // 3. GENERIAMO I BYTES DEL PDF + final provider = state.availableProviders.firstWhere( + (p) => p.id == state.document.providerId, + ); + final location = state.availableLocations.firstWhere( + (l) => l.id == state.document.destinationLocationId, + ); + final pdfBytes = await TicketShippingPdfService.generateDdt( + company: GetIt.I.get().state.company!, + provider: provider, + location: location, + document: state.document, + tickets: state.tickets, + ); + await _repository.createShipmentWithPdf( + document: state.document, + pdfBytes: pdfBytes, + newTicketStatus: newTicketStatus.value, + ); + emit( + state.copyWith( + status: TicketShippingStatus.success, + pdfBytes: pdfBytes, + ), + ); } catch (e) { emit(state.copyWith(isAutoNumber: false, errorMessage: e.toString())); return; } } - - try { - await _repository.createShipmentDocument( - document: state.document, - newTicketStatus: newTicketStatus, - ); - emit(state.copyWith(status: TicketShippingStatus.success)); - } catch (e) { - emit( - state.copyWith( - status: TicketShippingStatus.failure, - errorMessage: e.toString(), - ), - ); - } } } diff --git a/lib/features/tickets/blocs/ticket_shipping_state.dart b/lib/features/tickets/blocs/ticket_shipping_state.dart index 19bcaf8..f208131 100644 --- a/lib/features/tickets/blocs/ticket_shipping_state.dart +++ b/lib/features/tickets/blocs/ticket_shipping_state.dart @@ -4,7 +4,9 @@ enum TicketShippingStatus { initial, loading, success, failure } class TicketShippingState extends Equatable { final TicketShippingStatus status; - final ShipmentDocumentModel document; // Il nostro eroe! + final ShipmentDocumentModel document; + final List tickets; + final Uint8List? pdfBytes; // Per tenere il PDF in memoria dopo la generazione // Dati di supporto per la UI final List availableProviders; @@ -15,10 +17,12 @@ class TicketShippingState extends Equatable { const TicketShippingState({ this.status = TicketShippingStatus.initial, required this.document, + required this.tickets, this.availableProviders = const [], this.availableLocations = const [], this.isAutoNumber = true, this.errorMessage, + this.pdfBytes, }); TicketShippingState copyWith({ @@ -28,14 +32,17 @@ class TicketShippingState extends Equatable { List? availableLocations, bool? isAutoNumber, String? errorMessage, + Uint8List? pdfBytes, }) { return TicketShippingState( status: status ?? this.status, document: document ?? this.document, + tickets: tickets, availableProviders: availableProviders ?? this.availableProviders, availableLocations: availableLocations ?? this.availableLocations, isAutoNumber: isAutoNumber ?? this.isAutoNumber, errorMessage: errorMessage ?? this.errorMessage, + pdfBytes: pdfBytes ?? this.pdfBytes, ); } @@ -43,9 +50,11 @@ class TicketShippingState extends Equatable { List get props => [ status, document, + tickets, availableProviders, availableLocations, isAutoNumber, errorMessage, + pdfBytes, ]; } diff --git a/lib/features/tickets/data/tickets_shipment_repository.dart b/lib/features/tickets/data/tickets_shipment_repository.dart index 7baae94..071779d 100644 --- a/lib/features/tickets/data/tickets_shipment_repository.dart +++ b/lib/features/tickets/data/tickets_shipment_repository.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:flux/features/tickets/models/shipment_document_model.dart'; import 'package:flux/features/master_data/providers/models/provider_model.dart'; import 'package:flux/features/master_data/providers/models/provider_role.dart'; @@ -29,22 +31,51 @@ class TicketsShipmentRepository { } } - // NUOVO METODO: Salva il DDT e aggiorna i Ticket - Future createShipmentDocument({ + /// Salva il DDT nel DB, fa l'upload del PDF nello Storage e aggiorna il path + Future createShipmentWithPdf({ required ShipmentDocumentModel document, - required TicketStatus newTicketStatus, // es: 'shipped' o 'inExternalLab' + required Uint8List pdfBytes, + required String newTicketStatus, }) async { try { - // 1. Inseriamo il singolo Documento di Trasporto - await _supabase.from('shipment_documents').insert(document.toMap()); + // 1. Definiamo un percorso unico e ordinato per il file nello Storage + // Struttura: company_id / ddt / anno / numero_ddt.pdf + final year = document.docDate.year; + final cleanedDocNumber = document.docNumber + .replaceAll('/', '_') + .replaceAll(' ', '_'); + final storagePath = + '${document.companyId}/ddt/$year/$cleanedDocNumber.pdf'; - // 2. Aggiorniamo lo stato di TUTTI i ticket inclusi nel DDT + // 2. Facciamo l'upload dei bytes grezzi nel bucket 'company_documents' + await _supabase.storage + .from('company_documents') + .uploadBinary( + storagePath, + pdfBytes, + fileOptions: const FileOptions( + contentType: 'application/pdf', + upsert: true, + ), + ); + + // 3. Creiamo la mappa del documento includendo il percorso dello storage + final documentData = document.toMap(); + documentData['storage_path'] = storagePath; + + // 4. Inseriamo il Documento di Trasporto nel DB + await _supabase.from('shipment_documents').insert(documentData); + + // 5. Aggiorniamo lo stato di TUTTI i ticket inclusi nel DDT await _supabase .from('ticket') - .update({'ticket_status': newTicketStatus.value}) + .update({'ticket_status': newTicketStatus}) .inFilter('id', document.ticketIds); + + // Restituiamo lo storagePath per usarlo subito nell'interfaccia se serve + return storagePath; } catch (e) { - throw ('Errore durante la creazione della spedizione: $e'); + throw ('Errore durante il salvataggio e upload della spedizione: $e'); } } } diff --git a/lib/features/tickets/ui/widgets/ticket_list.dart b/lib/features/tickets/ui/widgets/ticket_list.dart index a4bc606..dab7398 100644 --- a/lib/features/tickets/ui/widgets/ticket_list.dart +++ b/lib/features/tickets/ui/widgets/ticket_list.dart @@ -1,3 +1,6 @@ +import 'dart:ffi'; +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; @@ -7,6 +10,7 @@ import 'package:flux/features/master_data/providers/models/provider_model.dart'; import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart'; import 'package:flux/features/tickets/blocs/ticket_list_state.dart'; import 'package:flux/features/tickets/blocs/ticket_shipping_cubit.dart'; +import 'package:flux/features/tickets/models/ticket_model.dart'; import 'package:flux/features/tickets/ui/widgets/ticket_list_card.dart'; import 'package:flux/features/tickets/ui/widgets/ticket_shipping_modal.dart'; import 'package:flux/features/tickets/utils/ticket_shipping_pdf_service.dart'; @@ -99,22 +103,14 @@ class TicketList extends StatelessWidget { FilledButton.icon( onPressed: () async { // 1. Apriamo la modale e ASPETTIAMO il risultato (tipizzandolo come Record) - final result = - await showModalBottomSheet< - ({ - ShipmentDocumentModel document, - ProviderModel provider, - ProviderLocationModel location, - }) - >( + final Uint8List? result = + await showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) { return BlocProvider( create: (context) => TicketShippingCubit( - ticketIds: state.selectedTickets - .map((t) => t.id!) - .toList(), + tickets: state.selectedTickets.toList(), )..loadRepairCenters(), child: TicketShippingModal( ticketIds: state.selectedTickets @@ -128,41 +124,17 @@ class TicketList extends StatelessWidget { // 2. Se l'utente ha chiuso trascinando giù, result è null. // Se ha salvato con successo, result contiene il nostro Record! if (result != null && context.mounted) { - try { - // Recuperiamo la Company (dal tuo SessionCubit o AuthCubit) - final company = context - .read() - .state - .company!; - - final ticketListCubit = context - .read(); - - // 3. GENERIAMO I BYTES DEL PDF - final pdfBytes = - await TicketShippingPdfService.generateDdt( - company: company, - provider: result.provider, - location: result.location, - document: result.document, - tickets: state.selectedTickets.toList(), - ); - await Printing.layoutPdf( - onLayout: (PdfPageFormat format) async => pdfBytes, - name: 'ddt_${result.document.docNumber}.pdf', - ); - - // 5. Pulizia finale: Deselezioniamo tutti i ticket e ricarichiamo la lista - ticketListCubit.clearSelection(); - // (Se necessario, chiama il metodo per ricaricare la lista dei ticket dal DB) - ticketListCubit.loadTickets(refresh: true); - } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Errore stampa PDF: $e')), - ); - } - } + await Printing.layoutPdf( + onLayout: (format) async => result, + name: + 'DDT_${DateTime.now().millisecondsSinceEpoch}.pdf', + ); + // 5. Pulizia finale: Deselezioniamo tutti i ticket e ricarichiamo la lista + context.read().clearSelection(); + // (Se necessario, chiama il metodo per ricaricare la lista dei ticket dal DB) + context.read().loadTickets( + refresh: true, + ); } }, icon: const Icon(Icons.local_shipping), diff --git a/lib/features/tickets/ui/widgets/ticket_shipping_modal.dart b/lib/features/tickets/ui/widgets/ticket_shipping_modal.dart index 46f23ef..c57d6c2 100644 --- a/lib/features/tickets/ui/widgets/ticket_shipping_modal.dart +++ b/lib/features/tickets/ui/widgets/ticket_shipping_modal.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/features/tickets/blocs/ticket_shipping_cubit.dart'; import 'package:flux/features/tickets/models/ticket_model.dart'; +import 'package:go_router/go_router.dart'; class TicketShippingModal extends StatefulWidget { final List ticketIds;