Three correctness bugs from the t-paliad-101 QA sweep, fixed together since
they all change displayed/saved numbers users rely on.
B1 — Kostenrechner UPC GESAMTKOSTEN double-count
ComputeUPCInstance was setting InstanceTotal = effectiveCourtFee +
recoverableCeiling. The R.152 recoverable-cost cap is the OPPOSING
side's worst-case loss-of-suit liability, not the user's own cost —
folding it into GESAMTKOSTEN inflated the UPC total under a label
that means "your outlay," and the DE LG/OLG/BGH branches don't add
any opponent estimate. Drop it from InstanceTotal; the ceiling
still surfaces as its own RecoverableCeiling line item.
Live pre-fix on paliad.de (Streitwert 100k, UPC 1. Instanz only):
instanceTotal = 52600 = 14600 court fee + 38000 R.152 ceiling
Post-fix:
instanceTotal = 14600 (court fee only); RecoverableCeiling stays 38000
B3 — Court-determined Termine emit trigger date as a real-looking date
Zwischenverfahren / Mündliche Verhandlung / Entscheidung all live in
paliad.deadline_rules with duration_value=0 and parent_id=NULL, so
Calculate() classified them as IsRootEvent and emitted the trigger
date as their own DueDate. Worse, RoP.151 "Antrag auf Kostenentscheidung"
parents off inf.decision and chained 1 month off the placeholder ->
bogus deadline that the UI rendered as real.
Fix: classify a zero-duration rule as IsCourtSet (not IsRootEvent)
when primary_party = 'court' or event_type ∈ {hearing, decision,
order}. Track court-set rule IDs and propagate IsCourtSet downstream
to any rule whose parent is court-set, so RoP.151 also surfaces as
court-set rather than a fabricated date. Save-modal already greys
out IsCourtSet rows so the "Gerichtsbestimmte Termine ohne Datum
werden übersprungen" footnote becomes truthful again.
Live pre-fix on paliad.de (UPC_INF, trigger 2026-04-29):
Zwischenverfahren / Oral / Entscheidung -> dueDate 2026-04-29
Antrag auf Kostenentscheidung -> 2026-05-29 (bogus, +1mo from trigger)
B6 — Fristenrechner save flow stored rule code in TITLE
Frontend was concatenating "RoP.023 — Klageerwiderung" into the
title because deadlines had nowhere else to put the citation, and
the /deadlines REGEL column ended up showing "—". Add migration 032
with a paliad.deadlines.rule_code text column, plumb it through
CreateDeadlineInput / insertTx, drop the now-redundant r.code AS
rule_code JOIN alias on the list query (the deadline owns its
citation), and render f.rule_code on the project-detail deadlines
table + /deadlines events list + deadline-detail page.
Build, vet, and tests all clean. New unit test
TestIsCourtDeterminedRule pins the B3 discriminator across the
event_type / primary_party combinations seen in migrations 012 + 031.
Repro creds: tester@hlc.de
66 lines
1.9 KiB
Go
66 lines
1.9 KiB
Go
package services
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"mgit.msbls.de/m/paliad/internal/models"
|
|
)
|
|
|
|
// TestIsCourtDeterminedRule covers the discriminator used by Calculate to
|
|
// classify zero-duration rules as court-set waypoints rather than
|
|
// trigger-anchored root events. t-paliad-111 B3 — without this gate the
|
|
// Fristenrechner emitted the trigger date as the placeholder date for
|
|
// Zwischenverfahren / Mündliche Verhandlung / Entscheidung and any
|
|
// downstream rule (e.g. RoP.151 Antrag auf Kostenentscheidung) that
|
|
// chained off them.
|
|
func TestIsCourtDeterminedRule(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
rule models.DeadlineRule
|
|
want bool
|
|
}{
|
|
{
|
|
name: "primary_party=court → court-set",
|
|
rule: models.DeadlineRule{PrimaryParty: ptr("court"), EventType: ptr("hearing")},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "event_type=hearing → court-set even when party is defendant (PI response)",
|
|
rule: models.DeadlineRule{PrimaryParty: ptr("defendant"), EventType: ptr("hearing")},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "event_type=decision → court-set",
|
|
rule: models.DeadlineRule{EventType: ptr("decision")},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "event_type=order → court-set",
|
|
rule: models.DeadlineRule{EventType: ptr("order")},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "claimant filing (e.g. inf.soc Klageerhebung) → NOT court-set, anchors trigger",
|
|
rule: models.DeadlineRule{PrimaryParty: ptr("claimant"), EventType: ptr("filing")},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "defendant filing with no court signals → NOT court-set",
|
|
rule: models.DeadlineRule{PrimaryParty: ptr("defendant"), EventType: ptr("filing")},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "nil party + nil event_type → NOT court-set",
|
|
rule: models.DeadlineRule{},
|
|
want: false,
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := isCourtDeterminedRule(tc.rule); got != tc.want {
|
|
t.Errorf("isCourtDeterminedRule = %v, want %v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|