refactor shipping attachments and changed shipment to shipping for coherence
This commit is contained in:
@@ -153,7 +153,8 @@ class _SharedAttachmentsSectionState extends State<SharedAttachmentsSection> {
|
|||||||
fileBytes = file.localBytes;
|
fileBytes = file.localBytes;
|
||||||
} else if (file.storagePath != null && file.storagePath!.isNotEmpty) {
|
} else if (file.storagePath != null && file.storagePath!.isNotEmpty) {
|
||||||
fileBytes = await repository.downloadAttachmentBytes(
|
fileBytes = await repository.downloadAttachmentBytes(
|
||||||
file.storagePath!,
|
storagePath: file.storagePath!,
|
||||||
|
bucket: Bucket.documents,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +284,8 @@ class _SharedAttachmentsSectionState extends State<SharedAttachmentsSection> {
|
|||||||
fileBytes = file.localBytes;
|
fileBytes = file.localBytes;
|
||||||
} else if (file.storagePath != null && file.storagePath!.isNotEmpty) {
|
} else if (file.storagePath != null && file.storagePath!.isNotEmpty) {
|
||||||
fileBytes = await repository.downloadAttachmentBytes(
|
fileBytes = await repository.downloadAttachmentBytes(
|
||||||
file.storagePath!,
|
storagePath: file.storagePath!,
|
||||||
|
bucket: Bucket.documents,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
|
|||||||
add(LoadAttachmentsEvent(parentId: parentId));
|
add(LoadAttachmentsEvent(parentId: parentId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onParentEntitySaved(
|
FutureOr<void> _onParentEntitySaved(
|
||||||
ParentEntitySavedEvent event,
|
ParentEntitySavedEvent event,
|
||||||
Emitter<AttachmentsState> emit,
|
Emitter<AttachmentsState> emit,
|
||||||
@@ -67,6 +68,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
|
|||||||
parentType: state.parentType,
|
parentType: state.parentType,
|
||||||
pickedFile: fakePlatformFile,
|
pickedFile: fakePlatformFile,
|
||||||
companyId: companyId!,
|
companyId: companyId!,
|
||||||
|
bucket: _getBucketForParentType,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
@@ -156,6 +158,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
|
|||||||
parentType: state.parentType,
|
parentType: state.parentType,
|
||||||
pickedFile: file,
|
pickedFile: file,
|
||||||
companyId: companyId!,
|
companyId: companyId!,
|
||||||
|
bucket: _getBucketForParentType,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
@@ -192,6 +195,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
|
|||||||
parentType: state.parentType,
|
parentType: state.parentType,
|
||||||
pickedFile: file,
|
pickedFile: file,
|
||||||
companyId: event.companyId,
|
companyId: event.companyId,
|
||||||
|
bucket: _getBucketForParentType,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -218,6 +222,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
|
|||||||
parentType: state.parentType,
|
parentType: state.parentType,
|
||||||
pickedFile: fakePlatformFile,
|
pickedFile: fakePlatformFile,
|
||||||
companyId: event.companyId,
|
companyId: event.companyId,
|
||||||
|
bucket: _getBucketForParentType,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -242,6 +247,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
|
|||||||
await _repository.deleteFiles(
|
await _repository.deleteFiles(
|
||||||
files: state.selectedFiles,
|
files: state.selectedFiles,
|
||||||
currentContextType: state.parentType,
|
currentContextType: state.parentType,
|
||||||
|
bucket: _getBucketForParentType,
|
||||||
);
|
);
|
||||||
emit(state.copyWith(status: AttachmentsStatus.ready, selectedFiles: []));
|
emit(state.copyWith(status: AttachmentsStatus.ready, selectedFiles: []));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -298,6 +304,8 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
|
|||||||
return file.copyWith(ticketId: event.targetId);
|
return file.copyWith(ticketId: event.targetId);
|
||||||
case AttachmentParentType.operation:
|
case AttachmentParentType.operation:
|
||||||
return file.copyWith(operationId: event.targetId);
|
return file.copyWith(operationId: event.targetId);
|
||||||
|
case AttachmentParentType.shippingDocument:
|
||||||
|
return file.copyWith(shippingDocumentId: event.targetId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return file;
|
return file;
|
||||||
@@ -386,4 +394,17 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
|
|||||||
emit(state.copyWith(localFiles: updatedLocalFiles));
|
emit(state.copyWith(localFiles: updatedLocalFiles));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bucket get _getBucketForParentType {
|
||||||
|
switch (state.parentType) {
|
||||||
|
case AttachmentParentType.customer:
|
||||||
|
return Bucket.documents;
|
||||||
|
case AttachmentParentType.ticket:
|
||||||
|
return Bucket.documents;
|
||||||
|
case AttachmentParentType.operation:
|
||||||
|
return Bucket.documents;
|
||||||
|
case AttachmentParentType.shippingDocument:
|
||||||
|
return Bucket.companyDocuments;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ enum AttachmentsStatus { initial, loading, ready, uploading, success, failure }
|
|||||||
enum AttachmentParentType {
|
enum AttachmentParentType {
|
||||||
operation('operation_id'),
|
operation('operation_id'),
|
||||||
ticket('ticket_id'),
|
ticket('ticket_id'),
|
||||||
customer('customer_id');
|
customer('customer_id'),
|
||||||
|
shippingDocument('shipping_document_id');
|
||||||
|
|
||||||
final String dbColumn;
|
final String dbColumn;
|
||||||
const AttachmentParentType(this.dbColumn);
|
const AttachmentParentType(this.dbColumn);
|
||||||
|
|||||||
@@ -4,17 +4,27 @@ import 'package:flux/features/attachments/models/attachment_model.dart';
|
|||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
import 'package:flux/features/attachments/blocs/attachments_bloc.dart';
|
import 'package:flux/features/attachments/blocs/attachments_bloc.dart';
|
||||||
|
|
||||||
|
enum Bucket {
|
||||||
|
documents('documents'),
|
||||||
|
companyDocuments('company_documents');
|
||||||
|
|
||||||
|
final String value;
|
||||||
|
const Bucket(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
class AttachmentsRepository {
|
class AttachmentsRepository {
|
||||||
final _supabase = Supabase.instance.client;
|
final _supabase = Supabase.instance.client;
|
||||||
static const String _bucketName = 'documents';
|
|
||||||
static const String _tableName =
|
static const String _tableName =
|
||||||
'attachment'; // Cambia col vero nome della tua tabella se diverso!
|
'attachment'; // Cambia col vero nome della tua tabella se diverso!
|
||||||
|
|
||||||
/// Scarica i byte di un file direttamente da Supabase Storage
|
/// Scarica i byte di un file direttamente da Supabase Storage
|
||||||
Future<Uint8List> downloadAttachmentBytes(String storagePath) async {
|
Future<Uint8List> downloadAttachmentBytes({
|
||||||
|
required String storagePath,
|
||||||
|
required Bucket bucket,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
final Uint8List bytes = await _supabase.storage
|
final Uint8List bytes = await _supabase.storage
|
||||||
.from(_bucketName)
|
.from(bucket.value)
|
||||||
.download(storagePath);
|
.download(storagePath);
|
||||||
return bytes;
|
return bytes;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -31,6 +41,8 @@ class AttachmentsRepository {
|
|||||||
return 'ticket_id';
|
return 'ticket_id';
|
||||||
case AttachmentParentType.customer:
|
case AttachmentParentType.customer:
|
||||||
return 'customer_id';
|
return 'customer_id';
|
||||||
|
case AttachmentParentType.shippingDocument:
|
||||||
|
return 'shipping_document_id';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,41 +67,70 @@ class AttachmentsRepository {
|
|||||||
Future<void> uploadAndRegisterFile({
|
Future<void> uploadAndRegisterFile({
|
||||||
required String parentId,
|
required String parentId,
|
||||||
required AttachmentParentType parentType,
|
required AttachmentParentType parentType,
|
||||||
required PlatformFile pickedFile,
|
|
||||||
required String companyId,
|
required String companyId,
|
||||||
|
required Bucket bucket,
|
||||||
|
PlatformFile? pickedFile, // Ora è opzionale
|
||||||
|
Uint8List? rawBytes, // Alternativa: bytes grezzi
|
||||||
|
String? rawFileName, // Alternativa: nome del file
|
||||||
}) async {
|
}) async {
|
||||||
|
// 🛡️ L'ASSERT NINJA: O c'è il pickedFile, o ci sono i byte e il nome.
|
||||||
|
// L'assert funziona solo in debug, ma è perfetto per beccare subito errori di chiamata!
|
||||||
|
assert(
|
||||||
|
pickedFile != null || (rawBytes != null && rawFileName != null),
|
||||||
|
'Devi passare o un PlatformFile, oppure rawBytes e rawFileName!',
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (pickedFile.bytes == null) {
|
// 1. Normalizziamo i dati in base a cosa ci è stato passato
|
||||||
throw Exception(
|
final Uint8List finalBytes;
|
||||||
"I bytes del file sono vuoti! Ricarica la pagina senza cache.",
|
final String finalFileName;
|
||||||
);
|
final int finalFileSize;
|
||||||
|
|
||||||
|
if (pickedFile != null) {
|
||||||
|
if (pickedFile.bytes == null) {
|
||||||
|
throw Exception(
|
||||||
|
"I bytes del file sono vuoti! Ricarica la pagina senza cache.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
finalBytes = pickedFile.bytes!;
|
||||||
|
finalFileName = pickedFile.name;
|
||||||
|
finalFileSize = pickedFile.size;
|
||||||
|
} else {
|
||||||
|
// Se pickedFile è null, grazie all'assert sappiamo che questi non lo sono
|
||||||
|
finalBytes = rawBytes!;
|
||||||
|
finalFileName = rawFileName!;
|
||||||
|
finalFileSize = finalBytes.length; // Calcoliamo la size dai byte reali
|
||||||
}
|
}
|
||||||
|
|
||||||
final extension = pickedFile.extension ?? pickedFile.name.split('.').last;
|
// 2. Estraiamo l'estensione e puliamo il nome
|
||||||
final cleanName = pickedFile.name
|
final extension = finalFileName.contains('.')
|
||||||
|
? finalFileName.split('.').last
|
||||||
|
: ''; // Fallback se il file non ha estensione
|
||||||
|
|
||||||
|
final cleanName = finalFileName
|
||||||
.replaceAll(RegExp(r'[^\w\s\.-]'), '')
|
.replaceAll(RegExp(r'[^\w\s\.-]'), '')
|
||||||
.replaceAll(' ', '_');
|
.replaceAll(' ', '_');
|
||||||
|
|
||||||
// Creiamo un path ordinato: idAzienda/tipoEntita/idEntita/timestamp_nomefile
|
// 3. Creiamo un path ordinato: idAzienda/tipoEntita/idEntita/timestamp_nomefile
|
||||||
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
final timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||||
final storagePath =
|
final storagePath =
|
||||||
'$companyId/${parentType.name}/$parentId/${timestamp}_$cleanName';
|
'$companyId/${parentType.name}/$parentId/${timestamp}_$cleanName';
|
||||||
|
|
||||||
// 1. Upload su Supabase Storage
|
// 4. Upload su Supabase Storage
|
||||||
await _supabase.storage
|
await _supabase.storage
|
||||||
.from(_bucketName)
|
.from(bucket.value)
|
||||||
.uploadBinary(
|
.uploadBinary(
|
||||||
storagePath,
|
storagePath,
|
||||||
pickedFile.bytes!,
|
finalBytes,
|
||||||
fileOptions: FileOptions(contentType: _guessContentType(extension)),
|
fileOptions: FileOptions(contentType: _guessContentType(extension)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Creiamo la mappa per il DB dinamicamente
|
// 5. Creiamo la mappa per il DB dinamicamente
|
||||||
final Map<String, dynamic> insertData = {
|
final Map<String, dynamic> insertData = {
|
||||||
'company_id': companyId,
|
'company_id': companyId,
|
||||||
'name': pickedFile.name.replaceAll('.$extension', ''),
|
'name': finalFileName.replaceAll('.$extension', ''),
|
||||||
'extension': extension,
|
'extension': extension,
|
||||||
'file_size': pickedFile.size,
|
'file_size': finalFileSize,
|
||||||
'storage_path': storagePath,
|
'storage_path': storagePath,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +138,7 @@ class AttachmentsRepository {
|
|||||||
final columnName = _getColumnNameForParent(parentType);
|
final columnName = _getColumnNameForParent(parentType);
|
||||||
insertData[columnName] = parentId;
|
insertData[columnName] = parentId;
|
||||||
|
|
||||||
// 3. Salviamo su Postgres
|
// 6. Salviamo su Postgres
|
||||||
await _supabase.from(_tableName).insert(insertData);
|
await _supabase.from(_tableName).insert(insertData);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw Exception("Errore caricamento: $e");
|
throw Exception("Errore caricamento: $e");
|
||||||
@@ -108,6 +149,7 @@ class AttachmentsRepository {
|
|||||||
Future<void> deleteFiles({
|
Future<void> deleteFiles({
|
||||||
required List<AttachmentModel> files,
|
required List<AttachmentModel> files,
|
||||||
required AttachmentParentType currentContextType,
|
required AttachmentParentType currentContextType,
|
||||||
|
required Bucket bucket,
|
||||||
}) async {
|
}) async {
|
||||||
if (files.isEmpty) return;
|
if (files.isEmpty) return;
|
||||||
|
|
||||||
@@ -120,6 +162,7 @@ class AttachmentsRepository {
|
|||||||
AttachmentParentType.operation: file.operationId,
|
AttachmentParentType.operation: file.operationId,
|
||||||
AttachmentParentType.ticket: file.ticketId,
|
AttachmentParentType.ticket: file.ticketId,
|
||||||
AttachmentParentType.customer: file.customerId,
|
AttachmentParentType.customer: file.customerId,
|
||||||
|
AttachmentParentType.shippingDocument: file.shippingDocumentId,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. Simuliamo la rimozione del collegamento per il contesto attuale
|
// 2. Simuliamo la rimozione del collegamento per il contesto attuale
|
||||||
@@ -142,7 +185,7 @@ class AttachmentsRepository {
|
|||||||
await _supabase.from(_tableName).delete().eq('id', file.id!);
|
await _supabase.from(_tableName).delete().eq('id', file.id!);
|
||||||
|
|
||||||
if (file.storagePath != null) {
|
if (file.storagePath != null) {
|
||||||
await _supabase.storage.from(_bucketName).remove([
|
await _supabase.storage.from(bucket.value).remove([
|
||||||
file.storagePath!,
|
file.storagePath!,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class AttachmentModel extends Equatable {
|
|||||||
final String? customerId;
|
final String? customerId;
|
||||||
final String? operationId;
|
final String? operationId;
|
||||||
final String? ticketId;
|
final String? ticketId;
|
||||||
|
final String? shippingDocumentId;
|
||||||
final String name;
|
final String name;
|
||||||
final String extension;
|
final String extension;
|
||||||
final String? storagePath;
|
final String? storagePath;
|
||||||
@@ -21,6 +22,7 @@ class AttachmentModel extends Equatable {
|
|||||||
this.customerId,
|
this.customerId,
|
||||||
this.operationId,
|
this.operationId,
|
||||||
this.ticketId,
|
this.ticketId,
|
||||||
|
this.shippingDocumentId,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.extension,
|
required this.extension,
|
||||||
this.storagePath,
|
this.storagePath,
|
||||||
@@ -36,6 +38,7 @@ class AttachmentModel extends Equatable {
|
|||||||
customerId,
|
customerId,
|
||||||
operationId,
|
operationId,
|
||||||
ticketId,
|
ticketId,
|
||||||
|
shippingDocumentId,
|
||||||
name,
|
name,
|
||||||
extension,
|
extension,
|
||||||
storagePath,
|
storagePath,
|
||||||
@@ -63,6 +66,7 @@ class AttachmentModel extends Equatable {
|
|||||||
String? customerId,
|
String? customerId,
|
||||||
String? operationId,
|
String? operationId,
|
||||||
String? ticketId,
|
String? ticketId,
|
||||||
|
String? shippingDocumentId,
|
||||||
String? name,
|
String? name,
|
||||||
String? extension,
|
String? extension,
|
||||||
String? storagePath,
|
String? storagePath,
|
||||||
@@ -75,6 +79,7 @@ class AttachmentModel extends Equatable {
|
|||||||
customerId: customerId ?? this.customerId,
|
customerId: customerId ?? this.customerId,
|
||||||
operationId: operationId ?? this.operationId,
|
operationId: operationId ?? this.operationId,
|
||||||
ticketId: ticketId ?? this.ticketId,
|
ticketId: ticketId ?? this.ticketId,
|
||||||
|
shippingDocumentId: shippingDocumentId ?? this.shippingDocumentId,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
extension: extension ?? this.extension,
|
extension: extension ?? this.extension,
|
||||||
storagePath: storagePath ?? this.storagePath,
|
storagePath: storagePath ?? this.storagePath,
|
||||||
@@ -92,6 +97,7 @@ class AttachmentModel extends Equatable {
|
|||||||
customerId: map['customer_id'] as String?,
|
customerId: map['customer_id'] as String?,
|
||||||
operationId: map['operation_id'] as String?,
|
operationId: map['operation_id'] as String?,
|
||||||
ticketId: map['ticket_id'] as String?,
|
ticketId: map['ticket_id'] as String?,
|
||||||
|
shippingDocumentId: map['shipping_document_id'] as String?,
|
||||||
name: map['name'] as String,
|
name: map['name'] as String,
|
||||||
extension: map['extension'] as String,
|
extension: map['extension'] as String,
|
||||||
storagePath: map['storage_path'] as String?,
|
storagePath: map['storage_path'] as String?,
|
||||||
@@ -111,6 +117,7 @@ class AttachmentModel extends Equatable {
|
|||||||
'customer_id': customerId,
|
'customer_id': customerId,
|
||||||
'operation_id': operationId,
|
'operation_id': operationId,
|
||||||
'ticket_id': ticketId,
|
'ticket_id': ticketId,
|
||||||
|
'shipping_document_id': shippingDocumentId,
|
||||||
'file_size': fileSize,
|
'file_size': fileSize,
|
||||||
'company_id': companyId,
|
'company_id': companyId,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
import 'package:flux/features/tickets/data/tickets_shipment_repository.dart';
|
import 'package:flux/features/tickets/data/tickets_shipping_repository.dart';
|
||||||
import 'package:flux/features/tickets/models/shipment_document_model.dart';
|
import 'package:flux/features/tickets/models/shipping_document_model.dart';
|
||||||
import 'package:flux/features/master_data/providers/models/provider_location_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/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/data/document_sequence_repository.dart';
|
||||||
@@ -16,15 +15,15 @@ import 'package:printing/printing.dart';
|
|||||||
part 'ticket_shipping_state.dart';
|
part 'ticket_shipping_state.dart';
|
||||||
|
|
||||||
class TicketShippingCubit extends Cubit<TicketShippingState> {
|
class TicketShippingCubit extends Cubit<TicketShippingState> {
|
||||||
final TicketsShipmentRepository _repository =
|
final TicketsShippingRepository _repository =
|
||||||
GetIt.I<TicketsShipmentRepository>();
|
GetIt.I<TicketsShippingRepository>();
|
||||||
final DocumentSequenceRepository _sequenceRepository =
|
final DocumentSequenceRepository _sequenceRepository =
|
||||||
GetIt.I<DocumentSequenceRepository>();
|
GetIt.I<DocumentSequenceRepository>();
|
||||||
TicketShippingCubit({required List<TicketModel> tickets})
|
TicketShippingCubit({required List<TicketModel> tickets})
|
||||||
: super(
|
: super(
|
||||||
TicketShippingState(
|
TicketShippingState(
|
||||||
// Inizializziamo il modello direttamente nello stato!
|
// Inizializziamo il modello direttamente nello stato!
|
||||||
document: ShipmentDocumentModel(
|
document: ShippingDocumentModel(
|
||||||
companyId: GetIt.I.get<SessionCubit>().state.company!.id!,
|
companyId: GetIt.I.get<SessionCubit>().state.company!.id!,
|
||||||
ticketIds: tickets.map((t) => t.id!).toList(),
|
ticketIds: tickets.map((t) => t.id!).toList(),
|
||||||
providerId: '', // Sarà riempito alla selezione
|
providerId: '', // Sarà riempito alla selezione
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ enum TicketShippingStatus { initial, loading, success, failure }
|
|||||||
|
|
||||||
class TicketShippingState extends Equatable {
|
class TicketShippingState extends Equatable {
|
||||||
final TicketShippingStatus status;
|
final TicketShippingStatus status;
|
||||||
final ShipmentDocumentModel document;
|
final ShippingDocumentModel document;
|
||||||
final List<TicketModel> tickets;
|
final List<TicketModel> tickets;
|
||||||
|
|
||||||
// Dati di supporto per la UI
|
// Dati di supporto per la UI
|
||||||
@@ -25,7 +25,7 @@ class TicketShippingState extends Equatable {
|
|||||||
|
|
||||||
TicketShippingState copyWith({
|
TicketShippingState copyWith({
|
||||||
TicketShippingStatus? status,
|
TicketShippingStatus? status,
|
||||||
ShipmentDocumentModel? document,
|
ShippingDocumentModel? document,
|
||||||
List<ProviderModel>? availableProviders,
|
List<ProviderModel>? availableProviders,
|
||||||
List<ProviderLocationModel>? availableLocations,
|
List<ProviderLocationModel>? availableLocations,
|
||||||
bool? isAutoNumber,
|
bool? isAutoNumber,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class TicketRepository {
|
|||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
customer (*),
|
customer (*),
|
||||||
shipment_document (*),
|
shipment_document (*, attachments (*)), -- BAM! Deep Join
|
||||||
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 (*),
|
||||||
@@ -89,7 +89,7 @@ class TicketRepository {
|
|||||||
.select('''
|
.select('''
|
||||||
*,
|
*,
|
||||||
customer (*),
|
customer (*),
|
||||||
shipment_document (*),
|
shipment_document (*, attachments (*)),
|
||||||
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 (*),
|
||||||
@@ -188,7 +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 (*),
|
shipment_document (*, attachments (*)),
|
||||||
''')
|
''')
|
||||||
.eq('id', ticketId)
|
.eq('id', ticketId)
|
||||||
.single();
|
.single();
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
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';
|
|
||||||
import 'package:get_it/get_it.dart';
|
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
||||||
|
|
||||||
class TicketsShipmentRepository {
|
|
||||||
final _supabase = GetIt.I.get<SupabaseClient>();
|
|
||||||
|
|
||||||
Future<List<ProviderModel>> fetchRepairCenters() async {
|
|
||||||
try {
|
|
||||||
final response = await _supabase
|
|
||||||
.from('provider')
|
|
||||||
.select('*, provider_locations (*)')
|
|
||||||
.eq('is_active', true)
|
|
||||||
.order('name');
|
|
||||||
|
|
||||||
final allProviders = (response as List)
|
|
||||||
.map((row) => ProviderModel.fromMap(row as Map<String, dynamic>))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
// Filtriamo lato client per prendere SOLO i repairCenter
|
|
||||||
return allProviders
|
|
||||||
.where((p) => p.roles.contains(ProviderRole.repairCenter))
|
|
||||||
.toList();
|
|
||||||
} catch (e) {
|
|
||||||
throw ('Errore caricamento laboratori: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Salva il DDT nel DB, fa l'upload del PDF nello Storage e aggiorna il path
|
|
||||||
Future<String> createShipmentWithPdf({
|
|
||||||
required ShipmentDocumentModel document,
|
|
||||||
required Uint8List pdfBytes,
|
|
||||||
required String newTicketStatus,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
// 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. 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
|
|
||||||
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,
|
|
||||||
'shipment_document_id': documentid,
|
|
||||||
})
|
|
||||||
.inFilter('id', document.ticketIds);
|
|
||||||
|
|
||||||
// Restituiamo lo storagePath per usarlo subito nell'interfaccia se serve
|
|
||||||
return storagePath;
|
|
||||||
} catch (e) {
|
|
||||||
throw ('Errore durante il salvataggio e upload della spedizione: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
135
lib/features/tickets/data/tickets_shipping_repository.dart
Normal file
135
lib/features/tickets/data/tickets_shipping_repository.dart
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
|
import 'package:flux/features/attachments/blocs/attachments_bloc.dart';
|
||||||
|
import 'package:flux/features/attachments/data/attachments_repository.dart';
|
||||||
|
import 'package:flux/features/tickets/models/shipping_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';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:supabase_flutter/supabase_flutter.dart' hide Bucket;
|
||||||
|
|
||||||
|
class TicketsShippingRepository {
|
||||||
|
final _supabase = GetIt.I.get<SupabaseClient>();
|
||||||
|
final _companyId = GetIt.I.get<SessionCubit>().state.company!.id!;
|
||||||
|
|
||||||
|
Future<List<ProviderModel>> fetchRepairCenters() async {
|
||||||
|
try {
|
||||||
|
final response = await _supabase
|
||||||
|
.from('provider')
|
||||||
|
.select('*, provider_locations (*)')
|
||||||
|
.eq('is_active', true)
|
||||||
|
.order('name');
|
||||||
|
|
||||||
|
final allProviders = (response as List)
|
||||||
|
.map((row) => ProviderModel.fromMap(row as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Filtriamo lato client per prendere SOLO i repairCenter
|
||||||
|
return allProviders
|
||||||
|
.where((p) => p.roles.contains(ProviderRole.repairCenter))
|
||||||
|
.toList();
|
||||||
|
} catch (e) {
|
||||||
|
throw ('Errore caricamento laboratori: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ShippingDocumentModel>> fetchShipmentDocumentsForTicket(
|
||||||
|
String ticketId,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _supabase
|
||||||
|
.from('shipping_documents')
|
||||||
|
.select('*, attachments (*)')
|
||||||
|
.contains('ticket_ids', [ticketId]);
|
||||||
|
|
||||||
|
return (response as List)
|
||||||
|
.map(
|
||||||
|
(row) => ShippingDocumentModel.fromMap(row as Map<String, dynamic>),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
} catch (e) {
|
||||||
|
throw ('Errore caricamento documenti di trasporto: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<ShippingDocumentModel>> fetchCompanyShipmentDocuments() async {
|
||||||
|
try {
|
||||||
|
final response = await _supabase
|
||||||
|
.from('shipping_documents')
|
||||||
|
.select('*, attachments (*)')
|
||||||
|
.eq('company_id', _companyId);
|
||||||
|
|
||||||
|
return (response as List)
|
||||||
|
.map(
|
||||||
|
(row) => ShippingDocumentModel.fromMap(row as Map<String, dynamic>),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
} catch (e) {
|
||||||
|
throw ('Errore caricamento documenti di trasporto aziendali: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<ShippingDocumentModel> fetchShipmentDocumentById(
|
||||||
|
String documentId,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await _supabase
|
||||||
|
.from('shipping_documents')
|
||||||
|
.select('*, attachments (*)')
|
||||||
|
.eq('id', documentId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
return ShippingDocumentModel.fromMap(response);
|
||||||
|
} catch (e) {
|
||||||
|
throw ('Errore caricamento documento di trasporto: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Salva il DDT nel DB, carica il PDF nello Storage e lo registra come Attachment ufficiale
|
||||||
|
Future<void> createShipmentWithPdf({
|
||||||
|
required ShippingDocumentModel document,
|
||||||
|
required Uint8List pdfBytes,
|
||||||
|
required String newTicketStatus,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final attachmentsRepo = GetIt.I.get<AttachmentsRepository>();
|
||||||
|
|
||||||
|
// 1. Inseriamo prima il Documento di Trasporto (DDT) su Postgres
|
||||||
|
// Il modello iniziale non ha ancora gli allegati popolati, quindi passiamo il toMap standard
|
||||||
|
final savedDocument = await _supabase
|
||||||
|
.from('shipping_documents')
|
||||||
|
.insert(document.toMap())
|
||||||
|
.select('id')
|
||||||
|
.single();
|
||||||
|
|
||||||
|
final documentId = savedDocument['id'] as String;
|
||||||
|
|
||||||
|
// 2. Prepariamo il nome del file PDF (es: DDT_123_2026.pdf)
|
||||||
|
final fileName =
|
||||||
|
'DDT_${document.docNumber.replaceAll('/', '_').replaceAll(' ', '_')}.pdf';
|
||||||
|
|
||||||
|
// 3. IL TOCCO MASTER: Delegiamo l'upload e la registrazione nel DB all'AttachmentsRepository
|
||||||
|
// Questo creerà la riga nella tabella degli allegati agganciando lo 'shipping_document_id'
|
||||||
|
await attachmentsRepo.uploadAndRegisterFile(
|
||||||
|
parentId: documentId,
|
||||||
|
parentType: AttachmentParentType.shippingDocument, // Il tuo nuovo enum!
|
||||||
|
companyId: document.companyId,
|
||||||
|
bucket: Bucket.companyDocuments,
|
||||||
|
rawBytes: pdfBytes,
|
||||||
|
rawFileName: fileName,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4. Aggiorniamo lo stato di TUTTI i ticket inclusi nel DDT e li colleghiamo all'ID del DDT
|
||||||
|
await _supabase
|
||||||
|
.from('ticket')
|
||||||
|
.update({
|
||||||
|
'ticket_status': newTicketStatus,
|
||||||
|
'shipment_document_id': documentId,
|
||||||
|
})
|
||||||
|
.inFilter('id', document.ticketIds);
|
||||||
|
} catch (e) {
|
||||||
|
throw ('Errore durante il salvataggio e upload della spedizione con allegato: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:flux/features/attachments/models/attachment_model.dart';
|
||||||
|
|
||||||
class ShipmentDocumentModel extends Equatable {
|
class ShippingDocumentModel extends Equatable {
|
||||||
final String? id;
|
final String? id;
|
||||||
final String companyId;
|
final String companyId;
|
||||||
final List<String> ticketIds;
|
final List<String> ticketIds;
|
||||||
@@ -12,8 +13,9 @@ class ShipmentDocumentModel extends Equatable {
|
|||||||
final double? weight;
|
final double? weight;
|
||||||
final String shippingReason;
|
final String shippingReason;
|
||||||
final String? notes;
|
final String? notes;
|
||||||
|
final List<AttachmentModel> attachments;
|
||||||
|
|
||||||
const ShipmentDocumentModel({
|
const ShippingDocumentModel({
|
||||||
this.id,
|
this.id,
|
||||||
required this.companyId,
|
required this.companyId,
|
||||||
required this.ticketIds,
|
required this.ticketIds,
|
||||||
@@ -25,9 +27,10 @@ class ShipmentDocumentModel extends Equatable {
|
|||||||
this.weight,
|
this.weight,
|
||||||
this.shippingReason = 'Riparazione esterna',
|
this.shippingReason = 'Riparazione esterna',
|
||||||
this.notes,
|
this.notes,
|
||||||
|
this.attachments = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
ShipmentDocumentModel copyWith({
|
ShippingDocumentModel copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
String? companyId,
|
String? companyId,
|
||||||
List<String>? ticketIds,
|
List<String>? ticketIds,
|
||||||
@@ -39,8 +42,9 @@ class ShipmentDocumentModel extends Equatable {
|
|||||||
double? weight,
|
double? weight,
|
||||||
String? shippingReason,
|
String? shippingReason,
|
||||||
String? notes,
|
String? notes,
|
||||||
|
List<AttachmentModel>? attachments,
|
||||||
}) {
|
}) {
|
||||||
return ShipmentDocumentModel(
|
return ShippingDocumentModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
companyId: companyId ?? this.companyId,
|
companyId: companyId ?? this.companyId,
|
||||||
ticketIds: ticketIds ?? this.ticketIds,
|
ticketIds: ticketIds ?? this.ticketIds,
|
||||||
@@ -53,11 +57,12 @@ class ShipmentDocumentModel extends Equatable {
|
|||||||
weight: weight ?? this.weight,
|
weight: weight ?? this.weight,
|
||||||
shippingReason: shippingReason ?? this.shippingReason,
|
shippingReason: shippingReason ?? this.shippingReason,
|
||||||
notes: notes ?? this.notes,
|
notes: notes ?? this.notes,
|
||||||
|
attachments: attachments ?? this.attachments,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory ShipmentDocumentModel.fromMap(Map<String, dynamic> map) {
|
factory ShippingDocumentModel.fromMap(Map<String, dynamic> map) {
|
||||||
return ShipmentDocumentModel(
|
return ShippingDocumentModel(
|
||||||
id: map['id'],
|
id: map['id'],
|
||||||
companyId: map['company_id'],
|
companyId: map['company_id'],
|
||||||
ticketIds: List<String>.from(map['ticket_ids']),
|
ticketIds: List<String>.from(map['ticket_ids']),
|
||||||
@@ -69,6 +74,11 @@ class ShipmentDocumentModel extends Equatable {
|
|||||||
weight: map['weight'] != null ? (map['weight'] as num).toDouble() : null,
|
weight: map['weight'] != null ? (map['weight'] as num).toDouble() : null,
|
||||||
shippingReason: map['shipping_reason'] ?? 'Riparazione esterna',
|
shippingReason: map['shipping_reason'] ?? 'Riparazione esterna',
|
||||||
notes: map['notes'],
|
notes: map['notes'],
|
||||||
|
attachments:
|
||||||
|
(map['attachments'] as List<dynamic>?)
|
||||||
|
?.map((e) => AttachmentModel.fromMap(e))
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,5 +99,17 @@ class ShipmentDocumentModel extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [id, docNumber, ticketIds];
|
List<Object?> get props => [
|
||||||
|
id,
|
||||||
|
docNumber,
|
||||||
|
ticketIds,
|
||||||
|
providerId,
|
||||||
|
destinationLocationId,
|
||||||
|
docDate,
|
||||||
|
packageCount,
|
||||||
|
weight,
|
||||||
|
shippingReason,
|
||||||
|
notes,
|
||||||
|
attachments,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
@@ -1,7 +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';
|
import 'package:flux/features/tickets/models/shipping_document_model.dart';
|
||||||
|
|
||||||
/// Enum per il tipo di ticket
|
/// Enum per il tipo di ticket
|
||||||
enum TicketType {
|
enum TicketType {
|
||||||
@@ -121,8 +121,7 @@ class TicketModel extends Equatable {
|
|||||||
final String? assignedToId;
|
final String? assignedToId;
|
||||||
final String? assignedToName;
|
final String? assignedToName;
|
||||||
final String? includedAccessories;
|
final String? includedAccessories;
|
||||||
final ShipmentDocumentModel?
|
final ShippingDocumentModel? shippingDocument;
|
||||||
shippingDocument; // Per tenere in memoria i dati del DDT associato al ticket
|
|
||||||
|
|
||||||
const TicketModel({
|
const TicketModel({
|
||||||
this.id,
|
this.id,
|
||||||
@@ -215,7 +214,7 @@ class TicketModel extends Equatable {
|
|||||||
String? assignedToId,
|
String? assignedToId,
|
||||||
String? assignedToName,
|
String? assignedToName,
|
||||||
String? includedAccessories,
|
String? includedAccessories,
|
||||||
ShipmentDocumentModel? shippingDocument,
|
ShippingDocumentModel? shippingDocument,
|
||||||
}) {
|
}) {
|
||||||
return TicketModel(
|
return TicketModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@@ -312,7 +311,7 @@ class TicketModel extends Equatable {
|
|||||||
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
|
shippingDocument: map['shipping_document'] != null
|
||||||
? ShipmentDocumentModel.fromMap(
|
? ShippingDocumentModel.fromMap(
|
||||||
map['shipping_document'] as Map<String, dynamic>,
|
map['shipping_document'] as Map<String, dynamic>,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flux/core/routes/routes.dart';
|
import 'package:flux/core/routes/routes.dart';
|
||||||
|
import 'package:flux/features/attachments/blocs/attachments_bloc.dart';
|
||||||
|
import 'package:flux/features/attachments/models/attachment_model.dart';
|
||||||
|
import 'package:flux/features/attachments/ui/attachment_viewer_screen.dart';
|
||||||
import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart';
|
import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart';
|
||||||
import 'package:flux/features/tickets/models/ticket_model.dart';
|
import 'package:flux/features/tickets/models/ticket_model.dart';
|
||||||
import 'package:flux/features/tickets/models/ticket_status_extension.dart';
|
import 'package:flux/features/tickets/models/ticket_status_extension.dart';
|
||||||
@@ -20,6 +23,32 @@ class TicketListCard extends StatelessWidget {
|
|||||||
required this.isDesktop,
|
required this.isDesktop,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
void _openFile({
|
||||||
|
required BuildContext context,
|
||||||
|
required AttachmentModel file,
|
||||||
|
}) {
|
||||||
|
// 1. Catturiamo il BLoC dalla pagina corrente prima di navigare
|
||||||
|
final operationFilesBloc = context.read<AttachmentsBloc>();
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (viewerContext) => BlocProvider.value(
|
||||||
|
value: operationFilesBloc,
|
||||||
|
child: AttachmentViewerScreen(
|
||||||
|
attachment: file,
|
||||||
|
onRename: (newName) {
|
||||||
|
// Spara l'evento al BLoC e lui farà il resto!
|
||||||
|
operationFilesBloc.add(RenameAttachmentEvent(file, newName));
|
||||||
|
},
|
||||||
|
onDelete: () {
|
||||||
|
operationFilesBloc.add(DeleteSpecificAttachmentEvent(file));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final statusColor = ticket.ticketStatus.color;
|
final statusColor = ticket.ticketStatus.color;
|
||||||
@@ -110,21 +139,57 @@ class TicketListCard extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (ticket.shippingDocument != null) ...[
|
if (ticket.shippingDocument != null &&
|
||||||
Row(
|
ticket.shippingDocument!.attachments.isNotEmpty) ...[
|
||||||
children: [
|
IconButton(
|
||||||
const Text('DDT'),
|
icon: const Icon(
|
||||||
const SizedBox(width: 4),
|
Icons.picture_as_pdf,
|
||||||
InkWell(
|
color: Colors.redAccent,
|
||||||
borderRadius: BorderRadius.circular(20),
|
),
|
||||||
onTap: () {},
|
onPressed: () {
|
||||||
child: const Icon(
|
final attachments =
|
||||||
Icons.picture_as_pdf,
|
ticket.shippingDocument!.attachments;
|
||||||
color: Colors.redAccent,
|
|
||||||
size: 20,
|
if (attachments.length == 1) {
|
||||||
),
|
// CASO 1: C'è solo il DDT. Apriamo SUBITO il Document Viewer!
|
||||||
),
|
_openFile(
|
||||||
],
|
context: context,
|
||||||
|
file: attachments.first,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// CASO 2: Più allegati. Mostriamo una BottomSheet fulminea per far scegliere all'utente.
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: attachments.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final file = attachments[index];
|
||||||
|
return ListTile(
|
||||||
|
leading: Icon(
|
||||||
|
file.extension == 'pdf'
|
||||||
|
? Icons.picture_as_pdf
|
||||||
|
: Icons.image,
|
||||||
|
),
|
||||||
|
title: Text(file.name),
|
||||||
|
subtitle: Text(
|
||||||
|
'${(file.fileSize / 1024).toStringAsFixed(1)} KB',
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
Navigator.pop(
|
||||||
|
context,
|
||||||
|
); // Chiude la scelta
|
||||||
|
_openFile(
|
||||||
|
context: context,
|
||||||
|
file: file,
|
||||||
|
); // Apre il viewer
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:flux/features/company/models/company_model.dart';
|
import 'package:flux/features/company/models/company_model.dart';
|
||||||
import 'package:flux/features/tickets/models/shipment_document_model.dart';
|
import 'package:flux/features/tickets/models/shipping_document_model.dart';
|
||||||
import 'package:flux/features/master_data/providers/models/provider_location_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/master_data/providers/models/provider_model.dart';
|
||||||
import 'package:flux/features/tickets/models/ticket_model.dart';
|
import 'package:flux/features/tickets/models/ticket_model.dart';
|
||||||
@@ -13,7 +13,7 @@ class TicketShippingPdfService {
|
|||||||
required CompanyModel company,
|
required CompanyModel company,
|
||||||
required ProviderModel provider,
|
required ProviderModel provider,
|
||||||
required ProviderLocationModel location,
|
required ProviderLocationModel location,
|
||||||
required ShipmentDocumentModel document,
|
required ShippingDocumentModel document,
|
||||||
required List<TicketModel> tickets,
|
required List<TicketModel> tickets,
|
||||||
}) async {
|
}) async {
|
||||||
final pdf = pw.Document();
|
final pdf = pw.Document();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
|
|||||||
import 'package:flux/features/attachments/data/attachments_repository.dart';
|
import 'package:flux/features/attachments/data/attachments_repository.dart';
|
||||||
import 'package:flux/features/auth/bloc/auth_cubit.dart';
|
import 'package:flux/features/auth/bloc/auth_cubit.dart';
|
||||||
import 'package:flux/features/company/data/company_repository.dart';
|
import 'package:flux/features/company/data/company_repository.dart';
|
||||||
import 'package:flux/features/tickets/data/tickets_shipment_repository.dart';
|
import 'package:flux/features/tickets/data/tickets_shipping_repository.dart';
|
||||||
import 'package:flux/features/master_data/providers/blocs/provider_list_cubit.dart';
|
import 'package:flux/features/master_data/providers/blocs/provider_list_cubit.dart';
|
||||||
import 'package:flux/features/operations/blocs/operation_list_cubit.dart';
|
import 'package:flux/features/operations/blocs/operation_list_cubit.dart';
|
||||||
import 'package:flux/features/operations/data/operations_repository.dart';
|
import 'package:flux/features/operations/data/operations_repository.dart';
|
||||||
@@ -127,8 +127,8 @@ Future<void> setupLocator() async {
|
|||||||
);
|
);
|
||||||
getIt.registerLazySingleton<CompanyRepository>(() => CompanyRepository());
|
getIt.registerLazySingleton<CompanyRepository>(() => CompanyRepository());
|
||||||
getIt.registerLazySingleton<TrackingRepository>(() => TrackingRepository());
|
getIt.registerLazySingleton<TrackingRepository>(() => TrackingRepository());
|
||||||
getIt.registerLazySingleton<TicketsShipmentRepository>(
|
getIt.registerLazySingleton<TicketsShippingRepository>(
|
||||||
() => TicketsShipmentRepository(),
|
() => TicketsShippingRepository(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user