GET /api/tools/courts[?courtType=UPC-LD] returns the deadline- computation slice of paliad.courts (id, code, names, country, regime, court_type) — distinct from the rich Gerichtsverzeichnis at /api/courts. Optional courtType filter narrows to a single tier. POST /api/tools/fristenrechner and POST /api/tools/fristenrechner/ calculate-rule both accept an optional courtId field. When set, the calculator resolves the court's (country, regime) and uses that calendar; when omitted, the proceeding's existing jurisdiction column seeds a sensible default — preserves today's behaviour for callers that don't yet send a court. Frontend: court-picker-row added to step 2 of the Fristenrechner wizard. Visible only for proceeding types with multiple compatible courts (today: every UPC-flavoured proceeding — UPC LDs span 12 countries, plus UPC CD seats and the CoA). DE-only proceedings (BPatG nullity, BGH appeals, DPMA, EPA, EP grant) keep the form unchanged. Picker re-runs the calc on selection so the user sees the same deadlines shift to a different calendar without a manual click. i18n key deadlines.court.label added for both DE and EN. Default courts wired sensibly: UPC_INF / UPC_REV / UPC_PI etc. → UPC LD München (HLC's home venue); UPC_APP / UPC_APP_ORDERS / UPC_COST_APPEAL → UPC CoA Luxembourg; UPC_REV → UPC CD Paris.
224 lines
8.5 KiB
Go
224 lines
8.5 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"mgit.msbls.de/m/paliad/internal/services"
|
|
)
|
|
|
|
// Fristenrechner page handler: serves the static HTML. No DB dependency.
|
|
func handleFristenrechnerPage(w http.ResponseWriter, r *http.Request) {
|
|
http.ServeFile(w, r, "dist/fristenrechner.html")
|
|
}
|
|
|
|
// POST /api/tools/fristenrechner — calculate the UI timeline for a proceeding.
|
|
//
|
|
// Phase C: routes through FristenrechnerService which pulls rules from
|
|
// paliad.deadline_rules. When DATABASE_URL is unset, returns 503; the page
|
|
// itself still renders because it's static HTML.
|
|
func handleFristenrechnerAPI(w http.ResponseWriter, r *http.Request) {
|
|
if dbSvc == nil || dbSvc.fristenrechner == nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]string{
|
|
"error": "Fristenrechner ist vorübergehend nicht verfügbar (keine Datenbank).",
|
|
})
|
|
return
|
|
}
|
|
var req struct {
|
|
ProceedingType string `json:"proceedingType"`
|
|
TriggerDate string `json:"triggerDate"`
|
|
PriorityDate string `json:"priorityDate,omitempty"`
|
|
Flags []string `json:"flags,omitempty"`
|
|
AnchorOverrides map[string]string `json:"anchorOverrides,omitempty"`
|
|
CourtID string `json:"courtId,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.ProceedingType == "" || req.TriggerDate == "" {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "proceedingType und triggerDate sind erforderlich"})
|
|
return
|
|
}
|
|
|
|
resp, err := dbSvc.fristenrechner.Calculate(r.Context(), req.ProceedingType, req.TriggerDate, services.CalcOptions{
|
|
PriorityDateStr: req.PriorityDate,
|
|
Flags: req.Flags,
|
|
AnchorOverrides: req.AnchorOverrides,
|
|
CourtID: req.CourtID,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, services.ErrUnknownProceedingType) {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "unbekannter Verfahrenstyp: " + req.ProceedingType})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, resp)
|
|
}
|
|
|
|
// POST /api/tools/fristenrechner/calculate-rule — single-rule calc for
|
|
// the v4 (t-paliad-136 Phase B) result-card click flow.
|
|
//
|
|
// Body: { ruleId? } OR { proceedingCode, ruleLocalCode }, plus
|
|
// triggerDate (YYYY-MM-DD, required) and flags? (string array,
|
|
// optional condition_flag inputs).
|
|
//
|
|
// Returns a RuleCalculation (see services.RuleCalculation) — the rule
|
|
// metadata + computed dueDate / originalDate / adjustmentReason. Used by
|
|
// the result-card calc panel; distinct from the full-timeline endpoint
|
|
// at POST /api/tools/fristenrechner.
|
|
func handleFristenrechnerCalculateRule(w http.ResponseWriter, r *http.Request) {
|
|
if dbSvc == nil || dbSvc.fristenrechner == nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]string{
|
|
"error": "Fristenrechner ist vorübergehend nicht verfügbar (keine Datenbank).",
|
|
})
|
|
return
|
|
}
|
|
var req struct {
|
|
RuleID string `json:"ruleId"`
|
|
ProceedingCode string `json:"proceedingCode"`
|
|
RuleLocalCode string `json:"ruleLocalCode"`
|
|
TriggerDate string `json:"triggerDate"`
|
|
Flags []string `json:"flags,omitempty"`
|
|
CourtID string `json:"courtId,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.RuleID == "" && (req.ProceedingCode == "" || req.RuleLocalCode == "") {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{
|
|
"error": "Entweder ruleId oder (proceedingCode + ruleLocalCode) ist erforderlich",
|
|
})
|
|
return
|
|
}
|
|
|
|
resp, err := dbSvc.fristenrechner.CalculateRule(r.Context(), services.CalcRuleParams{
|
|
RuleID: req.RuleID,
|
|
ProceedingCode: req.ProceedingCode,
|
|
RuleLocalCode: req.RuleLocalCode,
|
|
TriggerDate: req.TriggerDate,
|
|
Flags: req.Flags,
|
|
CourtID: req.CourtID,
|
|
})
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, services.ErrUnknownRule):
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "unbekannte Regel"})
|
|
case errors.Is(err, services.ErrUnknownProceedingType):
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "unbekannter Verfahrenstyp: " + req.ProceedingCode})
|
|
default:
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
}
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, resp)
|
|
}
|
|
|
|
// GET /api/tools/proceeding-types — metadata list for the wizard buttons.
|
|
// Returns 503 with an empty array when DATABASE_URL is unset so the page
|
|
// still renders (buttons are server-rendered from tsx and don't depend on
|
|
// this endpoint for existence, only for dynamic list updates).
|
|
func handleProceedingTypes(w http.ResponseWriter, r *http.Request) {
|
|
if dbSvc == nil || dbSvc.fristenrechner == nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]string{
|
|
"error": "Verfahrenstypen vorübergehend nicht verfügbar (keine Datenbank).",
|
|
})
|
|
return
|
|
}
|
|
types, err := dbSvc.fristenrechner.ListFristenrechnerTypes(r.Context())
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "konnte Verfahrenstypen nicht laden"})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, types)
|
|
}
|
|
|
|
// GET /api/tools/trigger-events — list active UPC trigger events for the
|
|
// "Was kommt nach…" mode picker. Sorted alphabetically by name.
|
|
func handleTriggerEventsList(w http.ResponseWriter, r *http.Request) {
|
|
if dbSvc == nil || dbSvc.eventDeadline == nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]string{
|
|
"error": "Trigger-Ereignisse vorübergehend nicht verfügbar (keine Datenbank).",
|
|
})
|
|
return
|
|
}
|
|
events, err := dbSvc.eventDeadline.ListTriggerEvents(r.Context())
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "konnte Trigger-Ereignisse nicht laden"})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, events)
|
|
}
|
|
|
|
// POST /api/tools/event-deadlines — compute all deadlines flowing from a
|
|
// trigger event + date. Body: {"triggerEventId": <int>, "triggerDate": "YYYY-MM-DD"}.
|
|
func handleEventDeadlinesCalculate(w http.ResponseWriter, r *http.Request) {
|
|
if dbSvc == nil || dbSvc.eventDeadline == nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]string{
|
|
"error": "Fristenrechner ist vorübergehend nicht verfügbar (keine Datenbank).",
|
|
})
|
|
return
|
|
}
|
|
var req struct {
|
|
TriggerEventID int64 `json:"triggerEventId"`
|
|
TriggerDate string `json:"triggerDate"`
|
|
CourtID string `json:"courtId"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "Ungültige Anfrage"})
|
|
return
|
|
}
|
|
if req.TriggerEventID <= 0 || req.TriggerDate == "" {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "triggerEventId und triggerDate sind erforderlich"})
|
|
return
|
|
}
|
|
resp, err := dbSvc.eventDeadline.Calculate(r.Context(), req.TriggerEventID, req.TriggerDate, req.CourtID)
|
|
if err != nil {
|
|
if errors.Is(err, services.ErrUnknownTriggerEvent) {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "unbekanntes Trigger-Ereignis"})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, resp)
|
|
}
|
|
|
|
// GET /api/tools/courts — list active courts for the Fristenrechner court
|
|
// picker. Optional ?courtType=UPC-LD filter narrows to a single tier so the
|
|
// UI can render only the courts compatible with the selected proceeding.
|
|
// Returns the deadline-computation slice (id, code, names, country, regime,
|
|
// court_type, sort_order) — NOT the full Gerichtsverzeichnis catalog. The
|
|
// rich addresses / phone / languages payload still lives at /api/courts.
|
|
func handleCourtsList(w http.ResponseWriter, r *http.Request) {
|
|
if dbSvc == nil || dbSvc.courts == nil {
|
|
writeJSON(w, http.StatusServiceUnavailable, map[string]string{
|
|
"error": "Gerichte vorübergehend nicht verfügbar (keine Datenbank).",
|
|
})
|
|
return
|
|
}
|
|
courtType := r.URL.Query().Get("courtType")
|
|
var (
|
|
courts []services.Court
|
|
err error
|
|
)
|
|
if courtType != "" {
|
|
courts, err = dbSvc.courts.ByCourtType(courtType)
|
|
} else {
|
|
courts, err = dbSvc.courts.All()
|
|
}
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "konnte Gerichte nicht laden"})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, courts)
|
|
}
|