Full event-driven deadline determination system ported from youpc.org:
Backend:
- DetermineService: walks proceeding event tree, calculates cascading
dates with holiday adjustment and conditional logic
- GET /api/proceeding-types/{code}/timeline — full event tree structure
- POST /api/deadlines/determine — calculate timeline with conditions
- POST /api/cases/{caseID}/deadlines/batch — batch-create deadlines
- DeadlineRule model: added is_spawn, spawn_label fields
- GetFullTimeline: recursive CTE following cross-type spawn branches
- Conditional deadlines: condition_rule_id toggles alt_duration/rule_code
(e.g. Reply changes from RoP.029b to RoP.029a when CCR is filed)
- Seed SQL with full UPC event trees (INF, REV, CCR, APM, APP, AMD)
Frontend:
- DeadlineWizard: interactive proceeding timeline with step-by-step flow
1. Select proceeding type (visual cards)
2. Enter trigger event date
3. Toggle conditional branches (CCR, Appeal, Amend)
4. See full calculated timeline with color-coded urgency
5. Batch-create all deadlines on a selected case
- Visual timeline tree with party icons, rule codes, duration badges
- Kept existing DeadlineCalculator as "Schnell" quick mode
Also resolved merge conflicts across 6 files (auth, router, handlers)
merging role-based permissions + audit trail features.
208 lines
5.3 KiB
Go
208 lines
5.3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/auth"
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
|
|
)
|
|
|
|
// DeadlineHandlers holds handlers for deadline CRUD endpoints
|
|
type DeadlineHandlers struct {
|
|
deadlines *services.DeadlineService
|
|
}
|
|
|
|
// NewDeadlineHandlers creates deadline handlers
|
|
func NewDeadlineHandlers(ds *services.DeadlineService) *DeadlineHandlers {
|
|
return &DeadlineHandlers{deadlines: ds}
|
|
}
|
|
|
|
// Get handles GET /api/deadlines/{deadlineID}
|
|
func (h *DeadlineHandlers) Get(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
deadlineID, err := parsePathUUID(r, "deadlineID")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid deadline ID")
|
|
return
|
|
}
|
|
|
|
deadline, err := h.deadlines.GetByID(tenantID, deadlineID)
|
|
if err != nil {
|
|
internalError(w, "failed to fetch deadline", err)
|
|
return
|
|
}
|
|
if deadline == nil {
|
|
writeError(w, http.StatusNotFound, "deadline not found")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, deadline)
|
|
}
|
|
|
|
// ListAll handles GET /api/deadlines
|
|
func (h *DeadlineHandlers) ListAll(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
deadlines, err := h.deadlines.ListAll(tenantID)
|
|
if err != nil {
|
|
internalError(w, "failed to list deadlines", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, deadlines)
|
|
}
|
|
|
|
// ListForCase handles GET /api/cases/{caseID}/deadlines
|
|
func (h *DeadlineHandlers) ListForCase(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
caseID, err := parsePathUUID(r, "caseID")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid case ID")
|
|
return
|
|
}
|
|
|
|
deadlines, err := h.deadlines.ListForCase(tenantID, caseID)
|
|
if err != nil {
|
|
internalError(w, "failed to list deadlines for case", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, deadlines)
|
|
}
|
|
|
|
// Create handles POST /api/cases/{caseID}/deadlines
|
|
func (h *DeadlineHandlers) Create(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
caseID, err := parsePathUUID(r, "caseID")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid case ID")
|
|
return
|
|
}
|
|
|
|
var input services.CreateDeadlineInput
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
input.CaseID = caseID
|
|
|
|
if input.Title == "" || input.DueDate == "" {
|
|
writeError(w, http.StatusBadRequest, "title and due_date are required")
|
|
return
|
|
}
|
|
if msg := validateStringLength("title", input.Title, maxTitleLen); msg != "" {
|
|
writeError(w, http.StatusBadRequest, msg)
|
|
return
|
|
}
|
|
|
|
deadline, err := h.deadlines.Create(r.Context(), tenantID, input)
|
|
if err != nil {
|
|
internalError(w, "failed to create deadline", err)
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusCreated, deadline)
|
|
}
|
|
|
|
// Update handles PUT /api/deadlines/{deadlineID}
|
|
func (h *DeadlineHandlers) Update(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
deadlineID, err := parsePathUUID(r, "deadlineID")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid deadline ID")
|
|
return
|
|
}
|
|
|
|
var input services.UpdateDeadlineInput
|
|
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
|
|
deadline, err := h.deadlines.Update(r.Context(), tenantID, deadlineID, input)
|
|
if err != nil {
|
|
internalError(w, "failed to update deadline", err)
|
|
return
|
|
}
|
|
if deadline == nil {
|
|
writeError(w, http.StatusNotFound, "deadline not found")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, deadline)
|
|
}
|
|
|
|
// Complete handles PATCH /api/deadlines/{deadlineID}/complete
|
|
func (h *DeadlineHandlers) Complete(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
deadlineID, err := parsePathUUID(r, "deadlineID")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid deadline ID")
|
|
return
|
|
}
|
|
|
|
deadline, err := h.deadlines.Complete(r.Context(), tenantID, deadlineID)
|
|
if err != nil {
|
|
internalError(w, "failed to complete deadline", err)
|
|
return
|
|
}
|
|
if deadline == nil {
|
|
writeError(w, http.StatusNotFound, "deadline not found")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, deadline)
|
|
}
|
|
|
|
// Delete handles DELETE /api/deadlines/{deadlineID}
|
|
func (h *DeadlineHandlers) Delete(w http.ResponseWriter, r *http.Request) {
|
|
tenantID, ok := auth.TenantFromContext(r.Context())
|
|
if !ok {
|
|
writeError(w, http.StatusForbidden, "missing tenant")
|
|
return
|
|
}
|
|
|
|
deadlineID, err := parsePathUUID(r, "deadlineID")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid deadline ID")
|
|
return
|
|
}
|
|
|
|
if err := h.deadlines.Delete(r.Context(), tenantID, deadlineID); err != nil {
|
|
writeError(w, http.StatusNotFound, "deadline not found")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]string{"status": "deleted"})
|
|
}
|