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.
51 lines
1.9 KiB
PL/PgSQL
51 lines
1.9 KiB
PL/PgSQL
-- mig 113 — t-paliad-222 / m/paliad#50 — auto-derived project codes.
|
|
--
|
|
-- Adds an opponent-code slug field on litigation projects. Used as
|
|
-- the middle segment when BuildProjectCode assembles an auto-derived
|
|
-- project code from the ancestor tree (e.g. EXMPL.OPNT.567.INF.CFI).
|
|
--
|
|
-- NULL = segment skipped silently. Existing litigation rows yield
|
|
-- codes without an opponent segment until the user fills the field.
|
|
-- No backfill from `title` — the litigation title is free-text
|
|
-- ("Siemens AG ./. Huawei", "Mandant vs Gegner") and any regex would
|
|
-- be brittle; the user enters the slug once at project creation /
|
|
-- next edit.
|
|
--
|
|
-- Slug shape: uppercase letters / digits / dashes, max 16 chars.
|
|
-- Constraint also gates on type='litigation' so a stray value on a
|
|
-- non-litigation row is rejected at the DB level (defence in depth;
|
|
-- the form already hides the field on other types).
|
|
--
|
|
-- Idempotent.
|
|
|
|
BEGIN;
|
|
|
|
ALTER TABLE paliad.projects
|
|
ADD COLUMN IF NOT EXISTS opponent_code text;
|
|
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM pg_constraint
|
|
WHERE conname = 'projects_opponent_code_check'
|
|
AND conrelid = 'paliad.projects'::regclass
|
|
) THEN
|
|
ALTER TABLE paliad.projects
|
|
ADD CONSTRAINT projects_opponent_code_check
|
|
CHECK (opponent_code IS NULL
|
|
OR (opponent_code ~ '^[A-Z0-9-]{1,16}$'
|
|
AND type = 'litigation'));
|
|
END IF;
|
|
END $$;
|
|
|
|
COMMENT ON COLUMN paliad.projects.opponent_code IS
|
|
'Short slug for the opposing party on a litigation project '
|
|
'(uppercase letters, digits, dashes, max 16 chars). Used as the '
|
|
'middle segment when BuildProjectCode walks the ancestor tree to '
|
|
'assemble a dotted project code — e.g. EXMPL.OPNT.567.INF.CFI '
|
|
'(t-paliad-222 / m/paliad#50). NULL = segment skipped silently. '
|
|
'Only meaningful on type=''litigation'' rows; the CHECK enforces '
|
|
'that pairing.';
|
|
|
|
COMMIT;
|