Files
tasq/lib/utils/task_duration.dart
T
redz1029 627645056b Add Programmer Tasks module (work-log + time tracking)
Separate feature for the `programmer` role to record dev work by category,
distinct from the IT help-desk `tasks` feature. Modeled on the IT Service
Requests pattern (Brick offline-first model -> Riverpod stream/controller ->
list/detail screens); seed of a future Project Management workflow.

- programmer_tasks + programmer_task_activity_logs (category enum, RLS,
  realtime) via supabase migration
- single assignee, self-logged or lead-assigned; assign/reassign logged
- pause-on-switch: starting a task while another runs prompts to pause the
  running one, keeping one active task per user for accurate time attribution
- worked-duration math shared with help-desk tasks via a generic core in
  task_duration.dart (existing callers unchanged)
- list distinguishes running vs paused with a per-task Resume shortcut
- accessible dialog labels; route /programmer-tasks + nav gated to admin/programmer
- unit/widget tests: controller lifecycle, duration math, pause-switch dialog

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-09-27 01:13:33 +08:00

99 lines
3.6 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].
///
/// [events] must be in DESCENDING order (newest first) — the same order
/// returned by activity-log queries with `ascending: false`.
Duration computeEffectiveDuration({
required DateTime? fallbackStartedAt,
required DateTime endAt,
required List<DurationEvent> events,
}) {
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;
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),
);