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
- @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>
151 lines
5.3 KiB
Markdown
151 lines
5.3 KiB
Markdown
# Plan: Client Model with WBS Hierarchy
|
|
|
|
**Date:** 2026-03-13
|
|
**Status:** Draft
|
|
**Depends on:** -
|
|
|
|
## Problem
|
|
|
|
Projects need to be linked to clients for chargeability reporting, budget tracking, and organizational grouping. The Dispo Categories file defines a two-level client hierarchy:
|
|
|
|
- **WBS Master Client** — the parent organization (e.g. "BMW", "VOLKSWAGEN")
|
|
- **WBS Client Name** — the legal entity (e.g. "BMW AG", "Dr. Ing. h.c. F. Porsche AG")
|
|
|
|
Currently Nexus has no Client model. Projects exist independently without client attribution.
|
|
|
|
## Data
|
|
|
|
35 WBS Master Clients with their legal sub-entities. Examples:
|
|
|
|
| WBS Master Client | WBS Client Names |
|
|
| ----------------- | --------------------------------------------------------------------------------------------- |
|
|
| BMW | BMW AG |
|
|
| VOLKSWAGEN | Audi Business Innovation GmbH, Dr. Ing. h.c. F. Porsche AG, MAN Truck & Bus SE, Volkswagen AG |
|
|
| DAIMLER | antoni garage GmbH & Co. KG, Mercedes-Benz AG |
|
|
| EXOR-STELLANTIS | AUTOMOBILES PEUGEOT, FCA Italy S.p.A., Ferrari S.p.A, MASERATI SPA A SOCIO UNICO |
|
|
|
|
## Schema
|
|
|
|
### New: `Client` model (self-referencing for parent/child)
|
|
|
|
```prisma
|
|
model Client {
|
|
id String @id @default(cuid())
|
|
name String // Display name
|
|
code String? @unique // Optional short code (e.g. "BMW", "VW")
|
|
parentId String?
|
|
parent Client? @relation("ClientTree", fields: [parentId], references: [id])
|
|
children Client[] @relation("ClientTree")
|
|
isActive Boolean @default(true)
|
|
sortOrder Int @default(0)
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
projects Project[]
|
|
|
|
@@unique([parentId, name])
|
|
}
|
|
```
|
|
|
|
Design choice: self-referencing tree instead of separate `MasterClient` + `ClientEntity` tables. This supports:
|
|
|
|
- Two levels today (Master → Entity)
|
|
- Potential deeper nesting in the future
|
|
- Simple queries via `parentId IS NULL` for top-level clients
|
|
|
|
### Project model extension
|
|
|
|
```prisma
|
|
model Project {
|
|
// ... existing fields ...
|
|
|
|
clientId String?
|
|
client Client? @relation(fields: [clientId], references: [id])
|
|
}
|
|
```
|
|
|
|
A project links to a WBS Client Name (child level). The master client is derived via `client.parent`.
|
|
|
|
## Shared Types
|
|
|
|
```typescript
|
|
// packages/shared/src/types/client.ts
|
|
|
|
interface Client {
|
|
id: string;
|
|
name: string;
|
|
code?: string;
|
|
parentId?: string;
|
|
isActive: boolean;
|
|
sortOrder: number;
|
|
}
|
|
|
|
interface ClientWithChildren extends Client {
|
|
children: Client[];
|
|
}
|
|
|
|
interface ClientTree extends Client {
|
|
children: ClientTree[];
|
|
}
|
|
```
|
|
|
|
## API
|
|
|
|
Location: `packages/api/src/router/client.ts`
|
|
|
|
| Procedure | Access | Description |
|
|
| ------------ | --------- | ------------------------------------------ |
|
|
| `list` | protected | Flat list, optionally filtered by parentId |
|
|
| `getTree` | protected | Nested tree for UI |
|
|
| `getById` | protected | Single client with parent/children |
|
|
| `create` | manager | Create client (top-level or child) |
|
|
| `update` | manager | Edit name, code, re-parent |
|
|
| `deactivate` | manager | Soft-delete |
|
|
|
|
Manager-level access (not just admin) since project managers typically need to manage client relationships.
|
|
|
|
## UI
|
|
|
|
### Client Management (`/clients` or `/admin/clients`)
|
|
|
|
- Tree view showing Master Clients → WBS Client Names
|
|
- CRUD at both levels
|
|
- Search/filter for quick lookup
|
|
- Badge showing number of linked projects per client
|
|
|
|
### Project: Client Assignment
|
|
|
|
- `ProjectModal.tsx` gets a cascading or searchable client picker:
|
|
1. Select or search Master Client
|
|
2. Select WBS Client Name under that master
|
|
- `ProjectWizard.tsx` Step 1 includes client selection
|
|
- Project list shows client name column
|
|
|
|
### Client Unit (Resource Attribute)
|
|
|
|
The chargeability report also has a "Client Unit" dimension on resources (BMW, Daimler, Porsche, etc.). This represents which client a resource is primarily assigned to. This is separate from project-client linking:
|
|
|
|
- **Project.clientId** → which client owns this project (WBS billing entity)
|
|
- **Resource client unit** → which client group the resource primarily serves (for reporting)
|
|
|
|
Resource client unit can be derived from the resource's primary assignments or set manually. This is covered in the resource extensions plan, not here.
|
|
|
|
## Seed Data
|
|
|
|
Seed the 35 master clients and their sub-entities from the Dispo Categories file. See `MandatoryDispoCategories_V3.xlsx` → `Project-Attr` sheet, rows 5-40.
|
|
|
|
## Migration
|
|
|
|
1. Create `Client` table with seed data (35 masters + ~50 sub-entities)
|
|
2. Add `clientId` to Project (nullable)
|
|
3. Admin links existing projects to clients
|
|
|
|
## Acceptance Criteria
|
|
|
|
- [ ] `Client` model with self-referencing parent/child tree
|
|
- [ ] Project linked to Client (WBS Client Name level)
|
|
- [ ] Client management UI with tree view
|
|
- [ ] Project modal/wizard includes client picker
|
|
- [ ] Seed data for all 35 master clients and sub-entities
|
|
- [ ] `getTree` API returns nested structure
|