feat(report): 'Last Activity' column in All Time Projects table
Konrad's Checkpoint-1 feedback: 'Inside the all time projects table, can we have a column with the last transaction date for a project? It will make it easier to find data for projects. It is nice to have the filter, but you can still skip around looking for when the last transaction was.' Added a 'last_activity' entry to each alltime_projects row in _build_report_context — computed as max(WorkLog.date) grouped by project name (respects the same project_ids/team_ids filters already applied to all_time_logs). Rendered in both the on-screen report (report.html) and the PDF (report_pdf.html) as a new 'Last Activity' column sitting between 'Start' and 'Working Days'. Existing ChapterOneEnrichmentTests extended with a last_activity assertion locking in the 'most recent log date' semantics. No other tests touched. 47/47 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0bbf2caae5
commit
f6975bfb2f
@ -565,6 +565,7 @@
|
||||
<tr>
|
||||
<th>Project</th>
|
||||
<th>Start</th>
|
||||
<th>Last Activity</th>
|
||||
<th class="r">Working Days</th>
|
||||
<th class="r">Total Cost</th>
|
||||
<th class="r">Avg R / Working Day</th>
|
||||
@ -573,6 +574,7 @@
|
||||
<tr>
|
||||
<td class="name">{{ item.project }}</td>
|
||||
<td>{% if item.start_date %}{{ item.start_date|date:"d M Y" }}{% else %}<span style="color:#cbd5e1;">—</span>{% endif %}</td>
|
||||
<td>{% if item.last_activity %}{{ item.last_activity|date:"d M Y" }}{% else %}<span style="color:#cbd5e1;">—</span>{% endif %}</td>
|
||||
<td class="r">{% if item.working_days %}{{ item.working_days }}{% else %}<span style="color:#cbd5e1;">—</span>{% endif %}</td>
|
||||
<td class="total">R {{ item.total|money }}</td>
|
||||
<td class="r">{% if item.working_days %}R {{ item.avg_per_working_day|money }}{% else %}<span style="color:#cbd5e1;">—</span>{% endif %}</td>
|
||||
|
||||
@ -245,6 +245,7 @@
|
||||
<tr>
|
||||
<th>Project</th>
|
||||
<th>Start</th>
|
||||
<th>Last Activity</th>
|
||||
<th class="text-end">Working Days</th>
|
||||
<th class="text-end">Total Cost</th>
|
||||
<th class="text-end">Avg R / Working Day</th>
|
||||
@ -255,6 +256,7 @@
|
||||
<tr>
|
||||
<td class="fw-medium">{{ item.project }}</td>
|
||||
<td>{% if item.start_date %}{{ item.start_date|date:"d M Y" }}{% else %}<span class="text-muted">—</span>{% endif %}</td>
|
||||
<td>{% if item.last_activity %}{{ item.last_activity|date:"d M Y" }}{% else %}<span class="text-muted">—</span>{% endif %}</td>
|
||||
<td class="text-end">{{ item.working_days|default:"—" }}</td>
|
||||
<td class="text-end fw-semibold">R {{ item.total|money }}</td>
|
||||
<td class="text-end">{% if item.working_days %}R {{ item.avg_per_working_day|money }}{% else %}<span class="text-muted">—</span>{% endif %}</td>
|
||||
|
||||
@ -692,6 +692,12 @@ class ChapterOneEnrichmentTests(TestCase):
|
||||
self.assertEqual(by_name['C1']['working_days'], 4)
|
||||
self.assertEqual(by_name['C1']['avg_per_working_day'], Decimal('200.00'))
|
||||
self.assertEqual(by_name['C1']['start_date'], datetime.date(2026, 1, 1))
|
||||
# last_activity = the most recent WorkLog.date (4th of March here)
|
||||
self.assertEqual(
|
||||
by_name['C1']['last_activity'], datetime.date(2026, 3, 4),
|
||||
'alltime_projects rows should expose the most-recent log date '
|
||||
'so the report can show "Last Activity" per project'
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@ -2138,6 +2138,15 @@ def _build_report_context(start_date, end_date, project_ids=None, team_ids=None)
|
||||
start_dates = dict(
|
||||
Project.objects.values_list('name', 'start_date')
|
||||
)
|
||||
# Lookup the most recent WorkLog.date for each project (for the new
|
||||
# "Last Activity" column — helps Konrad spot which projects are dormant
|
||||
# without having to scroll through date pickers).
|
||||
last_activity = dict(
|
||||
all_time_logs.filter(project__isnull=False)
|
||||
.values('project__name')
|
||||
.annotate(last=Max('date'))
|
||||
.values_list('project__name', 'last')
|
||||
)
|
||||
alltime_projects = []
|
||||
for row in alltime_projects_raw:
|
||||
name = row['project']
|
||||
@ -2149,6 +2158,7 @@ def _build_report_context(start_date, end_date, project_ids=None, team_ids=None)
|
||||
'worker_days': row['worker_days'],
|
||||
'total': total,
|
||||
'start_date': start_dates.get(name), # may be None
|
||||
'last_activity': last_activity.get(name), # may be None
|
||||
'working_days': wdays,
|
||||
'avg_per_working_day': avg,
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user