Files
tasq/lib/utils/programmer_task_ledger.dart
redz1029 b307f66f2c Add PM extension (Projects, Work Logs, Comments) + Quill rich-text UI
Projects: Brick-backed model with list/detail screens and nav entry.
Work Logs: per-contributor time ledger, assignee running-clock +
helper logged minutes with Hours/Minutes input; pause-after-save and
helper-deduct prompts.
Comments: threaded comments with file attachments and Quill composer.
Detail screen: tabbed layout (Work Log / Comments / Activity),
editable title + description via edit dialog, Quill-rendered
descriptions with legacy plain-text fallback.
Create dialog: fixed-width (480px max), Quill description editor.
Shared QuillFieldEditor + QuillReadOnly widgets extracted for reuse.
RLS: hardened activity-log actor binding, immutable creator_id/
created_at triggers, SECURITY INVOKER visibility helper,
append-only work-log and scoped comment policies.
Brick migration for project_id FK on programmer_tasks.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-09-27 16:06:26 +08:00

79 lines
2.3 KiB
Dart

import '../models/programmer_task_work_log.model.dart';
/// Formats a worked duration compactly, e.g. `2h 15m` or `45m`.
String formatDurationHm(Duration d) {
final h = d.inHours;
final m = d.inMinutes.remainder(60);
if (h > 0) return '${h}h ${m}m';
return '${m}m';
}
/// One contributor's share of a task's time.
class LedgerEntry {
final String userId;
final Duration duration;
final bool isAssignee;
const LedgerEntry({
required this.userId,
required this.duration,
required this.isAssignee,
});
}
/// Per-contributor time breakdown for a task: the assignee's effective running
/// duration plus each helper's summed logged minutes.
class TaskTimeLedger {
final LedgerEntry? assignee;
final List<LedgerEntry> helpers;
const TaskTimeLedger({required this.assignee, required this.helpers});
Duration get total =>
(assignee?.duration ?? Duration.zero) +
helpers.fold(Duration.zero, (a, e) => a + e.duration);
List<LedgerEntry> get all => [?assignee, ...helpers];
}
/// Builds the per-contributor ledger for a task.
///
/// [assigneeDuration] is the assignee's effective running duration (already
/// computed from the activity log, including any adjustments). Helper entries
/// sum the `minutes` of work logs authored by someone other than the assignee;
/// assignee work logs (minutes == null progress notes) are excluded because the
/// assignee's time comes from the running clock.
TaskTimeLedger computeTaskLedger({
required String? assigneeId,
required Duration assigneeDuration,
required List<ProgrammerTaskWorkLog> workLogs,
}) {
final byHelper = <String, int>{};
for (final log in workLogs) {
final minutes = log.minutes;
if (minutes == null || minutes <= 0) continue;
if (log.authorId == assigneeId) continue;
byHelper[log.authorId] = (byHelper[log.authorId] ?? 0) + minutes;
}
final helpers = byHelper.entries
.map((e) => LedgerEntry(
userId: e.key,
duration: Duration(minutes: e.value),
isAssignee: false,
))
.toList()
..sort((a, b) => b.duration.compareTo(a.duration));
return TaskTimeLedger(
assignee: assigneeId == null
? null
: LedgerEntry(
userId: assigneeId,
duration: assigneeDuration,
isAssignee: true,
),
helpers: helpers,
);
}