mirror of
https://github.com/Dvorinka/Bookra.git
synced 2026-10-28 15:51:15 +00:00
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:
1 parent
2464aa45fa
commit
7b9080eb1c
13 files changed
+541
-33
No files matched your search
@@ -61,7 +61,7 @@ func NewServer(cfg config.Config, pools *db.Pools) (*Server, error) {
|
||||
catalogService := catalog.NewService(repository, billingService, notificationService)
|
||||
calendarService := calendar.NewService(repository)
|
||||
activityService := activity.NewService(repository)
|
||||
exportService := export.NewService(repository)
|
||||
exportService := export.NewService(repository, bookingService)
|
||||
authService := auth.NewService(repository, cfg.AuthJWTSecret)
|
||||
adminService := admin.NewService(repository, cfg.AdminEmail, cfg.AdminKey)
|
||||
smsService := sms.NewService(cfg, repository)
|
||||
@@ -1354,6 +1354,38 @@ func NewServer(cfg config.Config, pools *db.Pools) (*Server, error) {
|
||||
c.Data(http.StatusOK, "text/csv; charset=utf-8", buf.Bytes())
|
||||
})
|
||||
|
||||
protected.GET("/export/staff-report.csv", func(c *gin.Context) {
|
||||
days, _ := strconv.Atoi(c.DefaultQuery("days", "30"))
|
||||
var buf bytes.Buffer
|
||||
err := exportService.WriteStaffReportCSV(c.Request.Context(), auth.PrincipalFromContext(c), &buf, days)
|
||||
if err != nil {
|
||||
status := http.StatusInternalServerError
|
||||
if errors.Is(err, export.ErrTenantMembership) {
|
||||
status = http.StatusNotFound
|
||||
}
|
||||
c.JSON(status, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.Header("Content-Disposition", `attachment; filename="bookra-staff-report.csv"`)
|
||||
c.Data(http.StatusOK, "text/csv; charset=utf-8", buf.Bytes())
|
||||
})
|
||||
|
||||
protected.GET("/export/service-report.csv", func(c *gin.Context) {
|
||||
days, _ := strconv.Atoi(c.DefaultQuery("days", "30"))
|
||||
var buf bytes.Buffer
|
||||
err := exportService.WriteServiceReportCSV(c.Request.Context(), auth.PrincipalFromContext(c), &buf, days)
|
||||
if err != nil {
|
||||
status := http.StatusInternalServerError
|
||||
if errors.Is(err, export.ErrTenantMembership) {
|
||||
status = http.StatusNotFound
|
||||
}
|
||||
c.JSON(status, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.Header("Content-Disposition", `attachment; filename="bookra-service-report.csv"`)
|
||||
c.Data(http.StatusOK, "text/csv; charset=utf-8", buf.Bytes())
|
||||
})
|
||||
|
||||
protected.GET("/billing/subscription", func(c *gin.Context) {
|
||||
response, err := billingService.GetSubscription(c.Request.Context(), auth.PrincipalFromContext(c))
|
||||
if err != nil {
|
||||
|
||||
Reference in new issue
Block a user