refactor shipping attachments and changed shipment to shipping for coherence

This commit is contained in:
2026-05-18 12:00:07 +02:00
parent b06a655bc3
commit 5e99324201
15 changed files with 359 additions and 152 deletions

View File

@@ -41,6 +41,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
add(LoadAttachmentsEvent(parentId: parentId));
}
}
FutureOr<void> _onParentEntitySaved(
ParentEntitySavedEvent event,
Emitter<AttachmentsState> emit,
@@ -67,6 +68,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
parentType: state.parentType,
pickedFile: fakePlatformFile,
companyId: companyId!,
bucket: _getBucketForParentType,
);
}).toList();
@@ -156,6 +158,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
parentType: state.parentType,
pickedFile: file,
companyId: companyId!,
bucket: _getBucketForParentType,
);
}).toList();
@@ -192,6 +195,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
parentType: state.parentType,
pickedFile: file,
companyId: event.companyId,
bucket: _getBucketForParentType,
),
);
}
@@ -218,6 +222,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
parentType: state.parentType,
pickedFile: fakePlatformFile,
companyId: event.companyId,
bucket: _getBucketForParentType,
),
);
}
@@ -242,6 +247,7 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
await _repository.deleteFiles(
files: state.selectedFiles,
currentContextType: state.parentType,
bucket: _getBucketForParentType,
);
emit(state.copyWith(status: AttachmentsStatus.ready, selectedFiles: []));
} catch (e) {
@@ -298,6 +304,8 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
return file.copyWith(ticketId: event.targetId);
case AttachmentParentType.operation:
return file.copyWith(operationId: event.targetId);
case AttachmentParentType.shippingDocument:
return file.copyWith(shippingDocumentId: event.targetId);
}
}
return file;
@@ -386,4 +394,17 @@ class AttachmentsBloc extends Bloc<AttachmentsEvent, AttachmentsState> {
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;
}
}
}

View File

@@ -5,7 +5,8 @@ enum AttachmentsStatus { initial, loading, ready, uploading, success, failure }
enum AttachmentParentType {
operation('operation_id'),
ticket('ticket_id'),
customer('customer_id');
customer('customer_id'),
shippingDocument('shipping_document_id');
final String dbColumn;
const AttachmentParentType(this.dbColumn);

View File

@@ -4,17 +4,27 @@ import 'package:flux/features/attachments/models/attachment_model.dart';
import 'package:supabase_flutter/supabase_flutter.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 {
final _supabase = Supabase.instance.client;
static const String _bucketName = 'documents';
static const String _tableName =
'attachment'; // Cambia col vero nome della tua tabella se diverso!
/// 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 {
final Uint8List bytes = await _supabase.storage
.from(_bucketName)
.from(bucket.value)
.download(storagePath);
return bytes;
} catch (e) {
@@ -31,6 +41,8 @@ class AttachmentsRepository {
return 'ticket_id';
case AttachmentParentType.customer:
return 'customer_id';
case AttachmentParentType.shippingDocument:
return 'shipping_document_id';
}
}
@@ -55,41 +67,70 @@ class AttachmentsRepository {
Future<void> uploadAndRegisterFile({
required String parentId,
required AttachmentParentType parentType,
required PlatformFile pickedFile,
required String companyId,
required Bucket bucket,
PlatformFile? pickedFile, // Ora è opzionale
Uint8List? rawBytes, // Alternativa: bytes grezzi
String? rawFileName, // Alternativa: nome del file
}) 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 {
if (pickedFile.bytes == null) {
throw Exception(
"I bytes del file sono vuoti! Ricarica la pagina senza cache.",
);
// 1. Normalizziamo i dati in base a cosa ci è stato passato
final Uint8List finalBytes;
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;
final cleanName = pickedFile.name
// 2. Estraiamo l'estensione e puliamo il nome
final extension = finalFileName.contains('.')
? finalFileName.split('.').last
: ''; // Fallback se il file non ha estensione
final cleanName = finalFileName
.replaceAll(RegExp(r'[^\w\s\.-]'), '')
.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 storagePath =
'$companyId/${parentType.name}/$parentId/${timestamp}_$cleanName';
// 1. Upload su Supabase Storage
// 4. Upload su Supabase Storage
await _supabase.storage
.from(_bucketName)
.from(bucket.value)
.uploadBinary(
storagePath,
pickedFile.bytes!,
finalBytes,
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 = {
'company_id': companyId,
'name': pickedFile.name.replaceAll('.$extension', ''),
'name': finalFileName.replaceAll('.$extension', ''),
'extension': extension,
'file_size': pickedFile.size,
'file_size': finalFileSize,
'storage_path': storagePath,
};
@@ -97,7 +138,7 @@ class AttachmentsRepository {
final columnName = _getColumnNameForParent(parentType);
insertData[columnName] = parentId;
// 3. Salviamo su Postgres
// 6. Salviamo su Postgres
await _supabase.from(_tableName).insert(insertData);
} catch (e) {
throw Exception("Errore caricamento: $e");
@@ -108,6 +149,7 @@ class AttachmentsRepository {
Future<void> deleteFiles({
required List<AttachmentModel> files,
required AttachmentParentType currentContextType,
required Bucket bucket,
}) async {
if (files.isEmpty) return;
@@ -120,6 +162,7 @@ class AttachmentsRepository {
AttachmentParentType.operation: file.operationId,
AttachmentParentType.ticket: file.ticketId,
AttachmentParentType.customer: file.customerId,
AttachmentParentType.shippingDocument: file.shippingDocumentId,
};
// 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!);
if (file.storagePath != null) {
await _supabase.storage.from(_bucketName).remove([
await _supabase.storage.from(bucket.value).remove([
file.storagePath!,
]);
}

View File

@@ -8,6 +8,7 @@ class AttachmentModel extends Equatable {
final String? customerId;
final String? operationId;
final String? ticketId;
final String? shippingDocumentId;
final String name;
final String extension;
final String? storagePath;
@@ -21,6 +22,7 @@ class AttachmentModel extends Equatable {
this.customerId,
this.operationId,
this.ticketId,
this.shippingDocumentId,
required this.name,
required this.extension,
this.storagePath,
@@ -36,6 +38,7 @@ class AttachmentModel extends Equatable {
customerId,
operationId,
ticketId,
shippingDocumentId,
name,
extension,
storagePath,
@@ -63,6 +66,7 @@ class AttachmentModel extends Equatable {
String? customerId,
String? operationId,
String? ticketId,
String? shippingDocumentId,
String? name,
String? extension,
String? storagePath,
@@ -75,6 +79,7 @@ class AttachmentModel extends Equatable {
customerId: customerId ?? this.customerId,
operationId: operationId ?? this.operationId,
ticketId: ticketId ?? this.ticketId,
shippingDocumentId: shippingDocumentId ?? this.shippingDocumentId,
name: name ?? this.name,
extension: extension ?? this.extension,
storagePath: storagePath ?? this.storagePath,
@@ -92,6 +97,7 @@ class AttachmentModel extends Equatable {
customerId: map['customer_id'] as String?,
operationId: map['operation_id'] as String?,
ticketId: map['ticket_id'] as String?,
shippingDocumentId: map['shipping_document_id'] as String?,
name: map['name'] as String,
extension: map['extension'] as String,
storagePath: map['storage_path'] as String?,
@@ -111,6 +117,7 @@ class AttachmentModel extends Equatable {
'customer_id': customerId,
'operation_id': operationId,
'ticket_id': ticketId,
'shipping_document_id': shippingDocumentId,
'file_size': fileSize,
'company_id': companyId,
};