package handlers import ( "encoding/json" "net/http" "mgit.msbls.de/m/KanzlAI-mGMT/internal/auth" "mgit.msbls.de/m/KanzlAI-mGMT/internal/services" "github.com/google/uuid" ) type InvoiceHandler struct { svc *services.InvoiceService } func NewInvoiceHandler(svc *services.InvoiceService) *InvoiceHandler { return &InvoiceHandler{svc: svc} } // List handles GET /api/invoices?case_id=&status= func (h *InvoiceHandler) List(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } var caseID *uuid.UUID if caseStr := r.URL.Query().Get("case_id"); caseStr != "" { parsed, err := uuid.Parse(caseStr) if err != nil { writeError(w, http.StatusBadRequest, "invalid case_id") return } caseID = &parsed } invoices, err := h.svc.List(r.Context(), tenantID, caseID, r.URL.Query().Get("status")) if err != nil { internalError(w, "failed to list invoices", err) return } writeJSON(w, http.StatusOK, map[string]any{"invoices": invoices}) } // Get handles GET /api/invoices/{id} func (h *InvoiceHandler) Get(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } invoiceID, err := parsePathUUID(r, "id") if err != nil { writeError(w, http.StatusBadRequest, "invalid invoice ID") return } inv, err := h.svc.GetByID(r.Context(), tenantID, invoiceID) if err != nil { internalError(w, "failed to get invoice", err) return } if inv == nil { writeError(w, http.StatusNotFound, "invoice not found") return } writeJSON(w, http.StatusOK, inv) } // Create handles POST /api/invoices func (h *InvoiceHandler) Create(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } userID, _ := auth.UserFromContext(r.Context()) var input services.CreateInvoiceInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON body") return } if input.ClientName == "" { writeError(w, http.StatusBadRequest, "client_name is required") return } if input.CaseID == uuid.Nil { writeError(w, http.StatusBadRequest, "case_id is required") return } inv, err := h.svc.Create(r.Context(), tenantID, userID, input) if err != nil { internalError(w, "failed to create invoice", err) return } writeJSON(w, http.StatusCreated, inv) } // Update handles PUT /api/invoices/{id} func (h *InvoiceHandler) Update(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } invoiceID, err := parsePathUUID(r, "id") if err != nil { writeError(w, http.StatusBadRequest, "invalid invoice ID") return } var input services.UpdateInvoiceInput if err := json.NewDecoder(r.Body).Decode(&input); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON body") return } inv, err := h.svc.Update(r.Context(), tenantID, invoiceID, input) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusOK, inv) } // UpdateStatus handles PATCH /api/invoices/{id}/status func (h *InvoiceHandler) UpdateStatus(w http.ResponseWriter, r *http.Request) { tenantID, ok := auth.TenantFromContext(r.Context()) if !ok { writeError(w, http.StatusForbidden, "missing tenant") return } invoiceID, err := parsePathUUID(r, "id") if err != nil { writeError(w, http.StatusBadRequest, "invalid invoice ID") return } var body struct { Status string `json:"status"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON body") return } if body.Status == "" { writeError(w, http.StatusBadRequest, "status is required") return } inv, err := h.svc.UpdateStatus(r.Context(), tenantID, invoiceID, body.Status) if err != nil { writeError(w, http.StatusBadRequest, err.Error()) return } writeJSON(w, http.StatusOK, inv) }