import 'dart:convert'; import '../utils/app_time.dart'; class Task { Task({ required this.id, required this.ticketId, required this.taskNumber, required this.title, required this.description, required this.officeId, required this.status, required this.priority, required this.queueOrder, required this.createdAt, required this.creatorId, required this.startedAt, required this.completedAt, // new optional metadata fields this.requestedBy, this.notedBy, this.receivedBy, this.requestType, this.requestTypeOther, this.requestCategory, this.actionTaken, this.cancellationReason, this.cancelledAt, this.itJobPrinted = false, this.itJobPrintedAt, this.itJobReceivedById, }); final String id; final String? ticketId; final String? taskNumber; final String title; final String description; final String? officeId; final String status; final int priority; final int? queueOrder; final DateTime createdAt; final String? creatorId; final DateTime? startedAt; final DateTime? completedAt; // Optional client/user metadata final String? requestedBy; final String? notedBy; final String? receivedBy; /// Optional request metadata added later in lifecycle. final String? requestType; final String? requestTypeOther; final String? requestCategory; // JSON serialized rich text for action taken (Quill Delta JSON encoded) final String? actionTaken; /// Cancellation details when a task was cancelled. final String? cancellationReason; final DateTime? cancelledAt; /// Whether the printed IT Job has been submitted. final bool itJobPrinted; final DateTime? itJobPrintedAt; final String? itJobReceivedById; @override bool operator ==(Object other) => identical(this, other) || other is Task && runtimeType == other.runtimeType && id == other.id && ticketId == other.ticketId && taskNumber == other.taskNumber && title == other.title && description == other.description && officeId == other.officeId && status == other.status && priority == other.priority && queueOrder == other.queueOrder && createdAt == other.createdAt && creatorId == other.creatorId && startedAt == other.startedAt && completedAt == other.completedAt && requestedBy == other.requestedBy && notedBy == other.notedBy && receivedBy == other.receivedBy && requestType == other.requestType && requestTypeOther == other.requestTypeOther && requestCategory == other.requestCategory && actionTaken == other.actionTaken && cancellationReason == other.cancellationReason && cancelledAt == other.cancelledAt && itJobPrinted == other.itJobPrinted && itJobPrintedAt == other.itJobPrintedAt && itJobReceivedById == other.itJobReceivedById; @override int get hashCode => Object.hash( id, ticketId, taskNumber, title, description, officeId, status, priority, queueOrder, createdAt, creatorId, startedAt, completedAt, requestedBy, notedBy, receivedBy, requestType, requestTypeOther, requestCategory, // Object.hash supports max 20 positional args; combine remainder. Object.hash(actionTaken, cancellationReason, cancelledAt, itJobPrinted, itJobPrintedAt, itJobReceivedById), ); /// Helper that indicates whether a completed task still has missing /// metadata such as signatories or action details. The parameter is used /// by UI to surface a warning icon/banner when a task has been closed but /// the user skipped filling out all fields. bool get hasIncompleteDetails { if (status != 'completed') return false; bool empty(String? v) => v == null || v.trim().isEmpty; return empty(requestedBy) || empty(notedBy) || empty(receivedBy) || empty(actionTaken); } factory Task.fromMap(Map map) { return Task( id: map['id'] as String, ticketId: map['ticket_id'] as String?, taskNumber: map['task_number'] as String?, title: map['title'] as String? ?? 'Task', description: map['description'] as String? ?? '', officeId: map['office_id'] as String?, status: map['status'] as String? ?? 'queued', priority: map['priority'] as int? ?? 1, queueOrder: map['queue_order'] as int?, createdAt: AppTime.parse(map['created_at'] as String), creatorId: map['creator_id'] as String?, startedAt: map['started_at'] == null ? null : AppTime.parse(map['started_at'] as String), completedAt: map['completed_at'] == null ? null : AppTime.parse(map['completed_at'] as String), requestType: map['request_type'] as String?, requestTypeOther: map['request_type_other'] as String?, requestCategory: map['request_category'] as String?, requestedBy: map['requested_by'] as String?, notedBy: map['noted_by'] as String?, receivedBy: map['received_by'] as String?, cancellationReason: map['cancellation_reason'] as String?, cancelledAt: map['cancelled_at'] == null ? null : AppTime.parse(map['cancelled_at'] as String), itJobPrinted: map['it_job_printed'] as bool? ?? false, itJobPrintedAt: map['it_job_printed_at'] == null ? null : AppTime.parse(map['it_job_printed_at'] as String), itJobReceivedById: map['it_job_received_by_id'] as String?, actionTaken: (() { final at = map['action_taken']; if (at == null) return null; if (at is String) return at; try { return jsonEncode(at); } catch (_) { return at.toString(); } })(), ); } }