This commit is contained in:
2026-05-18 08:31:39 +02:00
parent 1a21b44bc8
commit 906265a0e3
8 changed files with 72 additions and 49 deletions

View File

@@ -12,6 +12,7 @@ import 'package:flux/features/settings/document_sequence/models/document_sequenc
import 'package:flux/features/tickets/models/ticket_model.dart'; import 'package:flux/features/tickets/models/ticket_model.dart';
import 'package:flux/features/tickets/utils/ticket_shipping_pdf_service.dart'; import 'package:flux/features/tickets/utils/ticket_shipping_pdf_service.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:printing/printing.dart';
part 'ticket_shipping_state.dart'; part 'ticket_shipping_state.dart';
@@ -163,12 +164,11 @@ class TicketShippingCubit extends Cubit<TicketShippingState> {
pdfBytes: pdfBytes, pdfBytes: pdfBytes,
newTicketStatus: newTicketStatus.value, newTicketStatus: newTicketStatus.value,
); );
emit( await Printing.layoutPdf(
state.copyWith( onLayout: (format) async => pdfBytes,
status: TicketShippingStatus.success, name: 'DDT_${state.document.docNumber}.pdf',
pdfBytes: pdfBytes,
),
); );
emit(state.copyWith(status: TicketShippingStatus.success));
} catch (e) { } catch (e) {
emit(state.copyWith(isAutoNumber: false, errorMessage: e.toString())); emit(state.copyWith(isAutoNumber: false, errorMessage: e.toString()));
return; return;

View File

@@ -6,7 +6,6 @@ class TicketShippingState extends Equatable {
final TicketShippingStatus status; final TicketShippingStatus status;
final ShipmentDocumentModel document; final ShipmentDocumentModel document;
final List<TicketModel> tickets; final List<TicketModel> tickets;
final Uint8List? pdfBytes; // Per tenere il PDF in memoria dopo la generazione
// Dati di supporto per la UI // Dati di supporto per la UI
final List<ProviderModel> availableProviders; final List<ProviderModel> availableProviders;
@@ -22,7 +21,6 @@ class TicketShippingState extends Equatable {
this.availableLocations = const [], this.availableLocations = const [],
this.isAutoNumber = true, this.isAutoNumber = true,
this.errorMessage, this.errorMessage,
this.pdfBytes,
}); });
TicketShippingState copyWith({ TicketShippingState copyWith({
@@ -32,7 +30,6 @@ class TicketShippingState extends Equatable {
List<ProviderLocationModel>? availableLocations, List<ProviderLocationModel>? availableLocations,
bool? isAutoNumber, bool? isAutoNumber,
String? errorMessage, String? errorMessage,
Uint8List? pdfBytes,
}) { }) {
return TicketShippingState( return TicketShippingState(
status: status ?? this.status, status: status ?? this.status,
@@ -42,7 +39,6 @@ class TicketShippingState extends Equatable {
availableLocations: availableLocations ?? this.availableLocations, availableLocations: availableLocations ?? this.availableLocations,
isAutoNumber: isAutoNumber ?? this.isAutoNumber, isAutoNumber: isAutoNumber ?? this.isAutoNumber,
errorMessage: errorMessage ?? this.errorMessage, errorMessage: errorMessage ?? this.errorMessage,
pdfBytes: pdfBytes ?? this.pdfBytes,
); );
} }
@@ -55,6 +51,5 @@ class TicketShippingState extends Equatable {
availableLocations, availableLocations,
isAutoNumber, isAutoNumber,
errorMessage, errorMessage,
pdfBytes,
]; ];
} }

View File

