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.
107 lines
3.5 KiB
Go
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)
|
|
}
|