Files
paliad/internal/services/submission_vars_projectless_test.go
mAi c1781c9a89 feat(generation): t-paliad-364 styled+filled submissions — project-less caption fill (P3b) + merge-safe styled firm-skeleton generator (P3a Option B)
P3(b) fill-what-we-can (submission_vars.go):
The project-less branch of SubmissionVarsService.Build ran only firm/today/
user/proceduralEvent resolvers, so caption.*/project.proceeding.* never
populated and every Rubrum value rendered [KEIN WERT]. The rule is already
loaded on this path and carries ProceedingTypeID. Now the branch loads the
proceeding type via loadProceedingType(rule.ProceedingTypeID) (tolerates a nil
id) and appends a proceeding-only projectResolver{project:nil} + a
captionResolver{project:nil} — both nil-project safe. Result: caption heading /
designations / versus / subject + the proceeding line fill from the
submission_code's proceeding; only party names / case number / court stay blank
for the lawyer. Preserves the "Ohne Projekt" affordance (t-paliad-243).

addProjectVars is now nil-project safe (guards the project.* direct fields,
keeps the pt-driven project.proceeding.* block) so projectResolver can serve as
the proceeding-only resolver. Pinned by TestProjectlessFill_* +
TestAddProjectVars_NilProjectFillsProceedingOnly (no DB).

P3(a) styling — merge-safe styled firm-skeleton (scripts/gen-hl-skeleton-template):
Generation landed on docx.BuildFallbackSkeleton (generic Heading1/2/Normal)
because the firm-skeleton's body had been repurposed into an anchors-only
Composer base, which HasMergePlaceholders rejects. Rewrote the generator to take
the deployed clean .docx carrier and replace ONLY word/document.xml with a clean
caption-driven Rubrum that uses the firm Rubrum styles (Table-Recitals-Party/
PartyDetails/PartyRoles/Sequencers, Heading-H2, Signature, Body-B0) and the same
{{key}}/{{caption.*}} placeholders the in-process fallback uses — preserving the
carrier's styles/theme/numbering/letterhead/logo and its sectPr verbatim. Adds a
-lang flag (DE _firm-skeleton.docx, EN _skeleton.en.docx) and auto-detects the
firm style prefix (HLpat-/HLCpat-) so it stays correct across the .dotm rebrand
drift. The resolver's tier-4/tier-3 merge-safe guard auto-prefers the restored
templates — no handler change.

Dry-run (TEST_DATABASE_URL, de.inf.lg.erwidg): project-less render fills caption
heading="In dem Rechtsstreit"/Klägerin/gegen/Beklagte/Patentverletzung + the
proceeding line, only party/case/court blank; full-project render additionally
fills parties + case number + court. Both carry the HLpat-Table-Recitals-* /
HLpat-Heading-H2 / HLpat-Signature styles, HasMergePlaceholders=true, no
{{#section}} junk.
2026-06-01 16:14:28 +02:00

81 lines
3.3 KiB
Go

package services
// Pins the project-less fill-what-we-can wiring (t-paliad-364 P3b). On a
// draft started "Ohne Projekt" (project_id NULL) the draft still carries a
// submission_code whose rule supplies a ProceedingTypeID. SubmissionVarsService
// .Build's project-less branch now runs projectResolver{project:nil} +
// captionResolver{project:nil} off that proceeding type, so the caption.* and
// project.proceeding.* keys fill while the genuinely project-specific keys
// (title / case_number / court) stay blank.
//
// This pins the resolver-set the branch assembles directly (no DB): the same
// two nil-project resolvers, run into one bag, must produce filled caption
// wording + proceeding name and leave the project-specific keys absent.
import (
"testing"
"mgit.msbls.de/m/paliad/pkg/docforge"
)
func TestProjectlessFill_CaptionAndProceedingFromRuleProceeding(t *testing.T) {
// DE LG infringement proceeding — the kind a submission_code's rule
// carries via proceeding_type_id even when no project is bound.
pt := ptType("de.inf.lg", "DE")
pt.Name = "Patentverletzungsklage LG"
pt.NameEN = "Patent infringement action (LG)"
// Mirror exactly what the project-less branch of Build appends.
bag := docforge.NewResolverSet(
projectResolver{project: nil, pt: pt, lang: "de"},
captionResolver{project: nil, pt: pt, lang: "de"},
).BuildBag()
// caption.* fills from the proceeding alone (resolveCaption is nil-project
// safe) — heading / designations / versus / subject all resolved, NOT
// [KEIN WERT].
wantCaption := map[string]string{
"caption.heading": "In dem Rechtsstreit",
"caption.claimant_designation": "Klägerin",
"caption.defendant_designation": "Beklagte",
"caption.versus": "gegen",
"caption.subject": "Patentverletzung",
}
for k, want := range wantCaption {
if got := bag[k]; got != want {
t.Errorf("bag[%q] = %q, want %q (caption must fill on project-less draft)", k, got, want)
}
}
// project.proceeding.* fills from pt.
if got := bag["project.proceeding.name"]; got == "" {
t.Errorf("bag[\"project.proceeding.name\"] is empty; want the proceeding name filled from the rule's proceeding type")
}
if got := bag["project.proceeding.code"]; got != "de.inf.lg" {
t.Errorf("bag[\"project.proceeding.code\"] = %q, want %q", got, "de.inf.lg")
}
// Genuinely project-specific keys stay absent — addProjectVars skips them
// when project is nil, so they fall through to the lawyer's overrides /
// [KEIN WERT] rather than rendering a bogus value.
for _, k := range []string{"project.title", "project.case_number", "project.court"} {
if _, present := bag[k]; present {
t.Errorf("bag[%q] present on a project-less draft; expected it to stay unset for the lawyer to fill", k)
}
}
}
// Pins nil-project safety of addProjectVars directly: a nil project must not
// panic and must still populate the proceeding namespace from pt.
func TestAddProjectVars_NilProjectFillsProceedingOnly(t *testing.T) {
bag := PlaceholderMap{}
addProjectVars(bag, nil, ptType("upc.inf.cfi", "UPC"), "en")
if got := bag["project.proceeding.code"]; got != "upc.inf.cfi" {
t.Errorf("project.proceeding.code = %q, want %q", got, "upc.inf.cfi")
}
if _, present := bag["project.title"]; present {
t.Error("project.title present for a nil project; want it skipped")
}
}