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 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 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 workLogs, }) { final byHelper = {}; 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, ); }