Files
flux/lib/features/customers/data/customer_repository.dart

200 lines
6.2 KiB
Dart
Raw Normal View History

2026-04-11 12:40:03 +02:00
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/utils/string_extensions.dart';
2026-04-11 12:40:03 +02:00
import 'package:flux/features/customers/models/customer_file_model.dart';
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import '../models/customer_model.dart';
class CustomerRepository {
final SupabaseClient _supabase = GetIt.I<SupabaseClient>();
final String companyId = GetIt.I.get<SessionCubit>().state.company!.id!;
// Crea un nuovo cliente
Future<CustomerModel> saveCustomer(CustomerModel customer) async {
try {
final response = await _supabase
.from('customer')
.upsert(customer.toJson())
.select()
.single();
return CustomerModel.fromMap(response);
} catch (e) {
throw 'Errore durante il salvataggio del cliente: $e';
}
}
2026-04-10 11:11:55 +02:00
Future<CustomerModel> updateCustomer(CustomerModel customer) async {
try {
final response = await _supabase
2026-04-10 11:11:55 +02:00
.from('customer')
.update(customer.toJson())
.eq('id', customer.id!)
.select()
.single();
return CustomerModel.fromMap(response);
2026-04-10 11:11:55 +02:00
} catch (e) {
throw 'Errore durante la modifica del cliente: $e';
}
}
// Recupera tutti i clienti dell'azienda
Future<List<CustomerModel>> getCustomers(String companyId) async {
try {
final response = await _supabase
.from('customer')
.select('''
*,
customer_file(*)
''')
.eq('company_id', companyId)
.eq('is_active', true)
.order('nome');
return (response as List).map((c) => CustomerModel.fromMap(c)).toList();
} catch (e) {
throw 'Errore nel recupero clienti';
}
}
// Ricerca clienti per nome o telefono (fondamentale per la UX)
Future<List<CustomerModel>> searchCustomers(
String companyId,
String query,
) async {
try {
final response = await _supabase
.from('customer')
.select()
.eq('company_id', companyId)
.or('nome.ilike.%$query%,telefono.ilike.%$query%')
.limit(10);
return (response as List).map((c) => CustomerModel.fromMap(c)).toList();
} catch (e) {
return [];
}
}
2026-04-11 12:40:03 +02:00
/// Ascolta in tempo reale i file caricati per un cliente
Stream<List<CustomerFileModel>> getCustomerFilesStream(String customerId) {
return _supabase
.from('customer_file')
.stream(primaryKey: ['id'])
.eq('customer_id', customerId)
.order('created_at', ascending: false)
.map(
(listOfMaps) =>
listOfMaps.map((map) => CustomerFileModel.fromMap(map)).toList(),
);
}
2026-04-11 12:40:03 +02:00
/// Recupera i file di un cliente specifico
Future<List<CustomerFileModel>> getCustomerFiles(String customerId) async {
try {
final response = await _supabase
2026-04-11 12:40:03 +02:00
.from('customer_file')
.select()
.eq('customer_id', customerId);
return (response as List)
.map((f) => CustomerFileModel.fromMap(f))
2026-04-11 12:40:03 +02:00
.toList();
} catch (e) {
throw 'Errore recupero file: $e';
}
}
/// Carica un file e salva il riferimento nel database
Future<CustomerFileModel> uploadAndRegisterFile({
required String customerId,
required PlatformFile pickedFile,
}) async {
final cleanFileName = pickedFile.name.replaceAll(
RegExp(r'[^a-zA-Z0-9\.\-]'),
'_',
);
final storagePath =
'$companyId/customers/${DateTime.now().millisecondsSinceEpoch}_$cleanFileName';
final int fileSize = pickedFile.size;
final fileToSave = CustomerFileModel(
customerId: customerId,
name: cleanFileName.fileNameWithoutExtension(),
extension: cleanFileName.fileExtension(),
storagePath: storagePath,
fileSize: fileSize,
);
final String mimeType = fileToSave.extension.toLowerCase() == 'pdf'
? 'application/pdf'
: 'image/${fileToSave.extension}';
2026-04-11 12:40:03 +02:00
try {
// Usiamo bytes invece del path per massima compatibilità
if (pickedFile.bytes == null && pickedFile.path == null) {
throw 'Impossibile leggere il contenuto del file';
}
// Se siamo su desktop/mobile abbiamo il path, su web abbiamo i bytes
if (pickedFile.bytes != null) {
await _supabase.storage
2026-04-11 12:40:03 +02:00
.from('documents')
.uploadBinary(
storagePath,
pickedFile.bytes!,
fileOptions: FileOptions(contentType: mimeType, upsert: true),
);
2026-04-11 12:40:03 +02:00
}
final response = await _supabase
2026-04-11 12:40:03 +02:00
.from('customer_file')
.insert(fileToSave.toMap())
2026-04-11 12:40:03 +02:00
.select()
.single();
return CustomerFileModel.fromMap(response);
2026-04-11 12:40:03 +02:00
} catch (e) {
throw 'Errore durante l\'upload: $e';
}
}
Future<void> saveFileReference(CustomerFileModel file) async {
await _supabase.from('customer_file').upsert(file.toMap());
}
2026-04-11 12:40:03 +02:00
/// Aggiorna la lista degli URL nel database
Future<void> updateCustomerDocuments(int id, List<String> urls) async {
await _supabase
.from('customer')
.update({'document_urls': urls})
.eq('id', id);
2026-04-11 12:40:03 +02:00
}
Future<void> deleteDocuments(List<CustomerFileModel> 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();
final List<String> storagePaths = files.map((f) => f.storagePath).toList();
try {
// 2. Cancellazione MASSIVA dal DB (una sola chiamata invece di un ciclo!)
// .in_ dice: "cancella tutti i record il cui ID è contenuto in questa lista"
await _supabase
.from('customer_file')
.delete()
.inFilter('id', idsToDelete);
// 3. Cancellazione MASSIVA dallo Storage
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';
}
2026-04-11 12:40:03 +02:00
}
}