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

View File

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

View File

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

View File

@@ -64,12 +64,19 @@ class TicketsShipmentRepository {
documentData['storage_path'] = storagePath;
// 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
await _supabase
.from('ticket')
.update({'ticket_status': newTicketStatus})
.update({
'ticket_status': newTicketStatus,
'shipment_document_id': documentid,
})
.inFilter('id', document.ticketIds);
// Restituiamo lo storagePath per usarlo subito nell'interfaccia se serve

View File

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

View File

@@ -103,8 +103,7 @@ class TicketList extends StatelessWidget {
FilledButton.icon(
onPressed: () async {
// 1. Apriamo la modale e ASPETTIAMO il risultato (tipizzandolo come Record)
final Uint8List? result =
await showModalBottomSheet<Uint8List>(
final bool? result = await showModalBottomSheet<bool?>(
context: context,
isScrollControlled: true,
builder: (context) {
@@ -124,11 +123,6 @@ 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) {
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<TicketListCubit>().clearSelection();
// (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,
),
),
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
Container(
padding: const EdgeInsets.symmetric(
@@ -123,6 +141,7 @@ class TicketListCard extends StatelessWidget {
color: statusColor.withValues(alpha: 0.5),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [

View File

@@ -39,21 +39,7 @@ class _TicketShippingModalState extends State<TicketShippingModal> {
child: BlocConsumer<TicketShippingCubit, TicketShippingState>(
listener: (context, state) {
if (state.status == TicketShippingStatus.success) {
final provider = state.availableProviders.firstWhere(
(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);
Navigator.pop(context, true);
}
if (state.status == TicketShippingStatus.failure &&
state.errorMessage != null) {