Database: time_entries, billing_rates, invoices tables with RLS.
Backend: CRUD services+handlers for time entries, billing rates, invoices.
- Time entries: list/create/update/delete, summary by case/user/month
- Billing rates: upsert with auto-close previous, current rate lookup
- Invoices: create with auto-number (RE-YYYY-NNN), status transitions
(draft->sent->paid, cancellation), link time entries on invoice create
API: 11 new endpoints under /api/time-entries, /api/billing-rates, /api/invoices
Frontend: Zeiterfassung tab on case detail, /abrechnung overview with filters,
/abrechnung/rechnungen list+detail with status actions, billing rates settings
Also: resolved merge conflicts between audit-trail and role-based branches,
added missing types (Notification, AuditLogResponse, NotificationPreferences)
67 lines
1.6 KiB
Go
67 lines
1.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
|
|
)
|
|
|
|
type BillingRateHandler struct {
|
|
svc *services.BillingRateService
|
|
}
|
|
|
|
func NewBillingRateHandler(svc *services.BillingRateService) *BillingRateHandler {
|
|
return &BillingRateHandler{svc: svc}
|
|
}
|
|
|
|
// List handles GET /api/billing-rates
|
|
func (h *BillingRateHandler) List(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
rates, err := h.svc.List(r.Context(), tenantID)
|
|
if err != nil {
|
|
internalError(w, "failed to list billing rates", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{"billing_rates": rates})
|
|
}
|
|
|
|
// Upsert handles PUT /api/billing-rates
|
|
func (h *BillingRateHandler) Upsert(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
var input services.UpsertBillingRateInput
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid JSON body")
|
|
return
|
|
}
|
|
|
|
if input.Rate < 0 {
|
|
writeError(w, http.StatusBadRequest, "rate must be non-negative")
|
|
return
|
|
}
|
|
if input.ValidFrom == "" {
|
|
writeError(w, http.StatusBadRequest, "valid_from is required")
|
|
return
|
|
}
|
|
|
|
rate, err := h.svc.Upsert(r.Context(), tenantID, input)
|
|
if err != nil {
|
|
internalError(w, "failed to upsert billing rate", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, rate)
|
|
}
|