Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-05-01 11:54:39 +02:00
parent f8bcac51e1
commit ac97e47771
9 changed files with 358 additions and 321 deletions

View File

@@ -2,6 +2,7 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/utils/extensions.dart';
import 'package:flux/features/attachments/models/attachment_model.dart';
import 'package:flux/features/customers/data/customer_repository.dart';
import 'package:flux/features/customers/models/customer_file_model.dart';
import 'package:flux/features/operations/models/operation_file_model.dart';
@@ -21,11 +22,8 @@ class OperationsRepository {
.from('operation')
.select('''
*,
customer(nome),
energy_operation(*),
fin_operation(*),
entertainment_operation(*),
operation_file(*)
customer(name),
staff_member(name)
''')
.eq('id', id)
.single();
@@ -45,16 +43,13 @@ class OperationsRepository {
DateTimeRange? dateRange,
}) async {
try {
// Nota: 'customer(name, surname)' serve per il display name nella card
var query = _supabase
.from('operation')
.select('''
*,
customer(nome),
energy_operation(*),
fin_operation(*),
entertainment_operation(*),
operation_file(*)
customer(name),
staff_member(name),
attachments(*)
''')
.eq('company_id', companyId);
@@ -68,7 +63,7 @@ class OperationsRepository {
if (searchTerm != null && searchTerm.isNotEmpty) {
// Filtra sui campi della tabella principale O su quelli della tabella joinata
query = query.or(
'number.ilike.%$searchTerm%,note.ilike.%$searchTerm%,customer.nome.ilike.%$searchTerm%',
'reference.ilike.%$searchTerm%,note.ilike.%$searchTerm%,customer.name.ilike.%$searchTerm%',
);
}
@@ -80,7 +75,7 @@ class OperationsRepository {
.map((map) => OperationModel.fromMap(map))
.toList();
} catch (e) {
throw Exception('Errore nel caricamento servizi: $e');
throw Exception('$e');
}
}
@@ -112,66 +107,9 @@ class OperationsRepository {
final String newId = operationData['id'];
// 2. MODIFICA: Pulizia atomica dei figli
// Se stiamo modificando (id != null), resettiamo le tabelle collegate
if (operation.id != null) {
await Future.wait([
_supabase.from('energy_operation').delete().eq('operation_id', newId),
_supabase.from('fin_operation').delete().eq('operation_id', newId),
_supabase
.from('entertainment_operation')
.delete()
.eq('operation_id', newId),
// Aggiungi qui eventuali altre tabelle pivot o file
]);
}
// 3. Inserimento dei moduli in parallelo per velocità
final List<Future> insertTasks = [];
if (operation.energyOperations.isNotEmpty) {
insertTasks.add(
_supabase
.from('energy_operation')
.insert(
operation.energyOperations
.map((item) => item.copyWith(operationId: newId).toMap())
.toList(),
),
);
}
if (operation.finOperations.isNotEmpty) {
insertTasks.add(
_supabase
.from('fin_operation')
.insert(
operation.finOperations
.map((item) => item.copyWith(operationId: newId).toMap())
.toList(),
),
);
}
if (operation.entertainmentOperations.isNotEmpty) {
insertTasks.add(
_supabase
.from('entertainment_operation')
.insert(
operation.entertainmentOperations
.map((item) => item.copyWith(operationId: newId).toMap())
.toList(),
),
);
}
if (insertTasks.isNotEmpty) {
await Future.wait(insertTasks);
}
// 4. UPLOAD DEI FILE LOCALI (Nuovi)
// Filtriamo solo i file che non hanno ancora un ID (quindi sono locali)
final localFilesToUpload = operation.files
final localFilesToUpload = operation.attachments
.where((f) => f.id == null)
.toList();
@@ -202,7 +140,7 @@ class OperationsRepository {
);
// B. Inserimento riga nel DB relazionale
await _supabase.from('operation_file').insert(fileToSave.toMap());
await _supabase.from('attachment').insert(fileToSave.toMap());
}
uploadTasks.add(uploadAndLink());
@@ -219,10 +157,9 @@ class OperationsRepository {
.from('operation')
.select('''
*,
energy_operation(*),
fin_operation(*),
entertainment_operation(*),
operation_file(*)
staff_member(name),
customer(name),
attachments(*)
''')
.eq('id', newId)
.single();
@@ -230,7 +167,7 @@ class OperationsRepository {
return OperationModel.fromMap(updatedOperationData);
} catch (e) {
// Qui potresti aggiungere una logica di "rollback manuale" se necessario
throw Exception('Errore durante il salvataggio corazzato: $e');
throw Exception('$e');
}
}
@@ -239,7 +176,7 @@ class OperationsRepository {
try {
await _supabase.from('operation').delete().eq('id', id);
} catch (e) {
throw Exception('Errore durante l\'eliminazione: $e');
throw Exception('$e');
}
}
@@ -249,16 +186,17 @@ class OperationsRepository {
// Cerchiamo i tipi più frequenti associati ai servizi di questa company
// Nota: dobbiamo passare attraverso la tabella 'operation' per filtrare per company_id
final response = await _supabase
.from('entertainment_operation')
.select('type, operation!inner(store!inner(company_id))')
.eq('operation.store.company_id', companyId)
.limit(100); // Prendiamo un campione
.from('operation')
.select('description')
.eq('company_id', companyId)
.eq('type', 'Entertainment')
.limit(50); // Prendiamo un campione
// Logica rapida per contare le occorrenze e prendere i primi 5
final Map<String, int> counts = {};
for (var item in (response as List)) {
final type = item['type'] as String;
counts[type] = (counts[type] ?? 0) + 1;
final description = item['description'] as String;
counts[description] = (counts[description] ?? 0) + 1;
}
var sortedKeys = counts.keys.toList()
@@ -276,19 +214,19 @@ class OperationsRepository {
}
/// Ascolta in tempo reale i file caricati per una pratica
Stream<List<OperationFileModel>> getOperationFilesStream(String operationId) {
Stream<List<AttachmentModel>> getOperationFilesStream(String operationId) {
return _supabase
.from('operation_file')
.from('attachment')
.stream(primaryKey: ['id'])
.eq('operation_id', operationId)
.order('created_at', ascending: false)
.map(
(listOfMaps) =>
listOfMaps.map((map) => OperationFileModel.fromMap(map)).toList(),
listOfMaps.map((map) => AttachmentModel.fromMap(map)).toList(),
);
}
Future<OperationFileModel> uploadAndRegisterOperationFile({
Future<AttachmentModel> uploadAndRegisterOperationFile({
required String operationId,
required PlatformFile pickedFile,
}) async {
@@ -299,7 +237,8 @@ class OperationsRepository {
final storagePath =
'$companyId/operations/$operationId/${DateTime.now().millisecondsSinceEpoch}_$cleanFileName';
final int fileSize = pickedFile.size;
final fileToSave = OperationFileModel(
final fileToSave = AttachmentModel(
companyId: GetIt.I.get<SessionCubit>().state.company!.id!,
operationId: operationId,
name: cleanFileName.fileNameWithoutExtension(),
extension: cleanFileName.fileExtension(),
@@ -327,12 +266,12 @@ class OperationsRepository {
}
final response = await _supabase
.from('operation_file')
.from('attachment')
.insert(fileToSave.toMap())
.select()
.single();
return OperationFileModel.fromMap(response);
return AttachmentModel.fromMap(response);
} catch (e) {
throw 'Errore durante l\'upload: $e';
}
@@ -342,17 +281,13 @@ class OperationsRepository {
required OperationFileModel file,
required String customerId,
}) async {
CustomerFileModel fileToCopy = CustomerFileModel(
customerId: customerId,
name: file.name,
storagePath: file.storagePath,
extension: file.extension,
fileSize: file.fileSize,
);
await _customerRepository.saveFileReference(fileToCopy);
await _supabase
.from('attachment')
.update({'customer_id': customerId})
.eq('id', file.id!);
}
Future<void> deleteOperationFiles(List<OperationFileModel> files) async {
Future<void> deleteOperationFiles(List<AttachmentModel> files) async {
if (files.isEmpty) return;
// 1. Prepariamo le liste di ID e di Percorsi
final List<String> idsToDelete = files.map((f) => f.id!).toList();
@@ -360,18 +295,14 @@ class OperationsRepository {
try {
await _supabase
.from('operation_file')
.delete()
.from('attachment')
.update({'operation_id': null})
.inFilter('id', idsToDelete);
await _supabase.storage.from('documents').remove(storagePaths);
debugPrint("Eliminati con successo ${files.length} file.");
} on PostgrestException catch (e) {
debugPrint("Errore DB: ${e.message}");
throw 'Errore database: ${e.message}';
} catch (e) {
debugPrint("Errore generico: $e");
throw 'Errore durante l\'eliminazione dei file: $e';
}
}