Files
paliad/internal/services/deadline_calculator.go
m d72990ad1b feat(t-paliad-122): country+regime aware HolidayService + CourtService
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.
2026-05-06 12:47:12 +02:00

90 lines
2.7 KiB
Go

package services
import (
"time"
"mgit.msbls.de/m/paliad/internal/models"
)
// CalculatedDeadline is one computed deadline (rule + due date + adjustment info).
type CalculatedDeadline struct {
RuleCode string `json:"rule_code"`
RuleID string `json:"rule_id"`
Title string `json:"title"`
DueDate string `json:"due_date"` // YYYY-MM-DD, after holiday/weekend adjust
OriginalDueDate string `json:"original_due_date"` // YYYY-MM-DD, before adjust
WasAdjusted bool `json:"was_adjusted"`
}
// DeadlineCalculator turns rules into dates given a trigger event.
type DeadlineCalculator struct {
holidays *HolidayService
}
// NewDeadlineCalculator wires the calculator to the holiday service.
func NewDeadlineCalculator(holidays *HolidayService) *DeadlineCalculator {
return &DeadlineCalculator{holidays: holidays}
}
// CalculateEndDate applies a single rule's duration + timing to the event date,
// then bumps forward off non-working days for the given (country, regime).
// Returns (adjusted, original, didAdjust).
func (c *DeadlineCalculator) CalculateEndDate(eventDate time.Time, rule models.DeadlineRule, country, regime string) (time.Time, time.Time, bool) {
endDate := eventDate
timing := "after"
if rule.Timing != nil {
timing = *rule.Timing
}
sign := 1
if timing == "before" {
sign = -1
}
switch rule.DurationUnit {
case "days":
endDate = endDate.AddDate(0, 0, sign*rule.DurationValue)
case "weeks":
endDate = endDate.AddDate(0, 0, sign*rule.DurationValue*7)
case "months":
endDate = endDate.AddDate(0, sign*rule.DurationValue, 0)
}
original := endDate
adjusted, _, wasAdjusted := c.holidays.AdjustForNonWorkingDays(endDate, country, regime)
return adjusted, original, wasAdjusted
}
// CalculateFromRules calculates deadlines for a slice of rules using the
// given (country, regime) for non-working-day adjustment. Rules with
// duration == 0 (court-set hearings, decisions) return the event date itself.
func (c *DeadlineCalculator) CalculateFromRules(eventDate time.Time, rules []models.DeadlineRule, country, regime string) []CalculatedDeadline {
results := make([]CalculatedDeadline, 0, len(rules))
for _, r := range rules {
var adjusted, original time.Time
var wasAdjusted bool
if r.DurationValue > 0 {
adjusted, original, wasAdjusted = c.CalculateEndDate(eventDate, r, country, regime)
} else {
adjusted, original = eventDate, eventDate
}
code := ""
if r.Code != nil {
code = *r.Code
}
results = append(results, CalculatedDeadline{
RuleCode: code,
RuleID: r.ID.String(),
Title: r.Name,
DueDate: adjusted.Format("2006-01-02"),
OriginalDueDate: original.Format("2006-01-02"),
WasAdjusted: wasAdjusted,
})
}
return results
}