feat: add deadline CRUD, calculator, and holiday services (Phase 1C)
- 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)
This commit is contained in:
99
backend/internal/services/deadline_calculator.go
Normal file
99
backend/internal/services/deadline_calculator.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"mgit.msbls.de/m/KanzlAI-mGMT/internal/models"
|
||||
)
|
||||
|
||||
// CalculatedDeadline holds a calculated deadline with adjustment info
|
||||
type CalculatedDeadline struct {
|
||||
RuleCode string `json:"rule_code"`
|
||||
RuleID string `json:"rule_id"`
|
||||
Title string `json:"title"`
|
||||
DueDate string `json:"due_date"`
|
||||
OriginalDueDate string `json:"original_due_date"`
|
||||
WasAdjusted bool `json:"was_adjusted"`
|
||||
}
|
||||
|
||||
// DeadlineCalculator calculates deadlines from rules and event dates
|
||||
type DeadlineCalculator struct {
|
||||
holidays *HolidayService
|
||||
}
|
||||
|
||||
// NewDeadlineCalculator creates a new calculator
|
||||
func NewDeadlineCalculator(holidays *HolidayService) *DeadlineCalculator {
|
||||
return &DeadlineCalculator{holidays: holidays}
|
||||
}
|
||||
|
||||
// CalculateEndDate calculates the end date for a single deadline rule based on an event date.
|
||||
// Adapted from youpc.org CalculateDeadlineEndDate.
|
||||
func (c *DeadlineCalculator) CalculateEndDate(eventDate time.Time, rule models.DeadlineRule) (adjusted time.Time, original time.Time, wasAdjusted bool) {
|
||||
endDate := eventDate
|
||||
|
||||
timing := "after"
|
||||
if rule.Timing != nil {
|
||||
timing = *rule.Timing
|
||||
}
|
||||
|
||||
durationValue := rule.DurationValue
|
||||
durationUnit := rule.DurationUnit
|
||||
|
||||
if timing == "before" {
|
||||
switch durationUnit {
|
||||
case "days":
|
||||
endDate = endDate.AddDate(0, 0, -durationValue)
|
||||
case "weeks":
|
||||
endDate = endDate.AddDate(0, 0, -durationValue*7)
|
||||
case "months":
|
||||
endDate = endDate.AddDate(0, -durationValue, 0)
|
||||
}
|
||||
} else {
|
||||
switch durationUnit {
|
||||
case "days":
|
||||
endDate = endDate.AddDate(0, 0, durationValue)
|
||||
case "weeks":
|
||||
endDate = endDate.AddDate(0, 0, durationValue*7)
|
||||
case "months":
|
||||
endDate = endDate.AddDate(0, durationValue, 0)
|
||||
}
|
||||
}
|
||||
|
||||
original = endDate
|
||||
adjusted, _, wasAdjusted = c.holidays.AdjustForNonWorkingDays(endDate)
|
||||
return adjusted, original, wasAdjusted
|
||||
}
|
||||
|
||||
// CalculateFromRules calculates deadlines for a set of rules given an event date.
|
||||
// Returns a list of calculated deadlines with due dates.
|
||||
func (c *DeadlineCalculator) CalculateFromRules(eventDate time.Time, rules []models.DeadlineRule) []CalculatedDeadline {
|
||||
results := make([]CalculatedDeadline, 0, len(rules))
|
||||
|
||||
for _, rule := range rules {
|
||||
var adjusted, original time.Time
|
||||
var wasAdjusted bool
|
||||
|
||||
if rule.DurationValue > 0 {
|
||||
adjusted, original, wasAdjusted = c.CalculateEndDate(eventDate, rule)
|
||||
} else {
|
||||
adjusted = eventDate
|
||||
original = eventDate
|
||||
}
|
||||
|
||||
code := ""
|
||||
if rule.Code != nil {
|
||||
code = *rule.Code
|
||||
}
|
||||
|
||||
results = append(results, CalculatedDeadline{
|
||||
RuleCode: code,
|
||||
RuleID: rule.ID.String(),
|
||||
Title: rule.Name,
|
||||
DueDate: adjusted.Format("2006-01-02"),
|
||||
OriginalDueDate: original.Format("2006-01-02"),
|
||||
WasAdjusted: wasAdjusted,
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
Reference in New Issue
Block a user