feat(t-paliad-191): CalcOptions.RuleOverrides + applyRuleOverrides
Phase 3 Slice 11a calculator hook for the rule-editor preview (design §4.5, Q-H-4 option (a)). CalcOptions gains RuleOverrides []models.DeadlineRule. When non-empty, FristenrechnerService.Calculate substitutes any rule with matching .ID in the rule list with the override row, and appends overrides whose ID doesn't match an existing rule (net-new drafts the editor wants to preview). Wired into: - FristenrechnerService.Calculate (proceeding-tree path) - FristenrechnerService.calculateByTriggerEvent (Pipeline-C path) Helper: applyRuleOverrides(src, overrides) — small linear scan since the override slice is 1 row in practice (the draft being previewed). Empty overrides → pass-through (existing behaviour unchanged). No DB writes; pure simulation. The editor's "what would this rule do?" affordance uses this to preview the draft against the rest of the proceeding's rules without mutating the live corpus.
This commit is contained in:
@@ -141,6 +141,17 @@ type CalcOptions struct {
|
||||
// matches paliad.trigger_events.id (bigint, mig 028). See design
|
||||
// §3.D (calculator unification).
|
||||
TriggerEventIDFilter *int64
|
||||
// RuleOverrides substitutes specific rules in the calculator's
|
||||
// rule list with caller-supplied in-memory rows. Used by the
|
||||
// rule-editor preview (Slice 11a, t-paliad-191): the admin's
|
||||
// draft replaces its published peer (matched by rule.ID) so the
|
||||
// editor sees "what would this rule do?" without writing to the
|
||||
// DB. Net-new drafts (no draft_of peer) get appended to the rule
|
||||
// list so their effect lights up on a fresh evaluation.
|
||||
//
|
||||
// Empty / nil = no override (default). Overrides apply equally to
|
||||
// the proceeding-tree and trigger-event branches.
|
||||
RuleOverrides []models.DeadlineRule
|
||||
}
|
||||
|
||||
// Calculate renders the full UI timeline for a proceeding type + trigger date.
|
||||
@@ -240,6 +251,9 @@ func (s *FristenrechnerService) Calculate(ctx context.Context, proceedingCode, t
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(opts.RuleOverrides) > 0 {
|
||||
rules = applyRuleOverrides(rules, opts.RuleOverrides)
|
||||
}
|
||||
|
||||
// Walk the rule list in sequence_order (already sorted by the query) and
|
||||
// compute each entry, keeping a code→date map so RelativeTo / parent_id
|
||||
@@ -969,6 +983,43 @@ func wireFlagsFromPriority(priority string) (isMandatory, isOptional bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// applyRuleOverrides replaces rules whose ID appears in `overrides`
|
||||
// with the override row, and appends any override whose ID isn't in
|
||||
// the source list (net-new drafts the rule editor wants to preview).
|
||||
//
|
||||
// Used by the Slice 11a (t-paliad-191) preview endpoint: the editor
|
||||
// passes the draft as an override so Calculate runs against the
|
||||
// proposed shape without writing to the DB. Empty overrides slice =
|
||||
// pass-through (Calculate's existing behaviour for non-preview
|
||||
// callers). The override slice is small (1 row in practice — the
|
||||
// draft being previewed) so the linear scan is fine.
|
||||
func applyRuleOverrides(src, overrides []models.DeadlineRule) []models.DeadlineRule {
|
||||
if len(overrides) == 0 {
|
||||
return src
|
||||
}
|
||||
byID := make(map[uuid.UUID]models.DeadlineRule, len(overrides))
|
||||
for _, o := range overrides {
|
||||
byID[o.ID] = o
|
||||
}
|
||||
out := make([]models.DeadlineRule, 0, len(src)+len(overrides))
|
||||
seen := make(map[uuid.UUID]bool, len(overrides))
|
||||
for _, r := range src {
|
||||
if ov, ok := byID[r.ID]; ok {
|
||||
out = append(out, ov)
|
||||
seen[ov.ID] = true
|
||||
continue
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
for _, o := range overrides {
|
||||
if seen[o.ID] {
|
||||
continue
|
||||
}
|
||||
out = append(out, o)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// applyDuration is the unified date-arithmetic helper used by every
|
||||
// calculator path (Pipeline-A proceeding-tree, Pipeline-C trigger-event,
|
||||
// CalculateRule single-rule). Phase 3 Slice 4 (t-paliad-185) replaces
|
||||
@@ -1071,6 +1122,9 @@ func (s *FristenrechnerService) calculateByTriggerEvent(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(opts.RuleOverrides) > 0 {
|
||||
rules = applyRuleOverrides(rules, opts.RuleOverrides)
|
||||
}
|
||||
|
||||
deadlines := make([]UIDeadline, 0, len(rules))
|
||||
for _, r := range rules {
|
||||
|
||||
Reference in New Issue
Block a user