Implements m/paliad#47 (Client Role rework) + m/paliad#50 (auto-derived
project codes from the ancestor tree) in one shift.
Migrations:
- mig 112_client_role_rework: widen paliad.projects.our_side CHECK to
seven sub-roles (claimant / defendant / applicant / appellant /
respondent / third_party / other); drop legacy 'court' / 'both'
and backfill rows to NULL (no-op on prod, defensive on staging).
- mig 113_projects_opponent_code: add paliad.projects.opponent_code
text on litigation rows (slug pattern [A-Z0-9-]{1,16}); used as
the middle segment when assembling auto-derived project codes.
Backend:
- internal/services/project_code.go — new package-level helpers
BuildProjectCode (single row) + PopulateProjectCodes (bulk, one
CTE-based round-trip). Walks the existing paliad.projects.path
ltree; custom paliad.projects.reference on the target wins.
- Wired into ProjectService.List, GetByID, ListAncestors, GetTree,
LoadCounterclaimChildrenVisible, BuildTreeWithOptions — every
service entry-point that returns []models.Project / *models.Project
populates .Code before returning.
- Models: Project.OurSide doc widened; new Project.OpponentCode
(db:"opponent_code") and Project.Code (db:"-", projection-only).
- CreateProjectInput / UpdateProjectInput accept OpponentCode;
validateOpponentCode + nullableOpponentCode mirror our_side helpers.
- validateOurSide widens to the seven sub-roles; legacy 'court' /
'both' rejected at the service layer with a clear error before
the DB CHECK fires.
- derivedCounterclaimOurSide CCR flip widened: applicant ↔ respondent,
appellant → respondent; third_party / other / NULL pass through.
- submission_vars: project.code added to the placeholder bag.
ourSideDE / ourSideEN now use the gender-neutral "-Seite" /
"-Partei" suffix shape (Klägerseite / Antragstellerseite / ...);
better legal-prose default for a B2B patent practice, matches the
form labels which already used this shape (cf. head's soft-note on
Q4).
Frontend:
- ProjectFormFields: opponent_code on a new projekt-fields-litigation
block (hidden by default, shown when type=litigation); our_side
moved into projekt-fields-case and re-labelled "Client Role" /
"Mandantenrolle" with three <optgroup>s + seven options.
- project-form.ts: showFieldsForType toggles the new litigation
block; readPayload / prefillForm wire opponent_code; our_side
is now only emitted for type=case.
- fristenrechner: ourSideToPerspective widened to the seven sub-roles
(Active→claimant, Reactive→defendant, Other→null). ProjectOption
type literal updated.
- i18n.ts: new projects.field.client_role.* and
projects.field.opponent_code.* keys (DE+EN). Legacy
projects.field.our_side.* keys stay one release for cached
bundles + Verlauf event-history rendering of the new sub-roles.
Tests:
- TestProjectCodeSegment, TestAssembleProjectCode, TestPatentLast3,
TestSanitizeClientShort, TestProceedingTail, TestValidateOpponentCode,
TestValidateOurSideSubRoles pin the new pure helpers.
- TestOurSideTranslations widened to the seven sub-roles + new
prose shape; 'court'/'both' arms now return "" (legacy rejected).
- TestDerivedCounterclaimOurSide widened to the new flip map.
Migration slot history (this branch was rebumped twice on 2026-05-20):
mig 110 was claimed by m/paliad#51 (project_type_other, euler);
mig 111 was claimed by m/paliad#48 (project_admin_and_select, gauss).
Final slots 112 / 113.
go build && go test ./internal/... && cd frontend && bun run build
all clean.