Files
flux/lib/features/services/data/services_repository.dart

261 lines
8.4 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
2026-04-20 11:18:22 +02:00
import 'package:flux/core/blocs/session/session_bloc.dart';
import 'package:flux/features/customers/data/customer_repository.dart';
import 'package:flux/features/customers/models/customer_file_model.dart';
2026-04-20 11:18:22 +02:00
import 'package:flux/features/services/models/service_file_model.dart';
import 'package:get_it/get_it.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import '../models/service_model.dart';
class ServicesRepository {
final _supabase = Supabase.instance.client;
2026-04-20 11:18:22 +02:00
final companyId = GetIt.I.get<SessionBloc>().state.company!.id;
final CustomerRepository _customerRepository = GetIt.I<CustomerRepository>();
// --- RECUPERO SINGOLO SERVIZIO CON JOIN COMPLETO ---
Future<ServiceModel> fetchServiceById(String id) async {
try {
final response = await _supabase
.from('service')
.select('''
*,
customer(nome),
energy_service(*),
fin_service(*),
2026-04-20 11:18:22 +02:00
entertainment_service(*),
service_file(*)
''')
.eq('id', id)
.single();
return ServiceModel.fromMap(response);
} catch (e) {
throw Exception('Errore nel caricamento del servizio: $e');
}
}
// --- RECUPERO PAGINATO CON FILTRI E JOIN ---
Future<List<ServiceModel>> fetchServices({
required String companyId,
required int offset,
int limit = 50,
String? searchTerm,
DateTimeRange? dateRange,
}) async {
try {
// Nota: 'customer(name, surname)' serve per il display name nella card
var query = _supabase
.from('service')
.select('''
*,
customer(nome),
energy_service(*),
fin_service(*),
2026-04-20 11:18:22 +02:00
entertainment_service(*),
service_file(*)
''')
.eq('company_id', companyId);
// Filtro Range Date
if (dateRange != null) {
query = query
.gte('created_at', dateRange.start.toIso8601String())
.lte('created_at', dateRange.end.toIso8601String());
}
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%',
);
}
final response = await query
.order('created_at', ascending: false)
.range(offset, offset + limit - 1);
return (response as List)
.map((map) => ServiceModel.fromMap(map))
.toList();
} catch (e) {
throw Exception('Errore nel caricamento servizi: $e');
}
}
// --- SALVATAGGIO COMPLETO (PRIMA PADRE, POI FIGLI) ---
2026-04-20 11:18:22 +02:00
Future<void> saveFullService(
ServiceModel service,
List<ServiceFileModel> files,
2026-04-20 11:18:22 +02:00
) async {
try {
// 1. Upsert del record principale
final serviceData = await _supabase
.from('service')
.upsert(service.toMap())
.select()
.single();
final String newId = serviceData['id'];
// 2. MODIFICA: Pulizia atomica dei figli
// Se stiamo modificando (id != null), resettiamo le tabelle collegate
if (service.id != null) {
await Future.wait([
_supabase.from('energy_service').delete().eq('service_id', newId),
_supabase.from('fin_service').delete().eq('service_id', newId),
_supabase
.from('entertainment_service')
.delete()
.eq('service_id', newId),
// Aggiungi qui eventuali altre tabelle pivot o file
]);
}
// 3. Inserimento dei moduli in parallelo per velocità
final List<Future> insertTasks = [];
if (service.energyServices.isNotEmpty) {
insertTasks.add(
_supabase
.from('energy_service')
.insert(
service.energyServices
.map((item) => item.copyWith(serviceId: newId).toMap())
.toList(),
),
);
}
if (service.finServices.isNotEmpty) {
insertTasks.add(
_supabase
.from('fin_service')
.insert(
service.finServices
.map((item) => item.copyWith(serviceId: newId).toMap())
.toList(),
),
);
}
if (service.entertainmentServices.isNotEmpty) {
insertTasks.add(
_supabase
.from('entertainment_service')
.insert(
service.entertainmentServices
.map((item) => item.copyWith(serviceId: newId).toMap())
.toList(),
),
);
}
if (insertTasks.isNotEmpty) {
await Future.wait(insertTasks);
}
if (files.isNotEmpty) {
2026-04-20 11:18:22 +02:00
final List<Future> uploadTasks = [];
for (var file in files) {
2026-04-20 11:18:22 +02:00
final storagePath =
'$companyId/services/$newId/${DateTime.now().millisecondsSinceEpoch}_${file.name}.${file.extension}';
final String mimeType = file.extension.toLowerCase() == 'pdf'
? 'application/pdf'
: 'image/${file.extension}';
final fileToSave = file.copyWith(serviceId: newId);
2026-04-20 11:18:22 +02:00
// Creiamo una funzione asincrona per caricare file e scrivere nel DB
Future<void> uploadAndLink() async {
// Determiniamo il MIME type corretto in base all'estensione
2026-04-20 11:18:22 +02:00
// A. Upload nel Bucket Storage (usiamo i bytes così funziona anche su Web!)
await _supabase.storage
.from('documents')
.uploadBinary(
storagePath,
fileToSave.localBytes!,
2026-04-20 11:18:22 +02:00
fileOptions: FileOptions(
contentType:
mimeType, // Diciamo a Supabase esattamente cos'è!
upsert:
true, // Opzionale: sovrascrive se esiste già un file con lo stesso nome
),
);
// B. Otteniamo l'URL pubblico e scriviamo il record del file nel DB
final String publicUrl = _supabase.storage
.from('documents')
.getPublicUrl(storagePath);
await _supabase
.from('service_file')
.insert(fileToSave.copyWith(url: publicUrl).toMap());
}
uploadTasks.add(uploadAndLink());
}
// Eseguiamo tutti gli upload in parallelo per la massima velocità
await Future.wait(uploadTasks);
}
} catch (e) {
// Qui potresti aggiungere una logica di "rollback manuale" se necessario
throw Exception('Errore durante il salvataggio corazzato: $e');
}
}
// --- ELIMINAZIONE ---
Future<void> deleteService(String id) async {
try {
await _supabase.from('service').delete().eq('id', id);
} catch (e) {
throw Exception('Errore durante l\'eliminazione: $e');
}
}
// --- RECUPERO TIPI CONTENUTI PIÙ FREQUENTI PER AUTOCOMPLETE ---
Future<List<String>> fetchTopEntertainmentTypes(String companyId) async {
try {
// Cerchiamo i tipi più frequenti associati ai servizi di questa company
// Nota: dobbiamo passare attraverso la tabella 'service' per filtrare per company_id
final response = await _supabase
.from('entertainment_service')
.select('type, service!inner(store!inner(company_id))')
.eq('service.store.company_id', companyId)
.limit(100); // 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;
}
var sortedKeys = counts.keys.toList()
..sort((a, b) => counts[b]!.compareTo(counts[a]!));
return sortedKeys.take(5).toList();
} catch (e) {
return [
"Netflix",
"DAZN",
"Disney+",
"Sky",
]; // Fallback se non c'è ancora storia
}
}
Future<void> copyFileToCustomer({
required ServiceFileModel file,
required String customerId,
}) async {
CustomerFileModel fileToCopy = CustomerFileModel(
customerId: customerId,
name: file.name,
url: file.url,
extension: file.extension,
fileSize: file.fileSize,
);
await _customerRepository.saveCustomerFile(fileToCopy);
}
}