2026-05-26 12:28:12 +02:00
|
|
|
import 'package:equatable/equatable.dart';
|
|
|
|
|
import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
|
|
|
|
|
import 'package:flux/features/tasks/models/task_status.dart';
|
|
|
|
|
|
|
|
|
|
class TaskModel extends Equatable {
|
|
|
|
|
final String? id;
|
|
|
|
|
final String? companyId;
|
|
|
|
|
final String title;
|
|
|
|
|
final String? description;
|
|
|
|
|
final List<String> assignedToIds;
|
|
|
|
|
final List<StaffMemberModel> assignedToStaff; // I dati completi dal JOIN
|
|
|
|
|
final String? createdById;
|
|
|
|
|
final DateTime? dueDate;
|
|
|
|
|
final TaskStatus status;
|
|
|
|
|
final DateTime? createdAt;
|
|
|
|
|
final String? storeId;
|
|
|
|
|
|
|
|
|
|
const TaskModel({
|
|
|
|
|
this.id,
|
|
|
|
|
this.companyId,
|
|
|
|
|
required this.title,
|
|
|
|
|
this.description,
|
|
|
|
|
this.assignedToIds = const [],
|
|
|
|
|
this.assignedToStaff = const [],
|
|
|
|
|
this.createdById,
|
|
|
|
|
this.dueDate,
|
|
|
|
|
this.status = TaskStatus.open,
|
|
|
|
|
this.createdAt,
|
|
|
|
|
this.storeId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// --- FACTORY: MODELLO VUOTO (Per le creazioni) ---
|
|
|
|
|
factory TaskModel.empty({String? companyId, String? createdById}) {
|
|
|
|
|
return TaskModel(
|
|
|
|
|
companyId: companyId,
|
|
|
|
|
title: '',
|
|
|
|
|
description: '',
|
|
|
|
|
assignedToIds: const [],
|
|
|
|
|
assignedToStaff: const [],
|
|
|
|
|
createdById: createdById,
|
|
|
|
|
status: TaskStatus.open,
|
|
|
|
|
createdAt: DateTime.now(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- EQUATABLE: PROPRIETÀ DA COMPARARE ---
|
|
|
|
|
@override
|
|
|
|
|
List<Object?> get props => [
|
|
|
|
|
id,
|
|
|
|
|
companyId,
|
|
|
|
|
title,
|
|
|
|
|
description,
|
|
|
|
|
assignedToIds,
|
|
|
|
|
assignedToStaff,
|
|
|
|
|
createdById,
|
|
|
|
|
dueDate,
|
|
|
|
|
status,
|
|
|
|
|
createdAt,
|
|
|
|
|
storeId,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// --- COPY WITH ---
|
|
|
|
|
TaskModel copyWith({
|
|
|
|
|
String? id,
|
|
|
|
|
String? companyId,
|
|
|
|
|
String? title,
|
|
|
|
|
String? description,
|
|
|
|
|
List<String>? assignedToIds,
|
|
|
|
|
List<StaffMemberModel>? assignedToStaff,
|
|
|
|
|
String? createdById,
|
|
|
|
|
DateTime? dueDate,
|
|
|
|
|
bool clearDueDate = false, // Flag ninja per resettare la scadenza
|
|
|
|
|
TaskStatus? status,
|
|
|
|
|
DateTime? createdAt,
|
|
|
|
|
String? storeId,
|
|
|
|
|
bool clearStoreId = false,
|
|
|
|
|
}) {
|
|
|
|
|
return TaskModel(
|
|
|
|
|
id: id ?? this.id,
|
|
|
|
|
companyId: companyId ?? this.companyId,
|
|
|
|
|
title: title ?? this.title,
|
|
|
|
|
description: description ?? this.description,
|
|
|
|
|
assignedToIds: assignedToIds ?? this.assignedToIds,
|
|
|
|
|
assignedToStaff: assignedToStaff ?? this.assignedToStaff,
|
|
|
|
|
createdById: createdById ?? this.createdById,
|
|
|
|
|
dueDate: clearDueDate ? null : (dueDate ?? this.dueDate),
|
|
|
|
|
status: status ?? this.status,
|
|
|
|
|
createdAt: createdAt ?? this.createdAt,
|
|
|
|
|
storeId: clearStoreId ? null : (storeId ?? this.storeId),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- SERIALIZZAZIONE DA SUPABASE ---
|
2026-05-26 19:31:25 +02:00
|
|
|
factory TaskModel.fromMap(Map<String, dynamic> map) {
|
2026-05-26 12:28:12 +02:00
|
|
|
// 1. Gestiamo l'array nullo di Supabase trasformandolo in lista vuota
|
2026-05-26 19:31:25 +02:00
|
|
|
final List<String> parsedAssignedToIds = map['assigned_to_ids'] != null
|
|
|
|
|
? List<String>.from(map['assigned_to_ids'])
|
2026-05-26 12:28:12 +02:00
|
|
|
: [];
|
|
|
|
|
|
|
|
|
|
// 2. Mappiamo il JOIN dello staff, se presente
|
2026-05-26 19:31:25 +02:00
|
|
|
List<StaffMemberModel> staffList = [];
|
|
|
|
|
|
|
|
|
|
// Gestione del JSON proveniente dal Join nidificato (es. task_assignments -> staff_members)
|
|
|
|
|
if (map['task_assignments'] != null) {
|
|
|
|
|
staffList = (map['task_assignments'] as List)
|
|
|
|
|
.map((a) => a['staff_members'])
|
|
|
|
|
.where((s) => s != null)
|
|
|
|
|
.map((s) => StaffMemberModel.fromMap(s))
|
|
|
|
|
.toList();
|
|
|
|
|
}
|
|
|
|
|
// Gestione del JSON piatto (se mai lo userai in altre chiamate RPC o viste)
|
|
|
|
|
else if (map['assigned_to_staff'] != null) {
|
|
|
|
|
staffList = (map['assigned_to_staff'] as List)
|
|
|
|
|
.map((s) => StaffMemberModel.fromMap(s))
|
2026-05-26 12:28:12 +02:00
|
|
|
.toList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return TaskModel(
|
2026-05-26 19:31:25 +02:00
|
|
|
id: map['id'] as String?,
|
|
|
|
|
companyId: map['company_id'] as String?,
|
|
|
|
|
title: map['title'] as String? ?? '',
|
|
|
|
|
description: map['description'] as String?,
|
2026-05-26 12:28:12 +02:00
|
|
|
assignedToIds: parsedAssignedToIds,
|
2026-05-26 19:31:25 +02:00
|
|
|
assignedToStaff: staffList,
|
|
|
|
|
createdById: map['created_by_id'] as String?,
|
|
|
|
|
dueDate: map['due_date'] != null
|
|
|
|
|
? DateTime.parse(map['due_date'] as String).toLocal()
|
2026-05-26 12:28:12 +02:00
|
|
|
: null,
|
2026-05-26 19:31:25 +02:00
|
|
|
status: TaskStatusExtension.fromString(map['status'] as String?),
|
|
|
|
|
createdAt: map['created_at'] != null
|
|
|
|
|
? DateTime.parse(map['created_at'] as String).toLocal()
|
2026-05-26 12:28:12 +02:00
|
|
|
: null,
|
2026-05-26 19:31:25 +02:00
|
|
|
storeId: map['store_id'] as String?,
|
2026-05-26 12:28:12 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- SERIALIZZAZIONE VERSO SUPABASE ---
|
|
|
|
|
Map<String, dynamic> toMap() {
|
|
|
|
|
return {
|
|
|
|
|
if (id != null) 'id': id,
|
|
|
|
|
if (companyId != null) 'company_id': companyId,
|
|
|
|
|
'title': title,
|
|
|
|
|
if (description != null) 'description': description,
|
|
|
|
|
// Passiamo l'array vuoto se non ci sono assegnazioni
|
|
|
|
|
'assigned_to_ids': assignedToIds.isEmpty ? null : assignedToIds,
|
|
|
|
|
if (createdById != null) 'created_by_id': createdById,
|
|
|
|
|
'due_date': dueDate?.toUtc().toIso8601String(),
|
|
|
|
|
'status': status.toValue,
|
|
|
|
|
'store_id': storeId,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|