Backend:
- ReportingService with aggregation queries (CTEs, FILTER clauses)
- 4 API endpoints: /api/reports/{cases,deadlines,workload,billing}
- Date range filtering via ?from=&to= query params
Frontend:
- /berichte page with 4 tabs: Akten, Fristen, Auslastung, Abrechnung
- recharts: bar/pie/line charts for all report types
- Date range picker, CSV export, print-friendly view
- Sidebar nav entry with BarChart3 icon
Also resolves merge conflicts between role-based, notification, and
audit trail branches, and adds missing TS types (AuditLogResponse,
Notification, NotificationPreferences).
110 lines
2.6 KiB
Go
110 lines
2.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
|
|
)
|
|
|
|
type ReportHandler struct {
|
|
svc *services.ReportingService
|
|
}
|
|
|
|
func NewReportHandler(svc *services.ReportingService) *ReportHandler {
|
|
return &ReportHandler{svc: svc}
|
|
}
|
|
|
|
// parseDateRange extracts from/to query params, defaulting to last 12 months.
|
|
func parseDateRange(r *http.Request) (time.Time, time.Time) {
|
|
now := time.Now()
|
|
from := time.Date(now.Year()-1, now.Month(), 1, 0, 0, 0, 0, time.UTC)
|
|
to := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, time.UTC)
|
|
|
|
if v := r.URL.Query().Get("from"); v != "" {
|
|
if t, err := time.Parse("2006-01-02", v); err == nil {
|
|
from = t
|
|
}
|
|
}
|
|
if v := r.URL.Query().Get("to"); v != "" {
|
|
if t, err := time.Parse("2006-01-02", v); err == nil {
|
|
to = time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, time.UTC)
|
|
}
|
|
}
|
|
|
|
return from, to
|
|
}
|
|
|
|
func (h *ReportHandler) Cases(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
from, to := parseDateRange(r)
|
|
|
|
data, err := h.svc.CaseReport(r.Context(), tenantID, from, to)
|
|
if err != nil {
|
|
internalError(w, "failed to generate case report", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, data)
|
|
}
|
|
|
|
func (h *ReportHandler) Deadlines(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
from, to := parseDateRange(r)
|
|
|
|
data, err := h.svc.DeadlineReport(r.Context(), tenantID, from, to)
|
|
if err != nil {
|
|
internalError(w, "failed to generate deadline report", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, data)
|
|
}
|
|
|
|
func (h *ReportHandler) Workload(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
from, to := parseDateRange(r)
|
|
|
|
data, err := h.svc.WorkloadReport(r.Context(), tenantID, from, to)
|
|
if err != nil {
|
|
internalError(w, "failed to generate workload report", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, data)
|
|
}
|
|
|
|
func (h *ReportHandler) Billing(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
from, to := parseDateRange(r)
|
|
|
|
data, err := h.svc.BillingReport(r.Context(), tenantID, from, to)
|
|
if err != nil {
|
|
internalError(w, "failed to generate billing report", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, data)
|
|
}
|