- Holiday service with German federal holidays, Easter calculation, DB loading - Deadline calculator adapted from youpc.org (duration calc + non-working day adjustment) - Deadline CRUD service (tenant-scoped: list, create, update, complete, delete) - Deadline rule service (list, filter by proceeding type, hierarchical rule trees) - HTTP handlers for all endpoints with tenant resolution via X-Tenant-ID header - Router wired with all new endpoints under /api/ - Tests for holiday and calculator services (8 passing)
90 lines
2.7 KiB
Go
90 lines
2.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/models"
|
|
"mgit.msbls.de/m/KanzlAI-mGMT/internal/services"
|
|
)
|
|
|
|
// CalculateHandlers holds handlers for deadline calculation endpoints
|
|
type CalculateHandlers struct {
|
|
calculator *services.DeadlineCalculator
|
|
rules *services.DeadlineRuleService
|
|
}
|
|
|
|
// NewCalculateHandlers creates calculate handlers
|
|
func NewCalculateHandlers(calc *services.DeadlineCalculator, rules *services.DeadlineRuleService) *CalculateHandlers {
|
|
return &CalculateHandlers{calculator: calc, rules: rules}
|
|
}
|
|
|
|
// CalculateRequest is the input for POST /api/deadlines/calculate
|
|
type CalculateRequest struct {
|
|
ProceedingType string `json:"proceeding_type"`
|
|
TriggerEventDate string `json:"trigger_event_date"`
|
|
SelectedRuleIDs []string `json:"selected_rule_ids,omitempty"`
|
|
}
|
|
|
|
// Calculate handles POST /api/deadlines/calculate
|
|
func (h *CalculateHandlers) Calculate(w http.ResponseWriter, r *http.Request) {
|
|
var req CalculateRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
|
|
if req.ProceedingType == "" || req.TriggerEventDate == "" {
|
|
writeError(w, http.StatusBadRequest, "proceeding_type and trigger_event_date are required")
|
|
return
|
|
}
|
|
|
|
eventDate, err := time.Parse("2006-01-02", req.TriggerEventDate)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "invalid trigger_event_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
var results []services.CalculatedDeadline
|
|
|
|
if len(req.SelectedRuleIDs) > 0 {
|
|
ruleModels, err := h.rules.GetByIDs(req.SelectedRuleIDs)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "failed to fetch selected rules")
|
|
return
|
|
}
|
|
results = h.calculator.CalculateFromRules(eventDate, ruleModels)
|
|
} else {
|
|
tree, err := h.rules.GetRuleTree(req.ProceedingType)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadRequest, "unknown proceeding type")
|
|
return
|
|
}
|
|
// Flatten tree to get all rule models
|
|
var flatNodes []services.RuleTreeNode
|
|
flattenTree(tree, &flatNodes)
|
|
|
|
ruleModels := make([]models.DeadlineRule, 0, len(flatNodes))
|
|
for _, node := range flatNodes {
|
|
ruleModels = append(ruleModels, node.DeadlineRule)
|
|
}
|
|
results = h.calculator.CalculateFromRules(eventDate, ruleModels)
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"proceeding_type": req.ProceedingType,
|
|
"trigger_event_date": req.TriggerEventDate,
|
|
"deadlines": results,
|
|
})
|
|
}
|
|
|
|
func flattenTree(nodes []services.RuleTreeNode, result *[]services.RuleTreeNode) {
|
|
for _, n := range nodes {
|
|
*result = append(*result, n)
|
|
if len(n.Children) > 0 {
|
|
flattenTree(n.Children, result)
|
|
}
|
|
}
|
|
}
|