feat(projects): t-paliad-222 — Client Role + auto-derived project codes (#47 + #50) #67

Open
mAi wants to merge 8 commits from mai/kepler/inventorcoder-project into main
Collaborator

Summary

Pairs #47 (Client Role rework — drop "Wir vertreten", rename to "Client Role" on case projects, widen sub-roles) with #50 (auto-derived project codes from the ancestor tree — EXMPL.OPNT.567.INF.CFI etc.) in one shift.

Design doc: docs/design-project-metadata-rework-2026-05-20.md (head approved all 9 design picks at (R) earlier today).

Changes

Migrations

  • mig 112 client_role_rework: widen paliad.projects.our_side CHECK to seven sub-roles (Active: claimant/applicant/appellant; Reactive: defendant/respondent; Other: third_party/other); drop legacy court/both, backfill rows to NULL (no-op on prod — verified 2026-05-20 all 12 rows are NULL).
  • mig 113 projects_opponent_code: add paliad.projects.opponent_code text on litigation rows; CHECK enforces [A-Z0-9-]{1,16} AND type=litigation.

Slot history: was 110/111, bumped twice on 2026-05-20 — mig 110 claimed by m/paliad#51 (euler), mig 111 by m/paliad#48 (gauss).

Backend

  • New internal/services/project_code.goBuildProjectCode(ctx, projectID) (single) + PopulateProjectCodes(ctx, []Project) (bulk, one CTE round-trip). Walks the existing paliad.projects.path ltree.
  • Wired into ProjectService.List, GetByID, ListAncestors, GetTree, LoadCounterclaimChildrenVisible, BuildTreeWithOptions — every service entry-point that returns projects populates .Code before returning.
  • validateOurSide widens; derivedCounterclaimOurSide widens flip-map (applicant↔respondent, appellant→respondent, third_party/other pass through).
  • submission_vars: {{project.code}} added. ourSideDE/EN switched to gender-neutral -Seite/-Partei suffix shape (Klägerseite / Antragstellerseite / …) per head's Q4 soft-note — better legal-prose default for a B2B patent practice and matches the form labels which already used this shape.

Frontend

  • ProjectFormFields: opponent_code on a new projekt-fields-litigation block; our_side moved into projekt-fields-case re-labelled "Client Role" / "Mandantenrolle" with three <optgroup>s + seven options.
  • project-form.ts: showFieldsForType toggles litigation; payload reader/writer + prefill wire opponent_code; our_side is now only emitted for type=case.
  • fristenrechner.ts: ourSideToPerspective widened to seven sub-roles. ProjectOption type literal updated.
  • i18n: 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.

Tests (new + widened)

  • TestProjectCodeSegment, TestAssembleProjectCode, TestPatentLast3, TestSanitizeClientShort, TestProceedingTail, TestValidateOpponentCode, TestValidateOurSideSubRoles — pin pure helpers.
  • TestOurSideTranslations widened (seven sub-roles, new prose shape; legacy court/both arms return "").
  • TestDerivedCounterclaimOurSide widened (new flip-map).

Verification

  • go build && go test ./internal/... && cd frontend && bun run build all clean.
  • Live DB audit (2026-05-20): zero rows on legacy our_side values; no opponent_code field existed yet; reference tree Example Client → Siemens v Huawei → EP3456789 → upc.inf.cfi will derive to EXAMPLE.OPNT.789.INF.CFI once opponent_code is filled.

Test plan

  • Deploy applies migs 112 + 113 cleanly (auto via webhook).
  • Create new client project — form does not show our_side / opponent_code.
  • Create new litigation project — form shows opponent_code only.
  • Create new case project — form shows "Client Role" with 3 optgroups + 7 options; old "Wir vertreten" label gone.
  • Setting Client Role = Antragsteller pre-locks Fristenrechner perspective to Klägerseite (Active group).
  • Setting opponent_code OPNT on a litigation under client EXMPL yields EXMPL.OPNT.<patent-last3>.<case-tail> on cases beneath.
  • Custom reference on a case still wins (no auto-derivation).

Closes #47
Closes #50

## Summary Pairs **#47** (Client Role rework — drop "Wir vertreten", rename to "Client Role" on case projects, widen sub-roles) with **#50** (auto-derived project codes from the ancestor tree — `EXMPL.OPNT.567.INF.CFI` etc.) in one shift. Design doc: `docs/design-project-metadata-rework-2026-05-20.md` (head approved all 9 design picks at (R) earlier today). ## Changes ### Migrations - **mig 112** `client_role_rework`: widen `paliad.projects.our_side` CHECK to seven sub-roles (Active: claimant/applicant/appellant; Reactive: defendant/respondent; Other: third_party/other); drop legacy `court`/`both`, backfill rows to NULL (no-op on prod — verified 2026-05-20 all 12 rows are NULL). - **mig 113** `projects_opponent_code`: add `paliad.projects.opponent_code text` on litigation rows; CHECK enforces `[A-Z0-9-]{1,16}` AND `type=litigation`. *Slot history: was 110/111, bumped twice on 2026-05-20 — mig 110 claimed by m/paliad#51 (euler), mig 111 by m/paliad#48 (gauss).* ### Backend - **New** `internal/services/project_code.go` — `BuildProjectCode(ctx, projectID)` (single) + `PopulateProjectCodes(ctx, []Project)` (bulk, one CTE round-trip). Walks the existing `paliad.projects.path` ltree. - Wired into `ProjectService.List`, `GetByID`, `ListAncestors`, `GetTree`, `LoadCounterclaimChildrenVisible`, `BuildTreeWithOptions` — every service entry-point that returns projects populates `.Code` before returning. - `validateOurSide` widens; `derivedCounterclaimOurSide` widens flip-map (applicant↔respondent, appellant→respondent, third_party/other pass through). - `submission_vars`: `{{project.code}}` added. `ourSideDE`/`EN` switched to gender-neutral `-Seite`/`-Partei` suffix shape (Klägerseite / Antragstellerseite / …) per head's Q4 soft-note — better legal-prose default for a B2B patent practice and matches the form labels which already used this shape. ### Frontend - `ProjectFormFields`: `opponent_code` on a new `projekt-fields-litigation` block; `our_side` moved into `projekt-fields-case` re-labelled "Client Role" / "Mandantenrolle" with three `<optgroup>`s + seven options. - `project-form.ts`: `showFieldsForType` toggles litigation; payload reader/writer + prefill wire opponent_code; our_side is now only emitted for `type=case`. - `fristenrechner.ts`: `ourSideToPerspective` widened to seven sub-roles. ProjectOption type literal updated. - i18n: 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. ### Tests (new + widened) - `TestProjectCodeSegment`, `TestAssembleProjectCode`, `TestPatentLast3`, `TestSanitizeClientShort`, `TestProceedingTail`, `TestValidateOpponentCode`, `TestValidateOurSideSubRoles` — pin pure helpers. - `TestOurSideTranslations` widened (seven sub-roles, new prose shape; legacy court/both arms return ""). - `TestDerivedCounterclaimOurSide` widened (new flip-map). ## Verification - `go build && go test ./internal/... && cd frontend && bun run build` all clean. - Live DB audit (2026-05-20): zero rows on legacy `our_side` values; no opponent_code field existed yet; reference tree `Example Client → Siemens v Huawei → EP3456789 → upc.inf.cfi` will derive to `EXAMPLE.OPNT.789.INF.CFI` once opponent_code is filled. ## Test plan - [ ] Deploy applies migs 112 + 113 cleanly (auto via webhook). - [ ] Create new client project — form does **not** show our_side / opponent_code. - [ ] Create new litigation project — form shows opponent_code only. - [ ] Create new case project — form shows "Client Role" with 3 optgroups + 7 options; old "Wir vertreten" label gone. - [ ] Setting Client Role = Antragsteller pre-locks Fristenrechner perspective to Klägerseite (Active group). - [ ] Setting opponent_code OPNT on a litigation under client EXMPL yields `EXMPL.OPNT.<patent-last3>.<case-tail>` on cases beneath. - [ ] Custom `reference` on a case still wins (no auto-derivation). Closes #47 Closes #50
mAi added 6 commits 2026-05-20 12:51:02 +00:00
Design doc for paired m/paliad#47 (Client Role rework) + m/paliad#50
(auto-derived project codes from the ancestor tree). Two migrations
(110 widen our_side CHECK + backfill court/both → NULL; 111 add
opponent_code on litigations), one new BuildProjectCode helper that
walks the existing ltree path, plus form / submission-template /
Determinator wiring.

9 open design questions surfaced for the head; recommendations
default to the issue-body (R) picks unless a material concern is
flagged in §2.2 / §3.2.

Verified against live data (2026-05-20): all 12 projects have
our_side=NULL, so the backfill is a no-op on production today.
No 'opponent' field exists yet.
Backend: mig 110/111 (will be renumbered after merging main),
validators + helpers widened, BuildProjectCode helper + projection
populator wired into List/GetByID/ListAncestors/GetTree/CCR. All
internal Go tests pass.

Frontend: ProjectFormFields conditional render — opponent_code on
litigation, our_side renamed to Client Role on case with grouped
optgroups. i18n keys for both DE and EN. fristenrechner perspective
mapping widened. project-form.ts payload reader/writer + showFieldsForType
toggle for new litigation block.

Migration slots about to be bumped (mig 110 was claimed by euler's
project_type_other on main).
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.
m added 1 commit 2026-05-20 12:53:27 +00:00
t-paliad-222 follow-up — wire the .code field populated by
PopulateProjectCodes into the project-detail header. Shows next to
the manual reference when distinct, hidden when they match (avoid
duplication) or when no segments resolved. CSS `.entity-ref-code`
adds bracket-styling so the user knows the value is derived rather
than typed.

Also extends the frontend Project interface with code + opponent_code
to make TypeScript surface the new fields cleanly across consumers.
m added 1 commit 2026-05-20 12:54:22 +00:00
t-paliad-222 follow-up — wire .code into the parent-project picker so
two same-titled projects in different trees can be disambiguated by
their auto-derived dotted code. Search includes the code; the badge
renders only when distinct from the manual reference.

Excel __meta sheet still pending — the JSON code field is populated
by PopulateProjectCodes for every list payload, so the export
generator only needs to add one row in a follow-up shift.
This pull request has changes conflicting with the target branch.
  • frontend/src/client/fristenrechner.ts
  • frontend/src/client/project-form.ts
  • frontend/src/components/ProjectFormFields.tsx
  • frontend/src/styles/global.css
  • internal/services/project_code.go
  • internal/services/submission_render_test.go
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin mai/kepler/inventorcoder-project:mai/kepler/inventorcoder-project
git checkout mai/kepler/inventorcoder-project
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#67
No description provided.