nexus-5/core/graphql/queries/dashboard.py
2026-01-26 11:09:40 -05:00

275 lines
8.4 KiB
Python

from datetime import date
from typing import Optional
import strawberry
from strawberry import ID
from django.db.models import Prefetch
from asgiref.sync import sync_to_async
from core.graphql.types.dashboard import (
AdminDashboardData,
TeamDashboardData,
CustomerDashboardData,
)
from core.models.service import Service
from core.models.project import Project
from core.models.invoice import Invoice
from core.models.report import Report
from core.models.scope_template import ScopeTemplate, AreaTemplate, TaskTemplate
from core.models.project_scope_template import (
ProjectScopeTemplate,
ProjectAreaTemplate,
ProjectTaskTemplate,
)
def parse_month_range(month: str) -> tuple[date, date]:
"""Parse a month string like '2024-01' into start and end dates."""
year, month_num = map(int, month.split('-'))
start = date(year, month_num, 1)
# Calculate end of month
if month_num == 12:
end = date(year + 1, 1, 1)
else:
end = date(year, month_num + 1, 1)
# End is exclusive, so subtract one day for inclusive range
from datetime import timedelta
end = end - timedelta(days=1)
return start, end
def _fetch_admin_dashboard_sync(
start: date,
end: date,
invoice_status: Optional[str],
) -> AdminDashboardData:
"""Synchronous database fetching for admin dashboard."""
# Services - optimized with prefetch for team_members
services = list(
Service.objects
.filter(date__gte=start, date__lte=end)
.select_related('account_address', 'account_address__account')
.prefetch_related('team_members')
.order_by('date', 'id')
)
# Projects - optimized with prefetch for team_members
projects = list(
Project.objects
.filter(date__gte=start, date__lte=end)
.select_related('account_address', 'account_address__account', 'customer')
.prefetch_related('team_members')
.order_by('date', 'id')
)
# Invoices - show all (pages need full list, not month-filtered)
invoices_qs = Invoice.objects.select_related('customer')
if invoice_status:
invoices_qs = invoices_qs.filter(status=invoice_status)
invoices = list(invoices_qs.order_by('-date', '-id'))
# Reports - show all (pages need full list, not month-filtered)
reports = list(
Report.objects
.select_related('team_member')
.order_by('-date', '-id')
)
# Service Scope Templates - with nested areas and tasks prefetched
task_prefetch = Prefetch(
'task_templates',
queryset=TaskTemplate.objects.order_by('order', 'id')
)
area_prefetch = Prefetch(
'area_templates',
queryset=AreaTemplate.objects.prefetch_related(task_prefetch).order_by('order', 'name')
)
service_scope_templates = list(
ScopeTemplate.objects
.prefetch_related(area_prefetch)
.order_by('name')
)
# Project Scope Templates - with nested categories and tasks prefetched
project_task_prefetch = Prefetch(
'task_templates',
queryset=ProjectTaskTemplate.objects.order_by('order', 'id')
)
category_prefetch = Prefetch(
'category_templates',
queryset=ProjectAreaTemplate.objects.prefetch_related(project_task_prefetch).order_by('order', 'name')
)
project_scope_templates = list(
ProjectScopeTemplate.objects
.prefetch_related(category_prefetch)
.order_by('name')
)
return AdminDashboardData(
services=services,
projects=projects,
invoices=invoices,
reports=reports,
service_scope_templates=service_scope_templates,
project_scope_templates=project_scope_templates,
)
def _fetch_team_dashboard_sync(
team_profile_id: str,
start: date,
end: date,
) -> TeamDashboardData:
"""Synchronous database fetching for team dashboard."""
# Services assigned to this team member
services = list(
Service.objects
.filter(
team_members__id=team_profile_id,
date__gte=start,
date__lte=end
)
.select_related('account_address', 'account_address__account')
.prefetch_related('team_members')
.order_by('date', 'id')
)
# Projects assigned to this team member
projects = list(
Project.objects
.filter(
team_members__id=team_profile_id,
date__gte=start,
date__lte=end
)
.select_related('account_address', 'account_address__account', 'customer')
.prefetch_related('team_members')
.order_by('date', 'id')
)
# Reports for this team member
reports = list(
Report.objects
.filter(
team_member_id=team_profile_id,
date__gte=start,
date__lte=end
)
.select_related('team_member')
.order_by('-date', '-id')
)
return TeamDashboardData(
services=services,
projects=projects,
reports=reports,
)
def _fetch_customer_dashboard_sync(
customer_id: str,
) -> CustomerDashboardData:
"""Synchronous database fetching for customer dashboard."""
# Services for customer's accounts
services = list(
Service.objects
.filter(account_address__account__customer_id=customer_id)
.select_related('account_address', 'account_address__account')
.prefetch_related('team_members')
.order_by('-date', '-id')[:100] # Limit for performance
)
# Projects for customer
projects = list(
Project.objects
.filter(customer_id=customer_id)
.select_related('account_address', 'account_address__account', 'customer')
.prefetch_related('team_members')
.order_by('-date', '-id')[:100] # Limit for performance
)
# Invoices for customer
invoices = list(
Invoice.objects
.filter(customer_id=customer_id)
.select_related('customer')
.order_by('-date', '-id')[:100] # Limit for performance
)
return CustomerDashboardData(
services=services,
projects=projects,
invoices=invoices,
)
@strawberry.type
class Query:
@strawberry.field(
name="adminDashboard",
description="Consolidated dashboard data for admin/team leader users. "
"Returns all services, projects, invoices, reports, and scope templates "
"for the given month in a single optimized query."
)
async def admin_dashboard(
self,
info,
month: str,
invoice_status: Optional[str] = None,
) -> AdminDashboardData:
"""Fetch all admin dashboard data in a single optimized query.
Args:
month: Month string in format 'YYYY-MM' (e.g., '2024-01')
invoice_status: Optional invoice status filter (e.g., 'SENT', 'PAID')
Returns:
AdminDashboardData with all dashboard entities
"""
start, end = parse_month_range(month)
return await sync_to_async(_fetch_admin_dashboard_sync)(start, end, invoice_status)
@strawberry.field(
name="teamDashboard",
description="Consolidated dashboard data for team member users. "
"Returns services and projects assigned to the requesting user."
)
async def team_dashboard(
self,
info,
team_profile_id: ID,
month: str,
) -> TeamDashboardData:
"""Fetch all team dashboard data in a single optimized query.
Args:
team_profile_id: The team member's profile ID
month: Month string in format 'YYYY-MM' (e.g., '2024-01')
Returns:
TeamDashboardData with services and projects for the team member
"""
start, end = parse_month_range(month)
return await sync_to_async(_fetch_team_dashboard_sync)(team_profile_id, start, end)
@strawberry.field(
name="customerDashboard",
description="Consolidated dashboard data for customer users. "
"Returns services, projects, and invoices for the customer."
)
async def customer_dashboard(
self,
info,
customer_id: ID,
) -> CustomerDashboardData:
"""Fetch all customer dashboard data in a single optimized query.
Args:
customer_id: The customer's profile ID
Returns:
CustomerDashboardData with services, projects, and invoices
"""
return await sync_to_async(_fetch_customer_dashboard_sync)(customer_id)