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

178 lines
6.0 KiB
Markdown

# 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)
```prisma
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
```prisma
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
```typescript
// 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