import 'package:tasq/utils/programmer_daily_time.dart'; import 'package:tasq/utils/programmer_day_rows.dart'; /// Status constants for a programmer day sheet. class DaySheetStatus { static const String draft = 'draft'; static const String pending = 'pending'; static const String disapproved = 'disapproved'; static const String approved = 'approved'; static String label(String s) { switch (s) { case draft: return 'Draft'; case pending: return 'Pending'; case disapproved: return 'Disapproved'; case approved: return 'Approved'; default: return s; } } } /// Immutable snapshot of a day sheet at approval time. class DaySheetSnapshot { final int totalSeconds; final List rows; const DaySheetSnapshot({required this.totalSeconds, required this.rows}); Map toJson() => { 'v': 1, 'total_seconds': totalSeconds, 'rows': rows.map((r) => r.toJson()).toList(), }; /// Alias for [toJson]; used by the approval RPC payload. Map toMap() => toJson(); factory DaySheetSnapshot.fromJson(Map j) { final rowsList = (j['rows'] as List? ?? []) .map((r) => DaySheetRow.fromJson(r as Map)) .toList(); return DaySheetSnapshot( totalSeconds: (j['total_seconds'] as num).toInt(), rows: rowsList, ); } factory DaySheetSnapshot.fromRows(List rows) { final total = rows.fold(0, (sum, r) => sum + r.seconds); return DaySheetSnapshot(totalSeconds: total, rows: rows); } } /// A programmer's daily time-sheet submission for one work day. class ProgrammerDaySheet { final String id; final String programmerId; final String status; final DateTime workDate; final DateTime? submittedAt; final DateTime? reviewedAt; final bool autoSubmitted; final String? reviewedBy; final int resubmissions; final DaySheetSnapshot? approvedSnapshot; final DateTime createdAt; final DateTime updatedAt; const ProgrammerDaySheet({ required this.id, required this.programmerId, required this.status, required this.workDate, this.submittedAt, this.reviewedAt, this.autoSubmitted = false, this.reviewedBy, this.resubmissions = 0, this.approvedSnapshot, required this.createdAt, required this.updatedAt, }); /// True when approved on the first submission (no resubmissions needed). bool get isFirstPassApproved => status == DaySheetStatus.approved && resubmissions == 0; factory ProgrammerDaySheet.fromMap(Map m) { return ProgrammerDaySheet( id: m['id'] as String, programmerId: m['programmer_id'] as String, status: m['status'] as String? ?? DaySheetStatus.draft, workDate: parseDayKey(m['work_date'] as String), submittedAt: m['submitted_at'] != null ? DateTime.parse(m['submitted_at'] as String) : null, reviewedAt: m['reviewed_at'] != null ? DateTime.parse(m['reviewed_at'] as String) : null, autoSubmitted: m['auto_submitted'] as bool? ?? false, reviewedBy: m['reviewed_by'] as String?, resubmissions: (m['resubmissions'] as num?)?.toInt() ?? 0, approvedSnapshot: m['approved_snapshot'] != null ? DaySheetSnapshot.fromJson( m['approved_snapshot'] as Map) : null, createdAt: DateTime.parse(m['created_at'] as String), updatedAt: DateTime.parse(m['updated_at'] as String), ); } }