feat(reports): support multi-user chart series

This commit is contained in:
2026-05-13 09:59:23 +03:30
parent f9c4c06531
commit 77c07adec8
2 changed files with 66 additions and 18 deletions

View File

@@ -387,9 +387,23 @@ def build_chart_report(actor, raw_filters) -> dict:
filters = load_report_filters(actor, raw_filters)
entries = list(_base_queryset(filters))
summary = _summary_from_entries(entries)
buckets: dict[str, dict] = {}
grouped_entries: dict[str | None, list[TimeEntry]] = defaultdict(list)
if filters.is_workspace_scope and not filters.user_id:
for entry in entries:
grouped_entries[str(entry.user_id)].append(entry)
else:
grouped_entries[filters.user_id] = entries
serialized_series = []
for _, series_entries in sorted(
grouped_entries.items(),
key=lambda item: _user_display(item[1][0].user).lower() if item[1] else "",
):
if not series_entries:
continue
buckets: dict[str, dict] = {}
for entry in series_entries:
local_start = _localize_datetime(entry.start_time)
bucket_id, bucket_date = _bucket_key(filters, local_start)
bucket = buckets.setdefault(
@@ -408,10 +422,22 @@ def build_chart_report(actor, raw_filters) -> dict:
bucket["total_duration"] = _format_duration_seconds(bucket["total_seconds"])
serialized_buckets.append(bucket)
user = series_entries[0].user
serialized_series.append(
{
"user": {
"id": str(user.id),
"name": _user_display(user),
"mobile": user.mobile,
},
"buckets": serialized_buckets,
}
)
return {
"scope": _scope_payload(filters),
"summary": summary,
"buckets": serialized_buckets,
"series": serialized_series,
}

View File

@@ -100,6 +100,28 @@ class ReportViewTests(APITestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["summary"]["total_duration"], "01:00:00")
self.assertEqual(len(response.data["series"]), 1)
self.assertEqual(response.data["series"][0]["user"]["id"], str(self.member.id))
def test_admin_chart_without_user_filter_returns_series_for_all_users(self):
self.client.force_authenticate(user=self.admin)
with patch(
"apps.reports.services.aggregation.timezone.localdate",
return_value=date(2026, 4, 20),
):
response = self.client.get(
"/api/reports/chart/",
{"workspace": str(self.workspace.id), "period": "this_month"},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["summary"]["total_duration"], "03:00:00")
self.assertEqual(len(response.data["series"]), 2)
self.assertEqual(
{series["user"]["id"] for series in response.data["series"]},
{str(self.owner.id), str(self.member.id)},
)
def test_admin_can_request_combined_table_report(self):
self.client.force_authenticate(user=self.admin)