Files
Nexus/samples/Dispov2/plan-org-unit-hierarchy.md
T
Hartmut 4a5edeef3e
CI / Unit Tests (pull_request) Successful in 5m46s
CI / Lint (pull_request) Failing after 3m49s
CI / E2E Tests (pull_request) Has been skipped
CI / Fresh-Linux Docker Deploy (pull_request) Has been skipped
CI / Assistant Split Regression (pull_request) Failing after 35s
CI / Architecture Guardrails (pull_request) Failing after 2m14s
CI / Typecheck (pull_request) Successful in 4m22s
CI / Build (pull_request) Has been skipped
CI / Release Images (pull_request) Has been skipped
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI
- @capakraken/* → @nexus/* across 12 packages (root + 11 workspaces),
  1551 import lines migrated via codemod
- User-visible brand strings renamed (emails, page titles, PWA
  manifest, mobile header, MFA backup-codes header, tooltips, signin
  page, invite page, weekly digest, install prompt)
- TOTP issuer "CapaKraken" → "Nexus" (existing secrets still valid;
  re-enrollment relabels them in users' authenticator apps)
- Function rename: assertCapaKrakenDbTarget → assertNexusDbTarget
- LocalStorage migration shim in apps/web/src/app/layout.tsx copies
  capakraken_* → nexus_* on first load (guarded by nexus_migrated_v1
  sentinel; runs once per browser, then never again)
- Service-worker cache name capakraken-v2 → nexus-v2 with one-time
  caches.delete('capakraken-v2') from the same shim
- Email-domain fixtures @capakraken.{dev,app} → @nexus.{dev,app} in
  seed data, e2e specs, SMTP default fallback
- Dockerfile.dev / Dockerfile.prod / all .github/workflows/*.yml
  pnpm --filter @capakraken/* → @nexus/*
- README, CLAUDE.md, LEARNINGS.md, all docs/*.md, .env.example,
  tooling/deploy/.env.production.example brand sweep

Phase 1 deliberately leaves untouched (handled in Phase 3 cutover):
- PostgreSQL DB name "capakraken" and POSTGRES_USER "capakraken"
- Volume names capakraken_pgdata etc.
- Compose project name "capakraken" / "capakraken-prod"
- db-target-guard default expectedDatabase
- env-var CAPAKRAKEN_EXPECTED_DB_NAME
- Container DNS names in docker-compose.ci.yml

Quality gates green: pnpm typecheck (7/7), pnpm test:unit (7/7),
pnpm lint (0 errors), check:exports/imports/architecture all pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 15:10:44 +02:00

6.0 KiB

Plan: Org Unit Hierarchy (Level 5 / 6 / 7)

Date: 2026-03-13 Status: Draft Depends on: -

Problem

The chargeability report groups resources by a 3-level organizational hierarchy:

Level 5: Content Production (department)
  Level 6: CGI Content | CGI Technology | Creative Content Production | VFX (division)
    Level 7: Art Direction | CGI Production | 3D | IT Development | ... (chapter/team)

Every resource must be mapped to an Org Unit Level 7. Level 7 rolls up to Level 6, which rolls up to Level 5. The names of org units can change over time, so they must be editable.

Nexus already has a Role model, but roles represent skills/functions (e.g. "3D Artist"), not organizational placement. A person's org unit and their role are different dimensions.

Current Data

Level 5 → Level 6

Level 5 Level 6
Content Production CGI Content
Content Production CGI Technology
Content Production Creative Content Production
Content Production VFX

Level 6 → Level 7

Level 6 Level 7
CGI Content Art Direction
CGI Content Capability Development
CGI Content CGI Production
CGI Content Product Data Management
CGI Content Program/Delivery Mgmt
CGI Technology CGI Development
CGI Technology IT Development
Creative Content Production (direct, no sub-teams)
VFX 2D & Art Direction
VFX 3D
VFX Program/Delivery Mgmt & Other

Schema

New: OrgUnit model (self-referencing tree)

model OrgUnit {
  id        String    @id @default(cuid())
  name      String
  shortName String?                          // optional abbreviation
  level     Int                               // 5, 6, or 7
  parentId  String?
  parent    OrgUnit?  @relation("OrgUnitTree", fields: [parentId], references: [id])
  children  OrgUnit[] @relation("OrgUnitTree")
  sortOrder Int       @default(0)
  isActive  Boolean   @default(true)         // soft-delete for renamed/retired units
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt

  resources Resource[]                        // resources assigned to this org unit (level 7)

  @@unique([parentId, name])
}

Resource model extension

model Resource {
  // ... existing fields ...

  orgUnitId  String?
  orgUnit    OrgUnit?  @relation(fields: [orgUnitId], references: [id])
}

Constraint: A resource should only be assigned to a Level 7 org unit. The UI enforces this; the schema allows any level for flexibility.

Shared Types

// packages/shared/src/types/org-unit.ts

interface OrgUnit {
  id: string;
  name: string;
  shortName?: string;
  level: number; // 5, 6, or 7
  parentId?: string;
  sortOrder: number;
  isActive: boolean;
}

interface OrgUnitTree extends OrgUnit {
  children: OrgUnitTree[];
}

API

Location: packages/api/src/router/org-unit.ts

Procedure Access Description
list protected Returns flat list, optionally filtered by level or parentId
getTree protected Returns nested tree structure for UI rendering
create admin Create org unit with parent reference
update admin Rename, re-parent, change sort order
deactivate admin Soft-delete (sets isActive = false)

UI

Admin: Org Unit Management (/admin/org-units)

  • Tree view showing L5 → L6 → L7 hierarchy
  • Inline editing of names (since names can change)
  • Add/remove units at each level
  • Drag-and-drop reordering within a level
  • Deactivate (not hard delete) to preserve historical data

Resource: Org Unit Assignment

  • ResourceModal.tsx gets cascading dropdowns:
    1. Level 5 (pre-filtered or single value)
    2. Level 6 (filtered by selected L5)
    3. Level 7 (filtered by selected L6) — this is what gets stored on the resource
  • Alternative: single tree picker showing full path

Chargeability Report

The report groups by L6 → L7 (see plan-chargeability-report.md). The OrgUnit tree drives the row structure of the report.

Seed Data

Seed the initial hierarchy from the Dispo Categories file:

Content Production (L5)
├── CGI Content (L6)
│   ├── Art Direction (L7)
│   ├── Capability Development (L7)
│   ├── CGI Production (L7)
│   ├── Product Data Management (L7)
│   └── Program/Delivery Mgmt (L7)
├── CGI Technology (L6)
│   ├── CGI Development (L7)
│   └── IT Development (L7)
├── Creative Content Production (L6)
│   └── Creative Content Production (L7)
└── VFX (L6)
    ├── 2D & Art Direction (L7)
    ├── 3D (L7)
    └── Program/Delivery Mgmt & Other (L7)

Migration

  1. Create OrgUnit table and seed data
  2. Add orgUnitId to Resource (nullable)
  3. Admin assigns org units to existing resources
  4. No automated backfill possible — org unit assignment is business knowledge

Acceptance Criteria

  • OrgUnit model with self-referencing parent/child tree
  • Resource linked to OrgUnit (level 7)
  • Admin UI: tree view with CRUD for all levels
  • Resource modal: cascading L5 → L6 → L7 dropdowns
  • Org unit names are editable without breaking historical references
  • Seed data for the known hierarchy
  • getTree API returns nested structure for UI