Holiday struct gains Country (ISO-3166) + Regime ('UPC' | 'EPO' | "")
fields. AppliesTo(country, regime) is the matching rule the new lookup
methods filter through: a row matches when its Country equals the
court's country OR its Regime equals the court's regime. UPC LD München
(DE+UPC) sees DE federal + UPC vacations; LG München (DE+"") sees only
DE federal; UPC LD Paris (FR+UPC) sees FR + UPC. germanFederalHolidays
fallback now country-tagged 'DE' so the per-country filter applies it
only to DE-jurisdictional callers.
Public service methods (IsHoliday, IsNonWorkingDay, AdjustForNonWorking
Days, AdjustForNonWorkingDaysWithReason, findVacationBlock) all take
(country, regime). Cache stays year-keyed — single DB hit per year, all
courts touching that year share it.
New CourtService loads paliad.courts once + answers Lookup(id),
CountryRegime(id, defaultCountry, defaultRegime), All(), ByCourtType(t).
FristenrechnerService.CalcOptions / CalcRuleParams gain CourtID;
EventDeadlineService.Calculate gains courtID. When courtID is empty,
DefaultsForJurisdiction maps the proceeding's existing jurisdiction
column to a sensible (country, regime) default — UPC proceedings get
(DE, UPC), everything else gets DE-only — preserving today's behaviour
for callers that don't yet send a court.
Tests: new TestAppliesTo_CountryRegimeFilter + TestAppliesTo_Rules
cover the cross-product of (DE court / UPC LD München / UPC LD Paris /
LG München) × (DE federal / UPC vacation / FR holiday). Existing tests
threaded through with ('DE', 'UPC') to preserve behaviour they were
written to lock.
107 lines
3.4 KiB
Go
107 lines
3.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"mgit.msbls.de/m/paliad/internal/services"
|
|
)
|
|
|
|
// GET /api/deadline-rules?proceeding_type_id=N
|
|
//
|
|
// Lists deadline rules from the DB, optionally filtered by proceeding type.
|
|
// Returns 503 if the DB is not configured.
|
|
func handleListDeadlineRules(w http.ResponseWriter, r *http.Request) {
|
|
if !requireDB(w) {
|
|
return
|
|
}
|
|
var ptIDPtr *int
|
|
if raw := r.URL.Query().Get("proceeding_type_id"); raw != "" {
|
|
ptID, err := strconv.Atoi(raw)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid proceeding_type_id"})
|
|
return
|
|
}
|
|
ptIDPtr = &ptID
|
|
}
|
|
rules, err := dbSvc.rules.List(r.Context(), ptIDPtr)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to list rules"})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, rules)
|
|
}
|
|
|
|
// GET /api/proceeding-types-db
|
|
//
|
|
// Lists active proceeding types from the DB.
|
|
// (Distinct route name from the existing in-memory /api/tools/proceeding-types
|
|
// endpoint to avoid path conflicts during the Phase B → Phase C transition.)
|
|
func handleListProceedingTypesDB(w http.ResponseWriter, r *http.Request) {
|
|
if !requireDB(w) {
|
|
return
|
|
}
|
|
types, err := dbSvc.rules.ListProceedingTypes(r.Context())
|
|
if err != nil {
|
|
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to list proceeding types"})
|
|
return
|
|
}
|
|
writeJSON(w, http.StatusOK, types)
|
|
}
|
|
|
|
// POST /api/deadlines/calculate
|
|
//
|
|
// Body: { "proceeding_type": "INF", "trigger_date": "2026-04-15" }
|
|
// Calculates all deadlines for the proceeding type's rule tree, applying
|
|
// holiday/weekend adjustment via the DB-backed HolidayService.
|
|
//
|
|
// Lives at /api/deadlines/calculate (vs the existing /api/tools/fristenrechner
|
|
// which uses the in-memory rule tree). Phase C swaps the Fristenrechner UI
|
|
// to this endpoint, then deletes the in-memory rule tree.
|
|
func handleCalculateDeadlines(w http.ResponseWriter, r *http.Request) {
|
|
if !requireDB(w) {
|
|
return
|
|
}
|
|
var input struct {
|
|
ProceedingType string `json:"proceeding_type"`
|
|
TriggerDate string `json:"trigger_date"`
|
|
CourtID string `json:"court_id"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
|
|
return
|
|
}
|
|
if input.ProceedingType == "" || input.TriggerDate == "" {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "proceeding_type and trigger_date required"})
|
|
return
|
|
}
|
|
triggerDate, err := time.Parse("2006-01-02", input.TriggerDate)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "trigger_date must be YYYY-MM-DD"})
|
|
return
|
|
}
|
|
|
|
rules, pt, err := dbSvc.rules.GetFullTimeline(r.Context(), input.ProceedingType)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "unknown proceeding type"})
|
|
return
|
|
}
|
|
|
|
defaultCountry, defaultRegime := services.DefaultsForJurisdiction(pt.Jurisdiction)
|
|
country, regime, err := dbSvc.courts.CountryRegime(input.CourtID, defaultCountry, defaultRegime)
|
|
if err != nil {
|
|
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "unknown court_id"})
|
|
return
|
|
}
|
|
|
|
results := dbSvc.calc.CalculateFromRules(triggerDate, rules, country, regime)
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"proceeding_type": pt.Code,
|
|
"proceeding_name": pt.Name,
|
|
"trigger_date": input.TriggerDate,
|
|
"deadlines": results,
|
|
})
|
|
}
|