Added Type, Category and Signatories on task

This commit is contained in:
2026-02-21 14:33:22 +08:00
parent d32449d096
commit 8d31a629ac
14 changed files with 1178 additions and 48 deletions
+93 -4
View File
@@ -209,21 +209,41 @@ final tasksControllerProvider = Provider<TasksController>((ref) {
class TasksController {
TasksController(this._client);
final SupabaseClient _client;
// _client is declared dynamic allowing test doubles that mimic only the
// subset of methods used by this class. In production it will be a
// SupabaseClient instance.
final dynamic _client;
Future<void> createTask({
required String title,
required String description,
String? officeId,
String? ticketId,
// optional request metadata when creating a task
String? requestType,
String? requestTypeOther,
String? requestCategory,
}) async {
final actorId = _client.auth.currentUser?.id;
final payload = <String, dynamic>{
'title': title,
'description': description,
};
if (officeId != null) payload['office_id'] = officeId;
if (ticketId != null) payload['ticket_id'] = ticketId;
if (officeId != null) {
payload['office_id'] = officeId;
}
if (ticketId != null) {
payload['ticket_id'] = ticketId;
}
if (requestType != null) {
payload['request_type'] = requestType;
}
if (requestTypeOther != null) {
payload['request_type_other'] = requestTypeOther;
}
if (requestCategory != null) {
payload['request_category'] = requestCategory;
}
final data = await _client
.from('tasks')
@@ -291,7 +311,7 @@ class TasksController {
.from('profiles')
.select('id, role')
.inFilter('role', roles);
final rows = data as List<dynamic>;
final rows = data;
final ids = rows
.map((row) => row['id'] as String?)
.whereType<String>()
@@ -303,10 +323,36 @@ class TasksController {
}
}
/// Update only the status of a task.
///
/// Before marking a task as **completed** we enforce that the
/// request type/category metadata have been provided. This protects the
/// business rule that details must be specified before closing.
Future<void> updateTaskStatus({
required String taskId,
required String status,
}) async {
if (status == 'completed') {
// fetch current metadata to validate
try {
final row = await _client
.from('tasks')
.select('request_type, request_category')
.eq('id', taskId)
.maybeSingle();
final rt = row is Map ? row['request_type'] : null;
final rc = row is Map ? row['request_category'] : null;
if (rt == null || rc == null) {
throw Exception(
'Request type and category must be set before completing a task.',
);
}
} catch (e) {
// rethrow so callers can handle
rethrow;
}
}
await _client.from('tasks').update({'status': status}).eq('id', taskId);
// Log important status transitions
@@ -330,6 +376,49 @@ class TasksController {
}
}
/// Update arbitrary fields on a task row.
///
/// Primarily used to set request metadata after creation or during
/// status transitions.
Future<void> updateTask({
required String taskId,
String? requestType,
String? requestTypeOther,
String? requestCategory,
String? status,
String? requestedBy,
String? notedBy,
String? receivedBy,
}) async {
final payload = <String, dynamic>{};
if (requestType != null) {
payload['request_type'] = requestType;
}
if (requestTypeOther != null) {
payload['request_type_other'] = requestTypeOther;
}
if (requestCategory != null) {
payload['request_category'] = requestCategory;
}
if (requestedBy != null) {
payload['requested_by'] = requestedBy;
}
if (notedBy != null) {
payload['noted_by'] = notedBy;
}
// `performed_by` is derived from task assignments; we don't persist it here.
if (receivedBy != null) {
payload['received_by'] = receivedBy;
}
if (status != null) {
payload['status'] = status;
}
if (payload.isEmpty) {
return;
}
await _client.from('tasks').update(payload).eq('id', taskId);
}
// Auto-assignment logic executed once on creation.
Future<void> _autoAssignTask({
required String taskId,