feat-tickets (#14)
Some checks failed
Deploy to Cloudflare Pages / build-and-deploy (push) Has been cancelled
Some checks failed
Deploy to Cloudflare Pages / build-and-deploy (push) Has been cancelled
Reviewed-on: #14 Co-authored-by: mark-cachy <marco@catelli.it> Co-committed-by: mark-cachy <marco@catelli.it>
This commit is contained in:
238
lib/features/tickets/data/ticket_repository.dart
Normal file
238
lib/features/tickets/data/ticket_repository.dart
Normal file
@@ -0,0 +1,238 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/features/tickets/models/ticket_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
class TicketRepository {
|
||||
final SupabaseClient _supabase = GetIt.I.get<SupabaseClient>();
|
||||
|
||||
TicketRepository();
|
||||
|
||||
static const String _tableName = 'ticket';
|
||||
|
||||
// --- RECUPERO PAGINATO CON FILTRI E JOIN DEI TICKET DI UNO STORE ---
|
||||
Future<List<TicketModel>> fetchStoreTickets({
|
||||
required int offset,
|
||||
int limit = 50,
|
||||
String? searchTerm,
|
||||
DateTimeRange? dateRange,
|
||||
TicketStatus? statusFilter,
|
||||
TicketType? ticketTypeFilter,
|
||||
String? staffIdFilter,
|
||||
}) async {
|
||||
try {
|
||||
var query = _supabase
|
||||
.from(_tableName)
|
||||
.select('''
|
||||
*,
|
||||
customer (*),
|
||||
created_by:staff_member!ticket_staff_id_fkey (*),
|
||||
assigned_to:staff_member!ticket_assigned_to_id_fkey (*),
|
||||
target_model:model!ticket_model_id_1_fkey (*),
|
||||
source_model:model!ticket_model_id_2_fkey (*)
|
||||
''')
|
||||
.eq('store_id', GetIt.I.get<SessionCubit>().state.currentStore!.id!);
|
||||
|
||||
// Filtro Range Date
|
||||
if (dateRange != null) {
|
||||
query = query
|
||||
.gte('created_at', dateRange.start.toIso8601String())
|
||||
.lte('created_at', dateRange.end.toIso8601String());
|
||||
}
|
||||
|
||||
if (statusFilter != null) {
|
||||
query = query.eq('status', statusFilter.value);
|
||||
}
|
||||
|
||||
if (ticketTypeFilter != null) {
|
||||
query = query.eq('ticket_type', ticketTypeFilter.value);
|
||||
}
|
||||
|
||||
if (staffIdFilter != null) {
|
||||
query = query.eq('staff_id', staffIdFilter);
|
||||
}
|
||||
|
||||
if (searchTerm != null && searchTerm.isNotEmpty) {
|
||||
// Filtra sui campi della tabella principale O su quelli della tabella joinata
|
||||
query = query.or('customer.name.ilike.%$searchTerm%');
|
||||
}
|
||||
|
||||
final response = await query
|
||||
.order('created_at', ascending: false)
|
||||
.range(offset, offset + limit - 1);
|
||||
|
||||
return (response as List).map((map) => TicketModel.fromMap(map)).toList();
|
||||
} catch (e) {
|
||||
throw Exception('$e');
|
||||
}
|
||||
}
|
||||
|
||||
// --- RECUPERO PAGINATO CON FILTRI E JOIN DEI TICKET DI TUTTA L'AZIENDA ---
|
||||
Future<List<TicketModel>> fetchCompanyTickets({
|
||||
required int offset,
|
||||
int limit = 50,
|
||||
String? searchTerm,
|
||||
DateTimeRange? dateRange,
|
||||
TicketStatus? ticketStatusFilter,
|
||||
TicketType? ticketTypeFilter,
|
||||
String? staffIdFilter,
|
||||
}) async {
|
||||
try {
|
||||
var query = _supabase
|
||||
.from(_tableName)
|
||||
.select('''
|
||||
*,
|
||||
customer (*),
|
||||
created_by:staff_member!ticket_staff_id_fkey (*),
|
||||
assigned_to:staff_member!ticket_assigned_to_id_fkey (*),
|
||||
target_model:model!ticket_model_id_1_fkey (*),
|
||||
source_model:model!ticket_model_id_2_fkey (*)
|
||||
''')
|
||||
.eq('company_id', GetIt.I.get<SessionCubit>().state.company!.id!);
|
||||
|
||||
// Filtro Range Date
|
||||
if (dateRange != null) {
|
||||
query = query
|
||||
.gte('created_at', dateRange.start.toIso8601String())
|
||||
.lte('created_at', dateRange.end.toIso8601String());
|
||||
}
|
||||
|
||||
if (ticketStatusFilter != null) {
|
||||
query = query.eq('status', ticketStatusFilter.value);
|
||||
}
|
||||
|
||||
if (ticketTypeFilter != null) {
|
||||
query = query.eq('ticket_type', ticketTypeFilter.value);
|
||||
}
|
||||
|
||||
if (staffIdFilter != null) {
|
||||
query = query.eq('staff_id', staffIdFilter);
|
||||
}
|
||||
|
||||
if (searchTerm != null && searchTerm.isNotEmpty) {
|
||||
// Filtra sui campi della tabella principale O su quelli della tabella joinata
|
||||
query = query.or('customer.name.ilike.%$searchTerm%');
|
||||
}
|
||||
|
||||
final response = await query
|
||||
.order('created_at', ascending: false)
|
||||
.range(offset, offset + limit - 1);
|
||||
|
||||
return (response as List).map((map) => TicketModel.fromMap(map)).toList();
|
||||
} catch (e) {
|
||||
throw Exception('$e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Stream dei ticket che necessitano attenzione (es. in scadenza oggi o in ritardo)
|
||||
Stream<List<TicketModel>> getAttentionNeededTicketsStream() {
|
||||
return _supabase
|
||||
.from(_tableName)
|
||||
.stream(primaryKey: ['id'])
|
||||
.eq('store_id', GetIt.I.get<SessionCubit>().state.currentStore!.id!)
|
||||
// Purtroppo lo stream accetta solo filtri base, quindi ci facciamo
|
||||
// mandare i dati e li filtriamo con la potenza di Dart!
|
||||
.limit(300)
|
||||
.map((listOfMaps) {
|
||||
final now = DateTime.now();
|
||||
final endOfToday = DateTime(now.year, now.month, now.day, 23, 59, 59);
|
||||
|
||||
// 1. Mappiamo tutto in TicketModel
|
||||
final allStoreTickets = listOfMaps
|
||||
.map((map) => TicketModel.fromMap(map))
|
||||
.toList();
|
||||
|
||||
// 2. Filtriamo in memoria!
|
||||
final urgentTickets = allStoreTickets.where((ticket) {
|
||||
// Escludiamo quelli già chiusi o consegnati
|
||||
if (ticket.ticketStatus == TicketStatus.closed ||
|
||||
ticket.ticketStatus == TicketStatus.ready) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Se c'è una data di consegna stimata ed è <= a stasera, è urgente!
|
||||
if (ticket.estimatedDeliveryAt != null) {
|
||||
return ticket.estimatedDeliveryAt!.isBefore(endOfToday);
|
||||
}
|
||||
|
||||
return false;
|
||||
}).toList();
|
||||
|
||||
// 3. Li ordiniamo mettendo i più vecchi/urgenti in cima
|
||||
urgentTickets.sort(
|
||||
(a, b) => a.estimatedDeliveryAt!.compareTo(b.estimatedDeliveryAt!),
|
||||
);
|
||||
|
||||
return urgentTickets;
|
||||
});
|
||||
}
|
||||
|
||||
/// Recupera un ticket specifico CON TUTTE LE RELAZIONI espanse (Cliente e Modelli)
|
||||
/// Questa è la vera magia di Supabase!
|
||||
Future<TicketModel> getTicketById(String ticketId) async {
|
||||
try {
|
||||
// Usiamo i nomi esatti delle Foreign Key che hai definito nell'SQL!
|
||||
final response = await _supabase
|
||||
.from(_tableName)
|
||||
.select('''
|
||||
*,
|
||||
customer (*),
|
||||
target_model:model!ticket_model_id_1_fkey (*),
|
||||
source_model:model!ticket_model_id_2_fkey (*),
|
||||
created_by:staff_member!ticket_staff_id_fkey (*),
|
||||
assigned_to:staff_member!ticket_assigned_to_id_fkey (*),
|
||||
''')
|
||||
.eq('id', ticketId)
|
||||
.single();
|
||||
|
||||
return TicketModel.fromMap(response);
|
||||
} catch (e) {
|
||||
throw Exception('Errore nel recupero del dettaglio ticket: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Salva il ticket con upsert
|
||||
Future<TicketModel> saveTicket(TicketModel ticket) async {
|
||||
try {
|
||||
final response = await _supabase
|
||||
.from(_tableName)
|
||||
.upsert(ticket.toMap())
|
||||
.select()
|
||||
.single();
|
||||
|
||||
return TicketModel.fromMap(response);
|
||||
} catch (e) {
|
||||
throw Exception('Errore nella creazione del ticket: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Aggiorna un ticket esistente
|
||||
Future<TicketModel> updateTicket(TicketModel ticket) async {
|
||||
if (ticket.id == null) {
|
||||
throw Exception('Impossibile aggiornare un ticket senza ID');
|
||||
}
|
||||
|
||||
try {
|
||||
final response = await _supabase
|
||||
.from(_tableName)
|
||||
.update(ticket.toMap())
|
||||
.eq('id', ticket.id!)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
return TicketModel.fromMap(response);
|
||||
} catch (e) {
|
||||
throw Exception('Errore nell\'aggiornamento del ticket: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Elimina (o annulla) un ticket
|
||||
Future<void> deleteTicket(String ticketId) async {
|
||||
try {
|
||||
await _supabase.from(_tableName).delete().eq('id', ticketId);
|
||||
} catch (e) {
|
||||
throw Exception('Errore nell\'eliminazione del ticket: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user