12842bfa12
- 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>
161 lines
5.1 KiB
Dart
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);
|
|
}
|