Contacts DB tracks how long tasks spend in key workflow stages without requiring any manual timers. Every time a task’s status changes, theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/0m1n3m/contacts-db/llms.txt
Use this file to discover all available pages before exploring further.
TimeTrackingService inspects the previous status, calculates how many working seconds elapsed since the task entered that status, and adds the result to the appropriate time bucket on the task record. This gives teams accurate cycle-time data grounded in real working hours rather than calendar time.
Time metrics
Three independent time metrics are tracked per task, each stored as an integer number of seconds:| Metric | Database field | Accumulates when |
|---|---|---|
| Lead Time | lead_time | Task is created → task reaches done (total elapsed working time end-to-end) |
| Dev Time | dev_time | Task is in in_progress status |
| Review Time | review_time | Task is in in_review status |
lead_time, dev_time, and review_time are stored as unsignedBigInteger and default to 0. backward_transitions is stored as unsignedInteger and also defaults to 0. All four fields were added by the 2026_07_15_085835_add_time_tracking_to_tasks_table migration.
Working time calculation
Time is measured in working seconds only — weekends are excluded entirely, and only weekday time counts. TheWorkingTimeHelper class provides all the conversion utilities used across the application:
getWorkingSecondsPrecise counts partial days accurately. If a task entered in_progress at 16:00 on a Friday and the status changed at 10:00 the following Monday, only 2 hours (Friday 16:00–18:00… — actually the helper counts to end of day then resumes Monday) of weekend-free working time would accumulate, not the full weekend gap. Periods outside of working hours are not capped by a hard 09:00–18:00 window in this implementation; the helper counts all weekday time between the two timestamps, treating each weekday as a full working day.How accumulation works
Time accumulation is driven byTimeTrackingService::recordStateChange, which is called automatically whenever a task’s status field changes. The sequence on every status transition is:
Log the transition
A
status_changed entry is written to AuditLog with before.status and after.status. This audit history is later used for recalculation.Detect backward transitions
The service compares the numeric position of the old and new statuses using a fixed order (
created=0, accepted=1, in_progress=2, in_review=3, done=4). If the new position is lower than the old one (for example, moving from in_review back to in_progress), the backward_transitions counter on the task is incremented by one.Accumulate elapsed time for the previous status
Working seconds are calculated from
entered_current_status_at (when the task entered its previous status) to now():- If the previous status was
in_progress→ the elapsed seconds are added todev_time - If the previous status was
in_review→ the elapsed seconds are added toreview_time - Other statuses (
created,accepted) do not accumulate into any bucket
Set lead time on completion
If the new status is
done, lead_time is computed as the working seconds from task.created_at to the current moment, and completed_at is stamped with the current timestamp.All in-memory mutations made by
recordStateChange (backward_transitions, dev_time, review_time, lead_time, completed_at, entered_current_status_at) are applied directly to the $task model instance without calling save() inside the service. Laravel’s model updating event persists them automatically when the status change is saved by the calling action.Backward transitions
Thebackward_transitions field counts how many times a task has moved to an earlier stage in the workflow. A value of 0 means the task moved linearly through the pipeline; a high value may indicate unclear requirements, blocked work, or review quality issues. Use it as a simple process-health indicator when reviewing completed tasks.
Completed at
completed_at is set the first time a task reaches done. If a task is later moved backward (e.g., back to in_review) and then completes again, completed_at will be overwritten with the new completion timestamp. Use the audit log to reconstruct the full history.
Accessing time data on a task
TheTask model exposes convenience methods that delegate to WorkingTimeHelper so you never need to handle raw second values directly:
Recalculating times from history
If a task’s time data becomes inconsistent — for example after a direct database edit or a data migration — you can recompute all three metrics from scratch using the audit log:- Wraps everything in
Task::withoutEvents()so the observer does not fire during the rebuild - Resets
lead_time,dev_time,review_time,backward_transitions, andcompleted_atto zero / null in memory - Replays every
status_changedaudit log entry in chronological order, accumulating dev time, review time, and backward transition counts using the sameWorkingTimeHelperlogic - Calculates
lead_timefrom the first to last audit log entry if the final status isdone - Calls
$task->save()once to persist the fully recalculated state in a single query