b
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/core/enums_and_consts/consts.dart';
|
||||
import 'package:flux/features/tasks/models/task_reminder_config.dart';
|
||||
import 'package:flux/features/tasks/models/task_status.dart';
|
||||
@@ -10,46 +9,38 @@ import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
// Sostituisci con i percorsi corretti di FLUX
|
||||
import 'package:flux/features/tasks/models/task_model.dart';
|
||||
|
||||
class TaskRepository {
|
||||
final SupabaseClient _supabase;
|
||||
class TasksRepository {
|
||||
final _supabase = GetIt.I.get<SupabaseClient>();
|
||||
|
||||
TaskRepository({SupabaseClient? supabase})
|
||||
: _supabase = supabase ?? Supabase.instance.client;
|
||||
// =========================================================================
|
||||
// LETTURA REMINDER (Per il form in edit)
|
||||
// =========================================================================
|
||||
Future<List<TaskReminderConfig>> fetchPersonalReminders({
|
||||
required String taskId,
|
||||
required String staffId,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _supabase
|
||||
.from('task_reminders')
|
||||
.select()
|
||||
.eq('task_id', taskId)
|
||||
.eq('staff_id', staffId)
|
||||
.eq(
|
||||
'is_forced',
|
||||
false,
|
||||
); // Peschiamo SOLO quelli modificabili dall'utente
|
||||
|
||||
// --- LOGICA REAL-TIME (Il Campanello) ---
|
||||
Stream<void> watchCompanyTasks(String companyId) {
|
||||
// Usiamo un broadcast nel caso più bloc volessero ascoltarlo in futuro
|
||||
final controller = StreamController<void>.broadcast();
|
||||
|
||||
final channel = _supabase.channel('public:tasks_company_$companyId');
|
||||
|
||||
channel
|
||||
.onPostgresChanges(
|
||||
event: PostgresChangeEvent.all,
|
||||
schema: 'public',
|
||||
table: Tables.tasks,
|
||||
filter: PostgresChangeFilter(
|
||||
type: PostgresChangeFilterType.eq,
|
||||
column: 'company_id',
|
||||
value: companyId,
|
||||
),
|
||||
callback: (payload) {
|
||||
if (!controller.isClosed) {
|
||||
controller.add(
|
||||
null,
|
||||
); // Suoniamo il campanello! Nessun dato, solo il "ding"
|
||||
}
|
||||
},
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// Quando il Cubit smette di ascoltare, puliamo il canale Supabase in automatico
|
||||
controller.onCancel = () {
|
||||
channel.unsubscribe();
|
||||
controller.close();
|
||||
};
|
||||
|
||||
return controller.stream;
|
||||
return (response as List)
|
||||
.map(
|
||||
(r) => TaskReminderConfig(
|
||||
minutesBefore: r['minutes_before'],
|
||||
channel: r['channel'],
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
} catch (e) {
|
||||
throw Exception('Errore fetch personal reminders: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// --- RECUPERO DEI TASK FILTRATI ---
|
||||
@@ -107,385 +98,257 @@ class TaskRepository {
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// REALTIME STREAM (La sentinella per la bacheca)
|
||||
// =========================================================================
|
||||
Stream<List<TaskModel>> watchCompanyTasks(String companyId) {
|
||||
return _supabase
|
||||
.from('tasks')
|
||||
.stream(primaryKey: ['id'])
|
||||
.eq('company_id', companyId)
|
||||
.map((listOfMaps) {
|
||||
return listOfMaps.map((map) => TaskModel.fromMap(map)).toList();
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// CREAZIONE (Insert)
|
||||
// =========================================================================
|
||||
Future<void> createTask({
|
||||
required TaskModel task,
|
||||
required List<String> assignedStaffIds,
|
||||
required String currentUserId,
|
||||
required List<TaskReminderConfig> currentUserCustomReminders,
|
||||
TaskReminderConfig?
|
||||
managerForcedOverride, // Opzionale: l'avviso forzato del manager
|
||||
TaskReminderConfig? managerForcedOverride,
|
||||
}) async {
|
||||
try {
|
||||
// 1. Inserimento del Task principale -> otteniamo il taskId
|
||||
// 2. Inserimento dei record in task_assignments
|
||||
final String taskId = task.id!;
|
||||
|
||||
List<Map<String, dynamic>> remindersToInsert = [];
|
||||
|
||||
// 3. Recuperiamo i default degli ALTRI utenti assegnati
|
||||
final otherStaffIds = assignedStaffIds
|
||||
.where((id) => id != currentUserId)
|
||||
.toList();
|
||||
List<dynamic> otherDefaults = [];
|
||||
if (otherStaffIds.isNotEmpty) {
|
||||
otherDefaults = await _supabase
|
||||
.from('staff_task_reminder_defaults')
|
||||
.select()
|
||||
.inFilter('staff_id', otherStaffIds);
|
||||
}
|
||||
|
||||
// 4. CICLO DI COSTRUZIONE DELLA CODA REMINDER
|
||||
for (var staffId in assignedStaffIds) {
|
||||
// CASO A: È l'utente loggato che sta creando/partecipando al task
|
||||
if (staffId == currentUserId) {
|
||||
for (var config in currentUserCustomReminders) {
|
||||
final triggerAt = task.dueDate?.subtract(
|
||||
Duration(minutes: config.minutesBefore),
|
||||
);
|
||||
if (triggerAt != null && triggerAt.isAfter(DateTime.now())) {
|
||||
remindersToInsert.add({
|
||||
'company_id': task.companyId,
|
||||
'task_id': taskId,
|
||||
'staff_id': currentUserId,
|
||||
'minutes_before': config.minutesBefore,
|
||||
'channel': config.channel,
|
||||
'trigger_at': triggerAt.toIso8601String(),
|
||||
'is_forced': false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// CASO B: Sono gli altri assegnatari -> ereditano i loro default personali dal DB
|
||||
else {
|
||||
final staffRules = otherDefaults.where(
|
||||
(row) => row['staff_id'] == staffId,
|
||||
);
|
||||
for (var rule in staffRules) {
|
||||
final minutesBefore = rule['minutes_before'] as int;
|
||||
final triggerAt = task.dueDate?.subtract(
|
||||
Duration(minutes: minutesBefore),
|
||||
);
|
||||
if (triggerAt != null && triggerAt.isAfter(DateTime.now())) {
|
||||
remindersToInsert.add({
|
||||
'company_id': task.companyId,
|
||||
'task_id': taskId,
|
||||
'staff_id': staffId,
|
||||
'minutes_before': minutesBefore,
|
||||
'channel': rule['channel'],
|
||||
'trigger_at': triggerAt.toIso8601String(),
|
||||
'is_forced': false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CASO C: Il creatore ha impostato un avviso forzato (Override molto importante)
|
||||
if (managerForcedOverride != null && task.dueDate != null) {
|
||||
final triggerAt = task.dueDate!.subtract(
|
||||
Duration(minutes: managerForcedOverride.minutesBefore),
|
||||
);
|
||||
if (triggerAt.isAfter(DateTime.now())) {
|
||||
remindersToInsert.add({
|
||||
'company_id': task.companyId,
|
||||
'task_id': taskId,
|
||||
'staff_id': staffId, // Lo beccheranno tutti
|
||||
'minutes_before': managerForcedOverride.minutesBefore,
|
||||
'channel': managerForcedOverride.channel,
|
||||
'trigger_at': triggerAt.toIso8601String(),
|
||||
'is_forced':
|
||||
true, // Chiude la possibilità di cancellarlo lato utente
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Sparata unica di Bulk Insert su task_reminders
|
||||
if (remindersToInsert.isNotEmpty) {
|
||||
await _supabase.from('task_reminders').insert(remindersToInsert);
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Errore creazione task: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// --- 3. AGGIORNAMENTO DEL TASK ---
|
||||
Future<TaskModel> updateTask(TaskModel task) async {
|
||||
if (task.id == null) {
|
||||
throw Exception('ID Task mancante. Impossibile aggiornare.');
|
||||
}
|
||||
|
||||
try {
|
||||
final taskData = task.toMap();
|
||||
taskData.remove(
|
||||
'assigned_to_ids',
|
||||
); // Sempre via l'array dal body principale
|
||||
|
||||
// 1. Aggiorniamo i dati base del task (titolo, stato, scadenza, ecc.)
|
||||
await _supabase.from(Tables.tasks).update(taskData).eq('id', task.id!);
|
||||
|
||||
// 2. Sincronizziamo in modo distruttivo gli assegnatari nella tabella di giunzione
|
||||
await _syncAssignments(task.id!, task.assignedToIds);
|
||||
|
||||
// 3. Ritorniamo il modello fresco di DB
|
||||
return await getTaskById(task.id!);
|
||||
} catch (e) {
|
||||
throw Exception('Errore nell\'aggiornamento del task: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> createTask({
|
||||
required TaskModel task,
|
||||
required List<String> assignedStaffIds,
|
||||
required String currentUserId,
|
||||
required List<TaskReminderConfig> currentUserCustomReminders,
|
||||
TaskReminderConfig?
|
||||
managerForcedOverride, // Opzionale: l'avviso forzato del manager
|
||||
}) async {
|
||||
try {
|
||||
// 1. Inserimento del Task principale -> otteniamo il taskId
|
||||
// 2. Inserimento dei record in task_assignments
|
||||
final String taskId = task.id;
|
||||
|
||||
List<Map<String, dynamic>> remindersToInsert = [];
|
||||
|
||||
// 3. Recuperiamo i default degli ALTRI utenti assegnati
|
||||
final otherStaffIds = assignedStaffIds
|
||||
.where((id) => id != currentUserId)
|
||||
.toList();
|
||||
List<dynamic> otherDefaults = [];
|
||||
if (otherStaffIds.isNotEmpty) {
|
||||
otherDefaults = await _supabase
|
||||
.from('staff_task_reminder_defaults')
|
||||
.select()
|
||||
.inFilter('staff_id', otherStaffIds);
|
||||
}
|
||||
|
||||
// 4. CICLO DI COSTRUZIONE DELLA CODA REMINDER
|
||||
for (var staffId in assignedStaffIds) {
|
||||
// CASO A: È l'utente loggato che sta creando/partecipando al task
|
||||
if (staffId == currentUserId) {
|
||||
for (var config in currentUserCustomReminders) {
|
||||
final triggerAt = task.dueDate?.subtract(
|
||||
Duration(minutes: config.minutesBefore),
|
||||
);
|
||||
if (triggerAt != null && triggerAt.isAfter(DateTime.now())) {
|
||||
remindersToInsert.add({
|
||||
'company_id': task.companyId,
|
||||
'task_id': taskId,
|
||||
'staff_id': currentUserId,
|
||||
'minutes_before': config.minutesBefore,
|
||||
'channel': config.channel,
|
||||
'trigger_at': triggerAt.toIso8601String(),
|
||||
'is_forced': false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// CASO B: Sono gli altri assegnatari -> ereditano i loro default personali dal DB
|
||||
else {
|
||||
final staffRules = otherDefaults.where(
|
||||
(row) => row['staff_id'] == staffId,
|
||||
);
|
||||
for (var rule in staffRules) {
|
||||
final minutesBefore = rule['minutes_before'] as int;
|
||||
final triggerAt = task.dueDate?.subtract(
|
||||
Duration(minutes: minutesBefore),
|
||||
);
|
||||
if (triggerAt != null && triggerAt.isAfter(DateTime.now())) {
|
||||
remindersToInsert.add({
|
||||
'company_id': task.companyId,
|
||||
'task_id': taskId,
|
||||
'staff_id': staffId,
|
||||
'minutes_before': minutesBefore,
|
||||
'channel': rule['channel'],
|
||||
'trigger_at': triggerAt.toIso8601String(),
|
||||
'is_forced': false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CASO C: Il creatore ha impostato un avviso forzato (Override molto importante)
|
||||
if (managerForcedOverride != null && task.dueDate != null) {
|
||||
final triggerAt = task.dueDate!.subtract(
|
||||
Duration(minutes: managerForcedOverride.minutesBefore),
|
||||
);
|
||||
if (triggerAt.isAfter(DateTime.now())) {
|
||||
remindersToInsert.add({
|
||||
'company_id': task.companyId,
|
||||
'task_id': taskId,
|
||||
'staff_id': staffId, // Lo beccheranno tutti
|
||||
'minutes_before': managerForcedOverride.minutesBefore,
|
||||
'channel': managerForcedOverride.channel,
|
||||
'trigger_at': triggerAt.toIso8601String(),
|
||||
'is_forced':
|
||||
true, // Chiude la possibilità di cancellarlo lato utente
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Sparata unica di Bulk Insert su task_reminders
|
||||
if (remindersToInsert.isNotEmpty) {
|
||||
await _supabase.from('task_reminders').insert(remindersToInsert);
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Errore creazione task: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4. ELIMINAZIONE DEL TASK ---
|
||||
Future<void> deleteTask(String taskId) async {
|
||||
try {
|
||||
// Eliminando il task, se la Foreign Key su Supabase ha "ON DELETE CASCADE",
|
||||
// le righe nella tabella di giunzione si distruggeranno da sole in automatico!
|
||||
await _supabase.from(Tables.tasks).delete().eq('id', taskId);
|
||||
} catch (e) {
|
||||
throw Exception('Errore nell\'eliminazione del task: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// --- HELPER: RECUPERO SINGOLO TASK ---
|
||||
Future<TaskModel> getTaskById(String taskId) async {
|
||||
try {
|
||||
final response = await _supabase
|
||||
.from(Tables.tasks)
|
||||
.select('''
|
||||
*,
|
||||
task_assignments:${Tables.taskAssignments} (
|
||||
${Tables.staffMembers} (*)
|
||||
)
|
||||
''')
|
||||
.eq('id', taskId)
|
||||
// 1. Inseriamo il Task principale per farci generare l'ID dal DB
|
||||
final taskResponse = await _supabase
|
||||
.from('tasks')
|
||||
.insert(task.toMap()) // Assicurati che toMap() escluda l'id se è null
|
||||
.select('id')
|
||||
.single();
|
||||
return TaskModel.fromMap(response);
|
||||
} catch (e) {
|
||||
throw Exception('Errore nel recupero del singolo task: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// --- HELPER: SINCRONIZZAZIONE DELLA TABELLA DI GIUNZIONE ---
|
||||
Future<void> _syncAssignments(String taskId, List<String> staffIds) async {
|
||||
// TECNICA "WIPE & REPLACE":
|
||||
// Invece di capire chi è stato aggiunto e chi tolto, eliminiamo tutti i record
|
||||
// di questo task e li reinseriamo. È fulmineo ed esclude a priori bug di disallineamento.
|
||||
final String taskId = taskResponse['id'];
|
||||
|
||||
// 1. Facciamo piazza pulita
|
||||
await _supabase.from(Tables.taskAssignments).delete().eq('task_id', taskId);
|
||||
|
||||
// 2. Inseriamo le nuove assegnazioni (se ce ne sono)
|
||||
if (staffIds.isNotEmpty) {
|
||||
final List<Map<String, dynamic>> assignments = staffIds
|
||||
.map(
|
||||
(staffId) => {
|
||||
'task_id': taskId,
|
||||
'staff_id': staffId,
|
||||
'company_id': GetIt.I.get<SessionCubit>().state.company!.id!,
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
|
||||
await _supabase.from(Tables.taskAssignments).insert(assignments);
|
||||
}
|
||||
}
|
||||
|
||||
// --- IL MOTORE DELLA MAGIA ---
|
||||
|
||||
Future<void> generateTaskReminders({
|
||||
required String taskId,
|
||||
required String companyId,
|
||||
required List<String> assignedStaffIds,
|
||||
required DateTime? taskDueDate,
|
||||
}) async {
|
||||
if (assignedStaffIds.isEmpty) return;
|
||||
|
||||
try {
|
||||
// 1. Recuperiamo i default di TUTTI i collaboratori coinvolti in un colpo solo
|
||||
final response = await _supabase
|
||||
.from('staff_task_reminder_defaults')
|
||||
.select()
|
||||
.eq('company_id', companyId)
|
||||
.inFilter('staff_id', assignedStaffIds);
|
||||
|
||||
final List<Map<String, dynamic>> remindersToInsert = [];
|
||||
|
||||
for (var staffId in assignedStaffIds) {
|
||||
// Cerchiamo le preferenze di questo specifico membro dello staff
|
||||
final staffDefaults = response.where(
|
||||
(row) => row['staff_id'] == staffId,
|
||||
);
|
||||
|
||||
if (staffDefaults.isEmpty) {
|
||||
// STRATEGIA FALLBACK: Se l'utente non ha mai configurato i suoi default,
|
||||
// creiamo un reminder standard (es. una push 15 min prima) per non lasciarlo scoperto.
|
||||
if (taskDueDate != null) {
|
||||
remindersToInsert.add({
|
||||
'company_id': companyId,
|
||||
'task_id': taskId,
|
||||
'staff_id': staffId,
|
||||
'minutes_before': 15,
|
||||
'channel': 'push',
|
||||
'trigger_at': taskDueDate
|
||||
.subtract(const Duration(minutes: 15))
|
||||
.toIso8601String(),
|
||||
});
|
||||
}
|
||||
|
||||
// E spariamo la push di creazione immediata come comportamento standard
|
||||
_triggerImmediateNotification(staffId, taskId, 'push_creation');
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2. GESTIONE NOTIFICHE ISTANTANEE (EVENT-DRIVEN)
|
||||
// Prendiamo la prima riga delle impostazioni dell'utente (tanto le colonne nuove sono speculari)
|
||||
final userSetting = staffDefaults.first;
|
||||
if (userSetting['notify_on_creation_push'] == true) {
|
||||
_triggerImmediateNotification(staffId, taskId, 'push_creation');
|
||||
}
|
||||
if (userSetting['notify_on_creation_email'] == true) {
|
||||
_triggerImmediateNotification(staffId, taskId, 'email_creation');
|
||||
}
|
||||
|
||||
// 3. GESTIONE REMINDER TEMPORIZZATI (TIME-DRIVEN)
|
||||
if (taskDueDate != null) {
|
||||
for (var rule in staffDefaults) {
|
||||
final minutesBefore = rule['minutes_before'] as int;
|
||||
final triggerAt = taskDueDate.subtract(
|
||||
Duration(minutes: minutesBefore),
|
||||
);
|
||||
|
||||
// Se il task scade tra 5 minuti e il reminder è impostato a 1 ora prima,
|
||||
// il trigger_at sarebbe nel passato. Lo inseriamo solo se è nel futuro!
|
||||
if (triggerAt.isAfter(DateTime.now())) {
|
||||
remindersToInsert.add({
|
||||
'company_id': companyId,
|
||||
// 2. Inseriamo le Assegnazioni (tabella task_assignments)
|
||||
if (assignedStaffIds.isNotEmpty) {
|
||||
final assignmentsToInsert = assignedStaffIds
|
||||
.map(
|
||||
(staffId) => {
|
||||
'task_id': taskId,
|
||||
'staff_id': staffId,
|
||||
'minutes_before': minutesBefore,
|
||||
'channel': rule['channel'],
|
||||
'trigger_at': triggerAt.toIso8601String(),
|
||||
});
|
||||
'company_id': task.companyId,
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
await _supabase.from('task_assignments').insert(assignmentsToInsert);
|
||||
}
|
||||
|
||||
// Se non c'è data di scadenza, niente promemoria a tempo
|
||||
if (task.dueDate == null || assignedStaffIds.isEmpty) return;
|
||||
|
||||
// 3. Setup Reminder: Peschiamo i default degli ALTRI dipendenti coinvolti
|
||||
final otherStaffIds = assignedStaffIds
|
||||
.where((id) => id != currentUserId)
|
||||
.toList();
|
||||
List<dynamic> otherDefaults = [];
|
||||
if (otherStaffIds.isNotEmpty) {
|
||||
otherDefaults = await _supabase
|
||||
.from('staff_task_reminder_defaults')
|
||||
.select()
|
||||
.inFilter('staff_id', otherStaffIds);
|
||||
}
|
||||
|
||||
// 4. Creiamo la lista Bulk Insert per la tabella task_reminders
|
||||
List<Map<String, dynamic>> remindersToInsert = [];
|
||||
|
||||
for (var staffId in assignedStaffIds) {
|
||||
// A) Se è l'utente loggato -> usa i reminder configurati nel form
|
||||
if (staffId == currentUserId) {
|
||||
for (var config in currentUserCustomReminders) {
|
||||
final triggerAt = task.dueDate!.subtract(
|
||||
Duration(minutes: config.minutesBefore),
|
||||
);
|
||||
if (triggerAt.isAfter(DateTime.now())) {
|
||||
remindersToInsert.add(
|
||||
_buildReminderRow(
|
||||
task,
|
||||
taskId,
|
||||
staffId,
|
||||
config,
|
||||
triggerAt,
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// B) Se è un collega -> eredita i suoi default preimpostati
|
||||
else {
|
||||
final staffRules = otherDefaults.where(
|
||||
(row) => row['staff_id'] == staffId,
|
||||
);
|
||||
for (var rule in staffRules) {
|
||||
final config = TaskReminderConfig(
|
||||
minutesBefore: rule['minutes_before'],
|
||||
channel: rule['channel'],
|
||||
);
|
||||
final triggerAt = task.dueDate!.subtract(
|
||||
Duration(minutes: config.minutesBefore),
|
||||
);
|
||||
if (triggerAt.isAfter(DateTime.now())) {
|
||||
remindersToInsert.add(
|
||||
_buildReminderRow(
|
||||
task,
|
||||
taskId,
|
||||
staffId,
|
||||
config,
|
||||
triggerAt,
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// C) Override forzato del manager (per tutti)
|
||||
if (managerForcedOverride != null) {
|
||||
final triggerAt = task.dueDate!.subtract(
|
||||
Duration(minutes: managerForcedOverride.minutesBefore),
|
||||
);
|
||||
if (triggerAt.isAfter(DateTime.now())) {
|
||||
remindersToInsert.add(
|
||||
_buildReminderRow(
|
||||
task,
|
||||
taskId,
|
||||
staffId,
|
||||
managerForcedOverride,
|
||||
triggerAt,
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Bulk Insert dei reminder temporizzati nella coda operativa
|
||||
// 5. Inserimento massivo finale
|
||||
if (remindersToInsert.isNotEmpty) {
|
||||
await _supabase.from('task_reminders').insert(remindersToInsert);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Errore nella generazione dei reminder: $e');
|
||||
throw Exception('Errore durante la creazione del task: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _triggerImmediateNotification(
|
||||
String staffId,
|
||||
// =========================================================================
|
||||
// AGGIORNAMENTO (Update)
|
||||
// =========================================================================
|
||||
Future<void> updateTask({
|
||||
required TaskModel task,
|
||||
required List<String> assignedStaffIds,
|
||||
required String currentUserId,
|
||||
required List<TaskReminderConfig> currentUserCustomReminders,
|
||||
}) async {
|
||||
try {
|
||||
final taskId = task.id!;
|
||||
|
||||
// 1. Aggiornamento dati Task Base
|
||||
await _supabase
|
||||
.from('tasks')
|
||||
.update({
|
||||
'title': task.title,
|
||||
'description': task.description,
|
||||
'due_date': task.dueDate?.toIso8601String(),
|
||||
'store_id': task.storeId,
|
||||
'updated_at': DateTime.now().toIso8601String(),
|
||||
})
|
||||
.eq('id', taskId);
|
||||
|
||||
// 2. Aggiornamento Assegnazioni: eliminiamo le vecchie, inseriamo le nuove
|
||||
await _supabase.from('task_assignments').delete().eq('task_id', taskId);
|
||||
if (assignedStaffIds.isNotEmpty) {
|
||||
final assignmentsToInsert = assignedStaffIds
|
||||
.map(
|
||||
(staffId) => {
|
||||
'task_id': taskId,
|
||||
'staff_id': staffId,
|
||||
'company_id': task.companyId,
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
await _supabase.from('task_assignments').insert(assignmentsToInsert);
|
||||
}
|
||||
|
||||
// Se non c'è una data, eliminiamo tutti i vecchi promemoria dell'utente loggato per pulizia
|
||||
if (task.dueDate == null) {
|
||||
await _supabase
|
||||
.from('task_reminders')
|
||||
.delete()
|
||||
.eq('task_id', taskId)
|
||||
.eq('staff_id', currentUserId)
|
||||
.eq('is_forced', false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. GESTIONE REMINDER: Puliamo SOLO quelli modificabili dall'utente loggato
|
||||
await _supabase
|
||||
.from('task_reminders')
|
||||
.delete()
|
||||
.eq('task_id', taskId)
|
||||
.eq('staff_id', currentUserId)
|
||||
.eq('is_forced', false); // NON tocchiamo quelli forzati dal manager!
|
||||
|
||||
// 4. Inseriamo le nuove configurazioni salvate dal Cubit (solo se è ancora tra gli assegnatari)
|
||||
if (assignedStaffIds.contains(currentUserId) &&
|
||||
currentUserCustomReminders.isNotEmpty) {
|
||||
final List<Map<String, dynamic>> toInsert = [];
|
||||
|
||||
for (var config in currentUserCustomReminders) {
|
||||
final triggerAt = task.dueDate!.subtract(
|
||||
Duration(minutes: config.minutesBefore),
|
||||
);
|
||||
if (triggerAt.isAfter(DateTime.now())) {
|
||||
toInsert.add(
|
||||
_buildReminderRow(
|
||||
task,
|
||||
taskId,
|
||||
currentUserId,
|
||||
config,
|
||||
triggerAt,
|
||||
false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (toInsert.isNotEmpty) {
|
||||
await _supabase.from('task_reminders').insert(toInsert);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
throw Exception('Errore durante l\'aggiornamento del task: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// --- HELPER PRIVATO PER LA MAPPA DEL REMINDER ---
|
||||
Map<String, dynamic> _buildReminderRow(
|
||||
TaskModel task,
|
||||
String taskId,
|
||||
String type,
|
||||
String staffId,
|
||||
TaskReminderConfig config,
|
||||
DateTime triggerAt,
|
||||
bool isForced,
|
||||
) {
|
||||
// Questa funzione chiamerà direttamente l'Edge Function di Supabase
|
||||
// per far squillare il telefono o mandare la mail ADESSO.
|
||||
// La implementeremo appena il backend sarà pronto!
|
||||
return {
|
||||
'company_id': task.companyId,
|
||||
'task_id': taskId,
|
||||
'staff_id': staffId,
|
||||
'minutes_before': config.minutesBefore,
|
||||
'channel': config.channel,
|
||||
'trigger_at': triggerAt.toIso8601String(),
|
||||
'is_forced': isForced,
|
||||
'is_sent': false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user