# Time Reports (`/reports/time`)

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 56  
**Tier:** All tiers  
**Depends on:** `time-management`, `projects-module`, `foundation-auth-rbac`  
**Referenced by:** `time-management`, `reports-analytics`

---

## Overview

Aggregated time reporting for staff and managers. Route `/reports/time` in zync-app (canonical — reports-nav-hub, spec 103, is authoritative for all report routes). Spec 13 (`time-management`) owns the `time_entries` data model and CRUD API; this spec owns the reports UI and the aggregation API endpoint.

---

## Page: `/reports/time`

Three tabs: **By Person**, **By Project**, **By Task**.

### Controls (shared across all tabs)

```
┌──────────────────────────────────────────────────────────────┐
│  Time Reports                          [Export CSV ▾]        │
│                                                              │
│  Period:  [This Month ▾]   From: [____]  To: [____]         │
│  User:    [All Users ▾]    Project: [All Projects ▾]        │
└──────────────────────────────────────────────────────────────┘
```

Period presets: This Week, Last Week, This Month, Last Month, This Quarter, Custom.  
Custom: `from` / `to` date pickers (ISO date, no time).

User filter: OWNER/ADMIN with `reports:read` see all active tenant users + active contractors. MEMBER and any user without `reports:read` sees only their own entries (filter hidden or self-only, locked by API).

Permission: `time:read`. `reports:read` required to see all users' entries.

---

### Tab: By Person

```
┌──────────────────────────────────────────────────────────────┐
│  Person              Role          Hours    Billable  Entries │
│  ──────────────────────────────────────────────────────────  │
│  Alex Katz           Owner         34h 20m  28h 00m  47      │
│  Dana Levi           Member        22h 15m  20h 10m  31      │
│  [Contractor] Oren   Contractor    10h 00m  10h 00m  12      │
│  ──────────────────────────────────────────────────────────  │
│  Total                             66h 35m  58h 10m  90      │
└──────────────────────────────────────────────────────────────┘
```

Row click → expands inline to show that person's project breakdown for the period.

Contractors listed separately under a "Contractors" group header. Billable hours = entries where `time_entries.billable = true`.

---

### Tab: By Project

```
┌──────────────────────────────────────────────────────────────┐
│  Project             Customer       Hours    Members          │
│  ──────────────────────────────────────────────────────────  │
│  Website Redesign    Acme Corp      28h 00m  Alex, Dana       │
│  Mobile App          Globex         22h 15m  Alex             │
│  Internal            —              16h 20m  Dana, Oren       │
│  ──────────────────────────────────────────────────────────  │
│  Total                              66h 35m                   │
└──────────────────────────────────────────────────────────────┘
```

Row click → expands to show per-person breakdown within that project.

---

### Tab: By Task

```
┌──────────────────────────────────────────────────────────────┐
│  Task                 Project            Person       Hours   │
│  ───────────────────────────────────────────────────────────  │
│  Homepage design      Website Redesign   Alex Katz    8h 00m  │
│  API integration      Mobile App         Alex Katz    12h 00m │
│  Code review          Mobile App         Dana Levi    4h 15m  │
│  [No task]            Website Redesign   Dana Levi    6h 00m  │
└──────────────────────────────────────────────────────────────┘
```

Entries with no linked task grouped under "[No task]".

---

## Export

"Export CSV ▾" dropdown: **CSV** / **Excel**. Exports the currently active tab's data with all applied filters.  
`GET /api/time/reports/export?groupBy=person|project|task&from=&to=&format=csv|xlsx`

---

## API Endpoint

```
GET  /api/time/reports
     → aggregated time data
       query: groupBy=person|project|task, from=YYYY-MM-DD, to=YYYY-MM-DD,
              userId?, contractorId?, projectId?, billable?
       returns: { rows: [...], totals: { hours, billableHours, entries } }

GET  /api/time/reports/export
     → CSV or XLSX download
       query: same as above + format=csv|xlsx

GET  /api/time/reports/people-options
     → person filter options
       returns: { people: [{ id, type: 'user'|'contractor', name, email, roleLabel }] }
```

All endpoints require `time:read`. `people-options` returns all active tenant users + active contractors only when `reports:read` is present; otherwise it returns the current user only. Report/export requests without `reports:read` are scoped to `tenantQuery` + `userId = currentUser.id`, ignoring requested `userId`/`contractorId`.

Data source: `time_entries` with `GROUP BY` on the requested dimension. No pre-aggregation table needed at current scale.

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| On-demand aggregation | No pre-aggregate table | Time entries per tenant per month ≈ hundreds to low-thousands; GROUP BY query < 200ms; no queue needed |
| MEMBER scope lock | Filter hidden, locked to self | MEMBER should see personal productivity, not colleagues' hours |
| Contractors in By Person tab | Listed as separate group | Contractors not in `users` table (in `contractors`); separate row type prevents JOIN confusion |
| Person filter options | Report-scoped `/people-options` endpoint | Existing team-user endpoints omit contractors and are guarded for user management, not reports |
