feat(reports): capacity-based utilization + report CSVs

Utilization now measures booked minutes against each member's
availability-rule capacity in the tenant timezone, replacing the
confirmed-share proxy. StaffMetric exposes booked/capacity minutes.

New endpoints: GET /v1/export/staff-report.csv (reuses StaffMetrics)
and GET /v1/export/service-report.csv (per-service bookings/cancelled/
revenue, class bookings rolled up). Export buttons on the staff
performance panel and services page.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-18 17:10:15 +02:00
1 parent 2464aa45fa
commit 7b9080eb1c
13 files changed
+541 -33

No files matched your search

+62 -2
View File
@@ -1531,6 +1531,60 @@ paths:
type: string
"404":
description: Tenant membership not found
/v1/export/staff-report.csv:
get:
tags: [Export]
operationId: exportStaffReportCsv
security:
- bearerAuth: []
summary: Export per-staff performance report as CSV
description: >-
Summarized per-staff metrics — bookings, completed, cancelled, booked
and capacity minutes, capacity-based utilization, revenue — over a
trailing window. Same aggregation as `GET /v1/metrics/staff`.
parameters:
- name: days
in: query
schema:
type: integer
default: 30
maximum: 365
responses:
"200":
description: CSV file download
content:
text/csv:
schema:
type: string
"404":
description: Tenant membership not found
/v1/export/service-report.csv:
get:
tags: [Export]
operationId: exportServiceReportCsv
security:
- bearerAuth: []
summary: Export per-service booking report as CSV
description: >-
Bookings, cancellations, and revenue aggregated per catalog service
over a trailing window. Class bookings roll into a final
"class sessions" row when present.
parameters:
- name: days
in: query
schema:
type: integer
default: 30
maximum: 365
responses:
"200":
description: CSV file download
content:
text/csv:
schema:
type: string
"404":
description: Tenant membership not found
/v1/public/calendar/{token}:
get:
tags: [Calendar]
@@ -2219,7 +2273,7 @@ components:
description: Caller's read cursor; absent until the first mark-read
StaffMetric:
type: object
required: [staffId, displayName, bookings, completed, cancelled, utilizationPercent, revenueCents]
required: [staffId, displayName, bookings, completed, cancelled, bookedMinutes, capacityMinutes, utilizationPercent, revenueCents]
properties:
staffId:
type: string
@@ -2232,9 +2286,15 @@ components:
type: integer
cancelled:
type: integer
bookedMinutes:
type: integer
description: Sum of non-cancelled booking durations in the window
capacityMinutes:
type: integer
description: Sum of the member's availability-rule minutes across the window, evaluated on local dates in the tenant timezone
utilizationPercent:
type: integer
description: Share of bookings that stayed confirmed, 0-100
description: bookedMinutes / capacityMinutes as a percentage; 0 when the member has no availability rules
revenueCents:
type: integer
format: int64