Files
tasq/lib/utils/task_duration.dart
T
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

102 lines
3.8 KiB
Dart

import '../models/task_activity_log.model.dart';
/// A minimal activity event shape: just the fields the worked-duration math
/// needs. Both [TaskActivityLog] and ProgrammerTaskActivityLog map onto this,
/// so the pause-aware duration logic is shared across task modules.
typedef DurationEvent = ({DateTime createdAt, String actionType});
// ---------------------------------------------------------------------------
// Generic core (works on any DurationEvent list)
// ---------------------------------------------------------------------------
/// Finds the canonical execution start time from [events].
///
/// [events] is expected in DESCENDING order (newest first). The reversed
/// iteration walks chronologically forward and stops at the earliest 'started'
/// event, falling back to [fallbackStartedAt].
DateTime? resolveExecutionStart(
List<DurationEvent> events,
DateTime? fallbackStartedAt,
) {
DateTime? started;
for (final entry in events.reversed) {
if (entry.actionType == 'started') {
started = entry.createdAt;
break;
}
}
return started ?? fallbackStartedAt;
}
/// Computes the effective worked duration, subtracting all paused intervals
/// between the resolved start and [endAt], then subtracting [adjustment]
/// (e.g. time reassigned to another task when the user helped elsewhere).
///
/// [events] must be in DESCENDING order (newest first) — the same order
/// returned by activity-log queries with `ascending: false`. The result never
/// goes below zero.
Duration computeEffectiveDuration({
required DateTime? fallbackStartedAt,
required DateTime endAt,
required List<DurationEvent> events,
Duration adjustment = Duration.zero,
}) {
final start = resolveExecutionStart(events, fallbackStartedAt);
if (start == null || !endAt.isAfter(start)) return Duration.zero;
Duration pausedTotal = Duration.zero;
DateTime? pausedSince;
// events.reversed → ascending (chronological) order within [start, endAt]
final relevant = events.reversed.where((e) {
if (e.createdAt.isBefore(start)) return false;
if (e.createdAt.isAfter(endAt)) return false;
return e.actionType == 'paused' || e.actionType == 'resumed';
});
for (final event in relevant) {
if (event.actionType == 'paused') {
pausedSince ??= event.createdAt;
} else if (event.actionType == 'resumed' && pausedSince != null) {
if (event.createdAt.isAfter(pausedSince)) {
pausedTotal += event.createdAt.difference(pausedSince);
}
pausedSince = null;
}
}
// Still paused at endAt — count the open pause interval.
if (pausedSince != null && endAt.isAfter(pausedSince)) {
pausedTotal += endAt.difference(pausedSince);
}
final total = endAt.difference(start) - pausedTotal - adjustment;
return total.isNegative ? Duration.zero : total;
}
// ---------------------------------------------------------------------------
// TaskActivityLog wrappers (unchanged public API — existing call sites intact)
// ---------------------------------------------------------------------------
List<DurationEvent> _toEvents(List<TaskActivityLog> logs) =>
logs.map((l) => (createdAt: l.createdAt, actionType: l.actionType)).toList();
/// Finds the canonical execution start time for a task. See
/// [resolveExecutionStart].
DateTime? resolveTaskExecutionStart(
List<TaskActivityLog> logs,
DateTime? fallbackStartedAt,
) => resolveExecutionStart(_toEvents(logs), fallbackStartedAt);
/// Computes the effective worked duration for a task. See
/// [computeEffectiveDuration].
Duration computeEffectiveTaskDuration({
required DateTime? fallbackStartedAt,
required DateTime endAt,
required List<TaskActivityLog> logs,
}) => computeEffectiveDuration(
fallbackStartedAt: fallbackStartedAt,
endAt: endAt,
events: _toEvents(logs),
);