212 lines
6.1 KiB
Dart
212 lines
6.1 KiB
Dart
import 'package:file_picker/file_picker.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: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> insertCustomer(CustomerModel customer) async {
|
|
try {
|
|
final response = await _supabase
|
|
.from('customer')
|
|
.upsert(customer.toJson())
|
|
.select()
|
|
.single();
|
|
return CustomerModel.fromMap(response);
|
|
} catch (e) {
|
|
throw '$e';
|
|
}
|
|
}
|
|
|
|
Future<CustomerModel> updateCustomer(CustomerModel customer) async {
|
|
try {
|
|
final response = await _supabase
|
|
.from('customer')
|
|
.update(customer.toJson())
|
|
.eq('id', customer.id!)
|
|
.select()
|
|
.single();
|
|
return CustomerModel.fromMap(response);
|
|
} catch (e) {
|
|
throw '$e';
|
|
}
|
|
}
|
|
|
|
// Recupera tutti i clienti dell'azienda
|
|
Future<List<CustomerModel>> getCustomers(String companyId) async {
|
|
try {
|
|
final response = await _supabase
|
|
.from('customer')
|
|
.select('''
|
|
*,
|
|
attachment(*)
|
|
''')
|
|
.eq('company_id', companyId)
|
|
.eq('is_active', true)
|
|
.order('name');
|
|
|
|
return (response as List).map((c) => CustomerModel.fromMap(c)).toList();
|
|
} catch (e) {
|
|
throw '$e';
|
|
}
|
|
}
|
|
|
|
Future<CustomerModel> getCustomerById(String customerId) async {
|
|
try {
|
|
final response = await _supabase
|
|
.from('customer')
|
|
.select('''
|
|
*,
|
|
attachment(*)
|
|
''')
|
|
.eq('id', customerId)
|
|
.single();
|
|
|
|
return CustomerModel.fromMap(response);
|
|
} catch (e) {
|
|
throw '$e';
|
|
}
|
|
}
|
|
|
|
// 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('name.ilike.%$query%,phone_number.ilike.%$query%')
|
|
.limit(10);
|
|
|
|
return (response as List).map((c) => CustomerModel.fromMap(c)).toList();
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Ascolta in tempo reale i file caricati per un cliente
|
|
Stream<List<AttachmentModel>> getCustomerFilesStream(String customerId) {
|
|
return _supabase
|
|
.from('attachment')
|
|
.stream(primaryKey: ['id'])
|
|
.eq('customer_id', customerId)
|
|
.order('created_at', ascending: false)
|
|
.map(
|
|
(listOfMaps) =>
|
|
listOfMaps.map((map) => AttachmentModel.fromMap(map)).toList(),
|
|
);
|
|
}
|
|
|
|
/// Recupera i file di un cliente specifico
|
|
Future<List<AttachmentModel>> getCustomerFiles(String customerId) async {
|
|
try {
|
|
final response = await _supabase
|
|
.from('attachment')
|
|
.select()
|
|
.eq('customer_id', customerId);
|
|
|
|
return (response as List).map((f) => AttachmentModel.fromMap(f)).toList();
|
|
} catch (e) {
|
|
throw '$e';
|
|
}
|
|
}
|
|
|
|
/// Carica un file e salva il riferimento nel database
|
|
Future<AttachmentModel> 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 = AttachmentModel(
|
|
companyId: companyId,
|
|
customerId: customerId,
|
|
name: cleanFileName.fileNameWithoutExtension(),
|
|
extension: cleanFileName.fileExtension(),
|
|
storagePath: storagePath,
|
|
fileSize: fileSize,
|
|
);
|
|
final String mimeType = fileToSave.extension.toLowerCase() == 'pdf'
|
|
? 'application/pdf'
|
|
: 'image/${fileToSave.extension}';
|
|
try {
|
|
// Usiamo bytes invece del path per massima compatibilità
|
|
if (pickedFile.bytes == null && pickedFile.path == null) {
|
|
throw 'File read error';
|
|
}
|
|
|
|
// Se siamo su desktop/mobile abbiamo il path, su web abbiamo i bytes
|
|
if (pickedFile.bytes != null) {
|
|
await _supabase.storage
|
|
.from('documents')
|
|
.uploadBinary(
|
|
storagePath,
|
|
pickedFile.bytes!,
|
|
fileOptions: FileOptions(contentType: mimeType, upsert: true),
|
|
);
|
|
}
|
|
|
|
final response = await _supabase
|
|
.from('attachment')
|
|
.insert(fileToSave.toMap())
|
|
.select()
|
|
.single();
|
|
|
|
return AttachmentModel.fromMap(response);
|
|
} catch (e) {
|
|
throw '$e';
|
|
}
|
|
}
|
|
|
|
Future<void> saveFileReference(AttachmentModel file) async {
|
|
await _supabase.from('attachment').upsert(file.toMap());
|
|
}
|
|
|
|
Future<void> deleteDocuments(List<AttachmentModel> files) async {
|
|
if (files.isEmpty) return;
|
|
// 1. Prepariamo le liste di ID e di Percorsi
|
|
final List<String> idsToDelete = [];
|
|
final List<String> storagePathsToDelete = [];
|
|
final List<String> idsToEdit = [];
|
|
for (var file in files) {
|
|
if (file.operationId == null) {
|
|
idsToDelete.add(file.id!);
|
|
storagePathsToDelete.add(file.storagePath!);
|
|
} else {
|
|
idsToEdit.add(file.id!);
|
|
}
|
|
}
|
|
try {
|
|
if (idsToDelete.isNotEmpty) {
|
|
await _supabase.from('attachment').delete().inFilter('id', idsToDelete);
|
|
// 3. Cancellazione MASSIVA dallo Storage
|
|
await _supabase.storage.from('documents').remove(storagePathsToDelete);
|
|
}
|
|
if (idsToEdit.isNotEmpty) {
|
|
await _supabase
|
|
.from('attachment')
|
|
.update({'customer_id': null})
|
|
.inFilter('id', idsToEdit);
|
|
}
|
|
} on PostgrestException catch (e) {
|
|
throw e.message;
|
|
} catch (e) {
|
|
throw '$e';
|
|
}
|
|
}
|
|
}
|