@@ -31,6 +31,7 @@ class TicketRepository {
.select(''' .select('''
*, *,
customer (*), customer (*),
shipment_document (*),
created_by:staff_member!ticket_staff_id_fkey (*), created_by:staff_member!ticket_staff_id_fkey (*),
assigned_to:staff_member!ticket_assigned_to_id_fkey (*), assigned_to:staff_member!ticket_assigned_to_id_fkey (*),
target_model:model!ticket_model_id_1_fkey (*), target_model:model!ticket_model_id_1_fkey (*),
@@ -88,6 +89,7 @@ class TicketRepository {
.select(''' .select('''
*, *,
customer (*), customer (*),
shipment_document (*),
created_by:staff_member!ticket_staff_id_fkey (*), created_by:staff_member!ticket_staff_id_fkey (*),
assigned_to:staff_member!ticket_assigned_to_id_fkey (*), assigned_to:staff_member!ticket_assigned_to_id_fkey (*),
target_model:model!ticket_model_id_1_fkey (*), target_model:model!ticket_model_id_1_fkey (*),
@@ -186,6 +188,7 @@ class TicketRepository {
source_model:model!ticket_model_id_2_fkey (*), source_model:model!ticket_model_id_2_fkey (*),
created_by:staff_member!ticket_staff_id_fkey (*), created_by:staff_member!ticket_staff_id_fkey (*),
assigned_to:staff_member!ticket_assigned_to_id_fkey (*), assigned_to:staff_member!ticket_assigned_to_id_fkey (*),
shipment_document (*),
''') ''')
.eq('id', ticketId) .eq('id', ticketId)
.single(); .single();

View File

@@ -64,12 +64,19 @@ class TicketsShipmentRepository {
documentData['storage_path'] = storagePath; documentData['storage_path'] = storagePath;
// 4. Inseriamo il Documento di Trasporto nel DB // 4. Inseriamo il Documento di Trasporto nel DB
await _supabase.from('shipment_documents').insert(documentData); final savedDocument = await _supabase
.from('shipment_documents')
.insert(documentData)
.select('id')
.single();
final documentid = savedDocument['id'];
// 5. Aggiorniamo lo stato di TUTTI i ticket inclusi nel DDT // 5. Aggiorniamo lo stato di TUTTI i ticket inclusi nel DDT
await _supabase await _supabase
.from('ticket') .from('ticket')
.update({'ticket_status': newTicketStatus}) .update({
'ticket_status': newTicketStatus,
'shipment_document_id': documentid,
})
.inFilter('id', document.ticketIds); .inFilter('id', document.ticketIds);
// Restituiamo lo storagePath per usarlo subito nell'interfaccia se serve // Restituiamo lo storagePath per usarlo subito nell'interfaccia se serve

View File

@@ -1,6 +1,7 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flux/core/utils/extensions.dart'; import 'package:flux/core/utils/extensions.dart';
import 'package:flux/features/customers/models/customer_model.dart'; import 'package:flux/features/customers/models/customer_model.dart';
import 'package:flux/features/tickets/models/shipment_document_model.dart';
/// Enum per il tipo di ticket /// Enum per il tipo di ticket
enum TicketType { enum TicketType {
@@ -111,6 +112,7 @@ class TicketModel extends Equatable {
final DateTime? estimatedDeliveryAt; final DateTime? estimatedDeliveryAt;
final TicketResult? ticketResult; final TicketResult? ticketResult;
final String? resolutionNotes; final String? resolutionNotes;
final String? shippingDocumentId;
final CustomerModel? customer; final CustomerModel? customer;
final String? targetModelName; final String? targetModelName;
final String? sourceModelName; final String? sourceModelName;
@@ -119,6 +121,8 @@ class TicketModel extends Equatable {
final String? assignedToId; final String? assignedToId;
final String? assignedToName; final String? assignedToName;
final String? includedAccessories; final String? includedAccessories;
final ShipmentDocumentModel?
shippingDocument; // Per tenere in memoria i dati del DDT associato al ticket
const TicketModel({ const TicketModel({
this.id, this.id,
@@ -148,6 +152,7 @@ class TicketModel extends Equatable {
this.estimatedDeliveryAt, this.estimatedDeliveryAt,
this.ticketResult, this.ticketResult,
this.resolutionNotes, this.resolutionNotes,
this.shippingDocumentId,
this.customer, this.customer,
this.targetModelName, this.targetModelName,
this.sourceModelName, this.sourceModelName,
@@ -156,6 +161,7 @@ class TicketModel extends Equatable {
this.assignedToId, this.assignedToId,
this.assignedToName, this.assignedToName,
this.includedAccessories, this.includedAccessories,
this.shippingDocument,
}); });
/// Factory per creare un ticket vuoto (utile per i form di creazione) /// Factory per creare un ticket vuoto (utile per i form di creazione)
@@ -200,6 +206,7 @@ class TicketModel extends Equatable {
DateTime? estimatedDeliveryAt, DateTime? estimatedDeliveryAt,
TicketResult? ticketResult, TicketResult? ticketResult,
String? resolutionNotes, String? resolutionNotes,
String? shippingDocumentId,
CustomerModel? customer, CustomerModel? customer,
String? targetModelName, String? targetModelName,
String? sourceModelName, String? sourceModelName,
@@ -208,6 +215,7 @@ class TicketModel extends Equatable {
String? assignedToId, String? assignedToId,
String? assignedToName, String? assignedToName,
String? includedAccessories, String? includedAccessories,
ShipmentDocumentModel? shippingDocument,
}) { }) {
return TicketModel( return TicketModel(
id: id ?? this.id, id: id ?? this.id,
@@ -238,6 +246,7 @@ class TicketModel extends Equatable {
estimatedDeliveryAt: estimatedDeliveryAt ?? this.estimatedDeliveryAt, estimatedDeliveryAt: estimatedDeliveryAt ?? this.estimatedDeliveryAt,
ticketResult: ticketResult ?? this.ticketResult, ticketResult: ticketResult ?? this.ticketResult,
resolutionNotes: resolutionNotes ?? this.resolutionNotes, resolutionNotes: resolutionNotes ?? this.resolutionNotes,
shippingDocumentId: shippingDocumentId ?? this.shippingDocumentId,
customer: customer ?? this.customer, customer: customer ?? this.customer,
targetModelName: targetModelName ?? this.targetModelName, targetModelName: targetModelName ?? this.targetModelName,
sourceModelName: sourceModelName ?? this.sourceModelName, sourceModelName: sourceModelName ?? this.sourceModelName,
@@ -246,6 +255,7 @@ class TicketModel extends Equatable {
assignedToId: assignedToId ?? this.assignedToId, assignedToId: assignedToId ?? this.assignedToId,
assignedToName: assignedToName ?? this.assignedToName, assignedToName: assignedToName ?? this.assignedToName,
includedAccessories: includedAccessories ?? this.includedAccessories, includedAccessories: includedAccessories ?? this.includedAccessories,
shippingDocument: shippingDocument ?? this.shippingDocument,
); );
} }
@@ -288,6 +298,7 @@ class TicketModel extends Equatable {
: null, : null,
ticketResult: TicketResult.fromString(map['ticket_result'] as String?), ticketResult: TicketResult.fromString(map['ticket_result'] as String?),
resolutionNotes: map['resolution_notes'] as String?, resolutionNotes: map['resolution_notes'] as String?,
shippingDocumentId: map['shipping_document_id'] as String?,
customer: map['customer'] != null customer: map['customer'] != null
? CustomerModel.fromMap(map['customer'] as Map<String, dynamic>) ? CustomerModel.fromMap(map['customer'] as Map<String, dynamic>)
: null, : null,
@@ -300,6 +311,11 @@ class TicketModel extends Equatable {
assignedToId: map['assigned_to_id'] as String?, assignedToId: map['assigned_to_id'] as String?,
assignedToName: (map['assigned_to']?['name'] as String?)?.myFormat(), assignedToName: (map['assigned_to']?['name'] as String?)?.myFormat(),
includedAccessories: map['included_accessories'] as String?, includedAccessories: map['included_accessories'] as String?,
shippingDocument: map['shipping_document'] != null
? ShipmentDocumentModel.fromMap(
map['shipping_document'] as Map<String, dynamic>,
)
: null,
); );
} }
@@ -337,6 +353,7 @@ class TicketModel extends Equatable {
if (ticketResult != null) 'ticket_result': ticketResult!.value, if (ticketResult != null) 'ticket_result': ticketResult!.value,
'resolution_notes': resolutionNotes, 'resolution_notes': resolutionNotes,
'included_accessories': includedAccessories, 'included_accessories': includedAccessories,
'shipping_document_id': shippingDocumentId,
}; };
} }
@@ -376,5 +393,7 @@ class TicketModel extends Equatable {
createdByName, createdByName,
assignedToId, assignedToId,
assignedToName, assignedToName,
shippingDocumentId,
shippingDocument,
]; ];
} }

View File

@@ -103,8 +103,7 @@ class TicketList extends StatelessWidget {
FilledButton.icon( FilledButton.icon(
onPressed: () async { onPressed: () async {
// 1. Apriamo la modale e ASPETTIAMO il risultato (tipizzandolo come Record) // 1. Apriamo la modale e ASPETTIAMO il risultato (tipizzandolo come Record)
final Uint8List? result = final bool? result = await showModalBottomSheet<bool?>(
await showModalBottomSheet<Uint8List>(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
builder: (context) { builder: (context) {
@@ -124,11 +123,6 @@ class TicketList extends StatelessWidget {
// 2. Se l'utente ha chiuso trascinando giù, result è null. // 2. Se l'utente ha chiuso trascinando giù, result è null.
// Se ha salvato con successo, result contiene il nostro Record! // Se ha salvato con successo, result contiene il nostro Record!
if (result != null && context.mounted) { if (result != null && context.mounted) {
await Printing.layoutPdf(
onLayout: (format) async => result,
name:
'DDT_${DateTime.now().millisecondsSinceEpoch}.pdf',
);
// 5. Pulizia finale: Deselezioniamo tutti i ticket e ricarichiamo la lista // 5. Pulizia finale: Deselezioniamo tutti i ticket e ricarichiamo la lista
context.read<TicketListCubit>().clearSelection(); context.read<TicketListCubit>().clearSelection();
// (Se necessario, chiama il metodo per ricaricare la lista dei ticket dal DB) // (Se necessario, chiama il metodo per ricaricare la lista dei ticket dal DB)

View File

@@ -110,6 +110,24 @@ class TicketListCard extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
if (ticket.shippingDocument != null) ...[
Row(
children: [
const Text('DDT'),
const SizedBox(width: 4),
InkWell(
borderRadius: BorderRadius.circular(20),
onTap: () {},
child: const Icon(
Icons.picture_as_pdf,
color: Colors.redAccent,
size: 20,
),
),
],
),
],
// IL BADGE DELLO STATO // IL BADGE DELLO STATO
Container( Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -123,6 +141,7 @@ class TicketListCard extends StatelessWidget {
color: statusColor.withValues(alpha: 0.5), color: statusColor.withValues(alpha: 0.5),
), ),
), ),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [

View File

@@ -39,21 +39,7 @@ class _TicketShippingModalState extends State<TicketShippingModal> {
child: BlocConsumer<TicketShippingCubit, TicketShippingState>( child: BlocConsumer<TicketShippingCubit, TicketShippingState>(
listener: (context, state) { listener: (context, state) {
if (state.status == TicketShippingStatus.success) { if (state.status == TicketShippingStatus.success) {
final provider = state.availableProviders.firstWhere( Navigator.pop(context, true);
(p) => p.id == state.document.providerId,
);
final location = state.availableLocations.firstWhere(
(l) => l.id == state.document.destinationLocationId,
);
// Creiamo un Dart Record elegante e lo "spariamo" fuori
final ddtData = (
document: state.document,
provider: provider,
location: location,
);
Navigator.pop(context, ddtData);
} }
if (state.status == TicketShippingStatus.failure && if (state.status == TicketShippingStatus.failure &&
state.errorMessage != null) { state.errorMessage != null) {