Files
tasq/lib/utils/programmer_day_rows.dart
redz1029 12842bfa12 fix(programmer): day rows — Delta JSON test + non-assignee row gate
- Add Delta JSON happy-path test case to deltaJsonToPlainText tests
- Fix double-row bug: notes-only assignee rows only when userId == assigneeId
- Remove dead code: tautological taskMap.containsKey check in loop
Co-Authored-By: claude-flow <ruv@ruv.net>
2026-09-27 21:08:30 +08:00

161 lines
5.1 KiB
Dart

import 'package:tasq/models/programmer_task.model.dart';
import 'package:tasq/models/programmer_task_activity_log.model.dart';
import 'package:tasq/models/programmer_task_work_log.model.dart';
import 'package:tasq/widgets/quill_field.dart';
import 'programmer_daily_time.dart';
/// A single row in a programmer's day sheet (assignee or helper work).
class DaySheetRow {
final String taskId;
final String title;
final String category;
final String kind; // 'assignee' or 'helper'
final String? projectName;
final String? helpedName;
final int seconds;
final List<String> notes;
DaySheetRow({
required this.taskId,
required this.title,
required this.category,
required this.kind,
this.projectName,
this.helpedName,
required this.seconds,
required this.notes,
});
Map<String, dynamic> toJson() => {
'task_id': taskId,
'title': title,
'category': category,
'project_name': projectName,
'kind': kind,
'helped_name': helpedName,
'seconds': seconds,
'notes': notes,
};
factory DaySheetRow.fromJson(Map<String, dynamic> json) => DaySheetRow(
taskId: json['task_id'] as String,
title: json['title'] as String,
category: json['category'] as String,
kind: json['kind'] as String,
projectName: json['project_name'] as String?,
helpedName: json['helped_name'] as String?,
seconds: json['seconds'] as int,
notes: List<String>.from(json['notes'] as List? ?? []),
);
}
/// Build day rows for a programmer on a specific day.
/// Returns rows for assignee tasks (by seconds desc) then helper tasks (by seconds desc).
List<DaySheetRow> buildDayRows({
required String userId,
required DateTime day,
required List<ProgrammerTask> tasks,
required Map<String, List<ProgrammerTaskActivityLog>> logsByTask,
required List<ProgrammerTaskWorkLog> workLogs,
required Map<String, String> projectNames,
required Map<String, String> profileNames,
required DateTime now,
}) {
final taskMap = {for (final t in tasks) t.id: t};
final workLogsByTaskAndAuthor = <String, List<ProgrammerTaskWorkLog>>{};
// Group work logs by task and author for quick lookup
for (final log in workLogs) {
final key = '${log.taskId}:${log.authorId}';
workLogsByTaskAndAuthor.putIfAbsent(key, () => []).add(log);
}
final rows = <DaySheetRow>[];
// Process each task
for (final task in tasks) {
final assigneeId = task.assigneeId;
// Compute daily seconds for this task
final logs = logsByTask[task.id] ?? [];
final dailyByAssignee = dailySecondsByAssignee(
task: task,
logs: logs,
now: now,
);
final userDailySeconds = dailyByAssignee[userId] ?? {};
final assigneeSeconds = userDailySeconds[day] ?? 0;
// Check for assignee row: has seconds that day OR (is assignee AND authored notes-only log that day)
final assigneeLogsKey = '${task.id}:$userId';
final assigneeLogsThisDay = (userId == assigneeId)
? (workLogsByTaskAndAuthor[assigneeLogsKey]
?.where((l) => l.workDate == day && l.minutes == null)
.toList() ??
[])
: [];
if (assigneeSeconds > 0 || assigneeLogsThisDay.isNotEmpty) {
final notes = assigneeLogsThisDay
.map((l) => deltaJsonToPlainText(l.description))
.where((n) => n.isNotEmpty)
.toList();
rows.add(DaySheetRow(
taskId: task.id,
title: task.title,
category: task.category,
kind: 'assignee',
projectName: task.projectId != null ? projectNames[task.projectId] : null,
helpedName: null,
seconds: assigneeSeconds,
notes: notes,
));
}
// Check for helper row: userId != assigneeId AND has minutes logs that day
if (userId != assigneeId) {
final helperLogsKey = '${task.id}:$userId';
final helperLogs = workLogsByTaskAndAuthor[helperLogsKey]
?.where((l) => l.workDate == day && l.minutes != null && l.minutes! > 0)
.toList() ??
[];
if (helperLogs.isNotEmpty) {
final totalMinutes = helperLogs.fold<int>(0, (sum, l) => sum + (l.minutes ?? 0));
final seconds = totalMinutes * 60;
final notes = helperLogs
.map((l) => deltaJsonToPlainText(l.description))
.where((n) => n.isNotEmpty)
.toList();
rows.add(DaySheetRow(
taskId: task.id,
title: task.title,
category: task.category,
kind: 'helper',
projectName: task.projectId != null ? projectNames[task.projectId] : null,
helpedName: assigneeId != null ? profileNames[assigneeId] : null,
seconds: seconds,
notes: notes,
));
}
}
}
// Sort: assignee rows by seconds desc, then helper rows by seconds desc
rows.sort((a, b) {
if (a.kind != b.kind) {
return a.kind == 'assignee' ? -1 : 1;
}
return b.seconds.compareTo(a.seconds);
});
return rows;
}
/// Calculate total seconds from day sheet rows.
int totalSeconds(List<DaySheetRow> rows) {
return rows.fold(0, (sum, row) => sum + row.seconds);
}