Files
paliad/internal/handlers/event_trigger.go
mAi 7bfec310a0 feat(t-paliad-187): POST /api/tools/event-trigger handler + wiring
Phase 3 Slice 6 handler. Decodes JSON body (eventTypeId, conceptId,
triggerDate, flags, courtId, perspective), validates required
fields (triggerDate + at least one identifier), parses UUIDs (400
on malformed), delegates to EventTriggerService.Trigger, surfaces
ErrInvalidInput as 400 with the service's German user-facing
message.

Wiring:

  - dbServices gains an eventTrigger pointer (handlers package
    internal type) wired from handlers.Services.EventTrigger.
  - handlers.Services.EventTrigger is the new exported field; the
    bundle constructor in main.go fills it from
    NewEventTriggerService(pool, rules, holidays, courts).
  - Route registered as POST /api/tools/event-trigger on the
    protected mux, sibling to the existing /api/tools/fristenrechner
    and /api/tools/event-deadlines endpoints.

Returns 503 when DATABASE_URL is unset (matches every other
calculator endpoint's behaviour). Returns same JSON shape as
/api/tools/fristenrechner so the frontend can render with the
existing timeline renderer.
2026-05-15 01:09:20 +02:00

107 lines
3.5 KiB
Go

package handlers
import (
"encoding/json"
"errors"
"net/http"
"github.com/google/uuid"
"mgit.msbls.de/m/paliad/internal/services"
)
// POST /api/tools/event-trigger — Phase 3 Slice 6 (t-paliad-187, design
// §5). Discovers and computes deadline rules triggered by an event-type
// and/or a deadline-concept. Caller passes UUID identifiers (not the
// legacy /api/tools/event-deadlines bigint trigger_event_id surface);
// service handles the bridge to Pipeline-C rules internally.
//
// Body:
//
// {
// "eventTypeId": "uuid", // optional — fires Pipeline-C rules via event_types.trigger_event_id
// "conceptId": "uuid", // optional — fires Pipeline-A rules linked by concept_id FK
// "triggerDate": "2026-01-15", // required, YYYY-MM-DD
// "flags": ["with_ccr"], // optional, gates rules via evalConditionExpr
// "courtId": "upc-ld-mn", // optional, picks (country, regime) for non-working-day arithmetic
// "perspective": "claimant" // optional, drops opposing-side rules
// }
//
// At least one of eventTypeId / conceptId must be set. When both are
// set, the rule set is the UNION deduped by rule.id.
//
// Response: same shape as POST /api/tools/fristenrechner (UIResponse) —
// the frontend can render with the existing timeline renderer.
//
// Returns 503 when the DB pool is unavailable (server bootstrap before
// services attached); the page itself still renders since it's static
// HTML so a downstream error pop-up is the worst the user sees.
func handleEventTriggerCalculate(w http.ResponseWriter, r *http.Request) {
if dbSvc == nil || dbSvc.eventTrigger == nil {
writeJSON(w, http.StatusServiceUnavailable, map[string]string{
"error": "Event-Trigger ist vorübergehend nicht verfügbar (keine Datenbank).",
})
return
}
var req struct {
EventTypeID string `json:"eventTypeId,omitempty"`
ConceptID string `json:"conceptId,omitempty"`
TriggerDate string `json:"triggerDate"`
Flags []string `json:"flags,omitempty"`
CourtID string `json:"courtId,omitempty"`
Perspective string `json:"perspective,omitempty"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Ungültige Anfrage"})
return
}
if req.TriggerDate == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "triggerDate ist erforderlich"})
return
}
if req.EventTypeID == "" && req.ConceptID == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "eventTypeId oder conceptId ist erforderlich",
})
return
}
input := services.EventTriggerInput{
TriggerDate: req.TriggerDate,
Flags: req.Flags,
CourtID: req.CourtID,
Perspective: req.Perspective,
}
if req.EventTypeID != "" {
id, err := uuid.Parse(req.EventTypeID)
if err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "eventTypeId ist keine gültige UUID",
})
return
}
input.EventTypeID = &id
}
if req.ConceptID != "" {
id, err := uuid.Parse(req.ConceptID)
if err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "conceptId ist keine gültige UUID",
})
return
}
input.ConceptID = &id
}
resp, err := dbSvc.eventTrigger.Trigger(r.Context(), input)
if err != nil {
if errors.Is(err, services.ErrInvalidInput) {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, resp)
}