chore(repo): initialize planarchy workspace

This commit is contained in:
2026-03-14 14:31:09 +01:00
commit dd55d0e78b
769 changed files with 166461 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

@@ -0,0 +1,322 @@
# Plan: Chargeability Report
**Date:** 2026-03-13
**Status:** Draft
**Depends on:** Country/SAH, OrgUnit hierarchy, Utilization Categories, Client/WBS, Resource extensions
## Problem
The bi-weekly chargeability report is currently produced in Excel. Planarchy needs a **live reporting section** in the app that updates in real-time as assignments, resources, and SAH change. The report is not a static file — it is an interactive page that can be **exported** as Excel or PDF on demand.
Core requirements:
- Live FTE-weighted chargeability percentages grouped by org unit hierarchy
- Historical actuals (SAP-imported or tracked) alongside forward forecasts
- Region filtering (GER only vs ALL resource types)
- Target vs actual comparison per management level and chapter
- Business Development % as a separate metric
- Change tracking (delta vs previous forecast snapshot)
- Excel and PDF export of the current view
## Report Structure (from Excel)
The report screenshot shows this layout:
```
Chg Report Content Prod. // - All
Status as of: {date}
| Region | Org Unit (L6) | Chapter (L7) | Ressource Type | FTE | Target ACN | tracked Chg % (SAP) | predicted Chg % (forecast months) |
```
### Row Hierarchy
**GER section** (Production Studios only):
```
GER | Content Production | Content Production | Total | FTE | Target | Jan | Feb | Mar | Apr | ...
GER | CGI Content | Art Direction | | FTE | Target | ... |
GER | CGI Content | Capability Dev | | FTE | Target | ... |
GER | CGI Content | CGI Production | | FTE | Target | ... |
GER | CGI Content | Product Data Mgmt | | FTE | Target | ... |
GER | CGI Content | Program/Delivery | | FTE | Target | ... |
GER | CGI Content | CGI Content | Total | FTE | Target | ... | ← L6 subtotal
GER | CGI Technology | CGI Development | | FTE | Target | ... |
GER | CGI Technology | IT Development | | FTE | Target | ... |
GER | CGI Technology | CGI Technology | Total | FTE | Target | ... |
... (Creative Content Production, VFX)
GER | Content Prod. | Total | Chg Germany | FTE | Target | ... | ← grand total
GER | Content Prod. | Total | BD Germany | | | ... | ← BD% row
```
**PLUS row**: additional CHG for Near&Offshore + ACN FTEs
**ALL row**: combined total
**Change tracking**: previous forecast date + delta rows
### Column Structure
| Column Group | Type | Source |
|---|---|---|
| FTE | number | Sum of resource FTE for the group |
| Target ACN | % | Official target per management level, FTE-weighted |
| Historical months (SAP) | tracked Chg % | Imported actual chargeability from SAP/period data |
| Forecast months | predicted Chg % | FTE-weighted average of individual resource forecasts |
### Key Formulas
**FTE per chapter:**
```
SUM(resource.fte) WHERE resource.orgUnit.level7 = chapter
AND resource.resourceType = filter
```
**Chargeability per chapter (forecast):**
```
SUM(resource.fte * resource.forecastChargeability[month])
/ SUM(resource.fte)
```
This is an FTE-weighted average.
**Target per chapter:**
```
SUM(resource.fte * resource.managementLevel.targetPercentage)
/ SUM(resource.fte)
```
**Unassigned hours (implicit):**
```
SAH - sum(all categorized hours)
```
**BD% (Germany):**
```
SUM(resource.fte * resource.bdPercentage[month])
/ SUM(resource.fte)
```
## Data Requirements
### What Planarchy needs to have (per resource, per month)
| Data Point | Source | Notes |
|---|---|---|
| FTE | Resource.fte | May vary monthly |
| Org Unit (L5/L6/L7) | Resource.orgUnit + tree | Drives row grouping |
| Country / Metro City | Resource.country | Drives region filter (GER vs ALL) |
| Resource Type | Derived or stored | Production Studios / Near&Offshore / Accenture |
| Management Level | Resource.managementLevel | Drives target % |
| Target % | ManagementLevel.targetPercentage | Official chargeability target |
| Forecast Chargeability | **Derived from assignments** | Hours assigned to Chg projects / SAH |
| Forecast BD% | **Derived from assignments** | Hours assigned to BD projects / SAH |
| Tracked Chargeability | **Imported from SAP** or tracked in-app | Actual period data |
### Forecast Chargeability Derivation
This is the key insight: **predicted chargeability can be derived from what Planarchy already knows**:
```
forecastChg(resource, month) =
sum(assignment.hoursPerDay * workingDays)
WHERE assignment.project.utilizationCategory = 'Chg'
AND assignment overlaps month
/ SAH(resource, month)
```
Similarly for BD, MD&I, etc. — each utilization category's hours divided by SAH.
This means the chargeability report is a **query over existing assignments + SAH**, not a separate data entry.
### Tracked (Actual) Chargeability
For historical data, two options:
1. **Import from SAP**: bulk import of period data (P-1, P-2, etc.) as snapshots
2. **Track in-app**: if Planarchy becomes the system of record for time tracking
Recommendation: Start with SAP import. Add a `ChargeabilitySnapshot` model for imported actuals.
```prisma
model ChargeabilitySnapshot {
id String @id @default(cuid())
periodDate DateTime // e.g. 2026-02-28
periodType String // "PTD" or "MTD"
orgUnitId String // L7 chapter
resourceType String? // filter dimension
fte Float
chargeability Float // 0.0 to 1.0
bdPercentage Float?
mdiPercentage Float?
moPercentage Float?
recoveryRate Float?
importedAt DateTime @default(now())
@@unique([periodDate, periodType, orgUnitId, resourceType])
}
```
## API
Location: `packages/api/src/router/chargeability-report.ts`
| Procedure | Access | Description |
|---|---|---|
| `getReport` | manager | Full chargeability report for a date range and region filter |
| `getResourceForecast` | manager | Per-resource monthly forecast data (for the ChgFC-equivalent) |
| `importActuals` | admin | Bulk import SAP period data |
| `getSnapshots` | manager | List imported actuals for a period |
| `getChangeTracking` | manager | Delta between current forecast and a previous snapshot date |
### `getReport` Response Shape
```typescript
interface ChargeabilityReportRow {
region: string; // "GER" or "ALL"
orgUnitL6: string;
chapter: string; // L7 name
resourceType?: string; // for ALL section sub-rows
isSubtotal: boolean;
fte: number;
targetACN: number; // FTE-weighted target %
months: {
month: string; // "2026-03"
dataType: 'SAP' | 'MTD'; // actual vs forecast
fte: number;
chargeabilityPercent: number;
}[];
}
interface ChargeabilityReport {
statusDate: string;
gerRows: ChargeabilityReportRow[];
bdGermany: { month: string; bdPercent: number }[];
plusRow: { fte: number; months: { month: string; chargeabilityPercent: number }[] };
allTotalRow: ChargeabilityReportRow;
changeTracking?: {
previousDate: string;
gerDelta: { month: string; delta: number }[];
allDelta: { month: string; delta: number }[];
};
}
```
## Engine: Chargeability Calculator
Location: `packages/engine/src/chargeability/calculator.ts`
Pure functions for:
```typescript
// FTE-weighted chargeability for a group
function calculateGroupChargeability(
resources: { fte: number; chargeability: number }[]
): number;
// Derive forecast chargeability from assignments + SAH
function deriveResourceForecast(
assignments: { hoursPerDay: number; startDate: Date; endDate: Date; utilizationCategory: string }[],
sah: SAHResult,
month: { start: Date; end: Date }
): { chg: number; bd: number; mdi: number; mo: number; pdr: number; unassigned: number };
// FTE-weighted target for a group
function calculateGroupTarget(
resources: { fte: number; targetPercentage: number }[]
): number;
```
## UI
### Live Reporting Section (`/reports/chargeability`)
This is an **interactive, live-updating page** — not a static export. Data refreshes whenever assignments, resource attributes, or SAH inputs change.
**Layout:**
- Header: title, status date (auto = now), region toggle (GER / ALL)
- Filter bar: resource type toggles (Production Studios / Accenture / Near&Offshore), date range (which months to display)
- Main table: matching the Excel screenshot structure (see Row Hierarchy above)
- Color coding: green background = actual months (SAP data), blue = current month, white = forecast months
- Subtotal rows: bold, darker background
- BD% row: bottom of GER section
- Change tracking section: collapsible, shows delta vs a selectable previous snapshot date
- Sticky first columns (Region, Org Unit, Chapter, Resource Type, FTE, Target) with horizontal scroll for months
**Interactivity:**
- Click on a chapter row → drill down to individual resources in that chapter
- Click on a resource → navigate to resource detail page
- Click on a cell → tooltip showing the contributing assignments and their hours
- Compare mode: select two snapshot dates and see a side-by-side delta view
**Real-time updates:**
- SSE integration: report subscribes to assignment/resource change events
- When an assignment is created/modified/deleted, affected rows recalculate
- Debounced refresh (not per-keystroke, but within seconds of a change)
### Export (on demand)
Export buttons in the page header:
**Excel export:**
- Matches the current Excel report format for stakeholder familiarity
- Includes all visible rows and columns based on current filter state
- Separate sheets for GER and ALL views
- Formulas preserved where possible (e.g. subtotals as SUM formulas)
- Uses the existing PDF/Excel export infrastructure from Phase 6
**PDF export:**
- Landscape layout matching the on-screen table
- Includes header with status date and active filters
- Page breaks per L6 section
- Color coding preserved
## Dependencies
All other plans must land first:
1. **Country/SAH** — provides available hours denominator
2. **OrgUnit hierarchy** — provides row grouping
3. **Utilization Categories** — provides hour bucketing (Chg, BD, etc.)
4. **Client/WBS** — provides project attribution
5. **Resource extensions** — provides management level, resource type, client unit
6. **Management Level model** — provides target percentages
## Phased Delivery
### Phase A: Live forecast report
- Interactive page at `/reports/chargeability`
- Derive chargeability from assignments + SAH (live query, no static file)
- Show FTE and predicted Chg% per chapter per month
- Region filter (GER/ALL) and resource type toggles
- Subtotals and BD% row
- SSE subscription for real-time updates
### Phase B: Target comparison + drill-down
- Add management level targets
- Show Target ACN column with variance highlighting
- Click-to-drill-down into individual resources per chapter
- Cell tooltips showing contributing assignments
### Phase C: Historical actuals + snapshots
- SAP actuals import mechanism
- Show tracked Chg% for past periods (green columns)
- Mixed actual/forecast column display
- Snapshot save/compare for change tracking
### Phase D: Export + client views
- Excel export matching stakeholder format (with formulas)
- PDF export (landscape, color-coded)
- Client-specific report views (e.g. BMW-only filter)
- Compare mode (two snapshots side-by-side)
## Acceptance Criteria
- [ ] Chargeability report page with GER/ALL toggle
- [ ] Rows grouped by L6 → L7 with subtotals
- [ ] FTE-weighted chargeability percentages per chapter per month
- [ ] Forecast derived from assignments + SAH (not manual entry)
- [ ] Target column from management level target percentages
- [ ] Resource type sub-rows in ALL section
- [ ] BD% row for Germany
- [ ] SAP actuals import mechanism
- [ ] Change tracking (delta vs previous snapshot)
- [ ] Excel export
+149
View File
@@ -0,0 +1,149 @@
# 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 Planarchy 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
+210
View File
@@ -0,0 +1,210 @@
# Plan: Country-Driven Standard Available Hours (SAH) and FTE
**Date:** 2026-03-13
**Status:** Draft
**Depends on:** OrgUnit hierarchy, Resource model extensions
## Problem
Planarchy currently uses a flat `hoursPerDay` on allocations. The chargeability reporting model requires:
- Country-specific daily working hours (8h Germany, 9h India, variable Spain)
- Public holidays per country AND metro city
- FTE as a multiplier that reduces available hours (FTE 0.5 = 4h/day in an 8h country)
- SAH (Standard Available Hours) = net hours after deducting holidays and absences
Without SAH, FTE-based chargeability calculations cannot be accurate.
## Concepts
### Standard Available Hours (SAH)
> "Die Standard Available Hrs (SAH) bezeichnen die Netto-Arbeitszeit, also die Zeit, die nach Abzug aller Fehlzeiten tatsaechlich fuer die produktive Arbeit zur Verfuegung steht."
**Formula per resource per period:**
```
grossHoursPerDay = country.dailyWorkingHours (with Spain schedule rules)
effectiveHoursPerDay = grossHoursPerDay * resource.fte
workingDaysInPeriod = calendarDays - weekends - publicHolidays(country, metroCity)
absenceDays = vacation + illness + other absence
SAH = effectiveHoursPerDay * (workingDaysInPeriod - absenceDays)
```
### Daily Working Hours by Country
| Country | Daily Hours | Special Rules |
|---|---|---|
| Costa Rica | 8h | - |
| Germany | 8h | - |
| Hungary | 8h | - |
| India | 9h | - |
| Italy | 8h | - |
| Portugal | 8h | - |
| Spain | variable | Fridays always 6.5h; Mon-Thu 6.5h during 1 Jul - 15 Sep, otherwise 9h |
| United Kingdom | 8h | - |
### FTE Impact
- FTE is stored with at least 2 decimal places (e.g. 0.50, 0.80)
- FTE can change per month (contract changes, joiners/leavers)
- Part-time 50% in Germany: 8h * 0.50 = 4h effective daily hours
- Part-time 80% in India: 9h * 0.80 = 7.2h effective daily hours
## Schema Changes
### New: `Country` model
```prisma
model Country {
id String @id @default(cuid())
code String @unique // ISO 3166-1 alpha-2 (DE, IN, ES, ...)
name String // Display name
dailyWorkingHours Float @default(8.0) // Base daily hours
scheduleRules Json? @db.JsonB // For Spain-style variable schedules
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
metroCities MetroCity[]
resources Resource[]
}
```
### New: `MetroCity` model
```prisma
model MetroCity {
id String @id @default(cuid())
name String // e.g. "Munich", "Stuttgart"
countryId String
country Country @relation(fields: [countryId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
resources Resource[]
@@unique([countryId, name])
}
```
### Resource model extensions
```prisma
model Resource {
// ... existing fields ...
fte Float @default(1.0) // Already exists, ensure 2+ decimals
countryId String?
country Country? @relation(fields: [countryId], references: [id])
metroCityId String?
metroCity MetroCity? @relation(fields: [metroCityId], references: [id])
}
```
Note: Resource already has `postalCode` and `federalState` from Phase 9 (Vacation Pro).
The new `countryId` + `metroCityId` are complementary — `federalState` stays for German public holiday derivation, `countryId` drives daily hours and SAH.
## Engine: SAH Calculator
Location: `packages/engine/src/sah/calculator.ts`
```typescript
interface SAHInput {
country: { dailyWorkingHours: number; scheduleRules?: SpainScheduleRule };
fte: number;
periodStart: Date;
periodEnd: Date;
publicHolidays: Date[]; // from country + metro city
absenceDays: Date[]; // vacation, illness, other
}
interface SAHResult {
grossWorkingDays: number;
publicHolidayDays: number;
absenceDays: number;
netWorkingDays: number;
effectiveHoursPerDay: number;
standardAvailableHours: number;
}
function calculateSAH(input: SAHInput): SAHResult;
```
Spain schedule handling:
```typescript
interface SpainScheduleRule {
type: 'spain';
fridayHours: number; // 6.5
summerPeriod: { from: string; to: string }; // "07-01" to "09-15"
summerHours: number; // 6.5
regularHours: number; // 9.0
}
function getSpainDailyHours(date: Date, rule: SpainScheduleRule): number;
```
## Public Holidays
Phase 9 already has `computeEaster()` and `getPublicHolidays(year, state?)` for German states (Bavaria).
This needs extension:
- Move to a country-aware public holiday system
- Support metro city specific holidays (e.g. Munich Assumption Day)
- Seed known holidays per country from a reference table
- Admin UI to manage/override holidays per country+city
Existing Phase 9 German holiday logic becomes the "Germany" implementation within the broader system.
## FTE Monthly Tracking
The chargeability report shows FTE can vary by month per resource. Options:
1. **Simple:** Single `fte` field on Resource, changed when contract changes (current approach)
2. **Monthly:** `ResourceFTEHistory` model tracking FTE per month with effective date
Recommendation: Start with option 1 (single FTE field). Add history tracking as a follow-up if month-over-month FTE change reporting is needed. The current Resource model already has `fte`.
## UI Changes
### Admin: Country + Metro City Management
- `/admin/countries` page
- CRUD for countries with daily working hours and schedule rules
- Nested metro city management per country
- Seed data for the 8 known countries + their metro cities
### Resource: Country + Metro City Assignment
- `ResourceModal.tsx` gets Country dropdown (required) and Metro City dropdown (filtered by country)
- Auto-suggest metro city based on existing `postalCode` where possible
### Dashboard/Reports: SAH Display
- Resource detail page shows SAH for current period
- Chargeability report uses SAH as the denominator
## Migration
1. Create Country + MetroCity tables with seed data
2. Backfill existing resources: derive country from `federalState` (Germany) or leave null
3. Admin assigns countries to remaining resources
## Dependencies
- Public holiday system must be extended to all countries (not just German states)
- Vacation/absence system (Phase 9) feeds into SAH absence deduction
- Chargeability report (separate plan) consumes SAH as primary metric
## Acceptance Criteria
- [ ] Country model with daily working hours and optional schedule rules
- [ ] Metro City model linked to Country
- [ ] Resource linked to Country + Metro City
- [ ] SAH calculator in engine: `calculateSAH()` pure function
- [ ] Spain variable schedule correctly handled
- [ ] FTE reduces effective daily hours: `effectiveHours = dailyHours * fte`
- [ ] Public holidays extended beyond Germany to all 8 countries
- [ ] Admin UI for Country + Metro City CRUD
- [ ] Resource modal with Country + Metro City selection
- [ ] Seed data for all 8 countries and their metro cities
+177
View File
@@ -0,0 +1,177 @@
# 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.
Planarchy 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
+98
View File
@@ -0,0 +1,98 @@
# Dispo v2 Implementation Plan — Overview
**Date:** 2026-03-13
**Status:** Draft
**Source:** `MandatoryDispoCategories_V3.xlsx` + `20260309_Bi-Weekly_Chargeability_Reporting_Content_Production_V0.943_4Hartmut.xlsx`
## Goal
Extend Planarchy to support chargeability reporting with country-specific SAH (Standard Available Hours), FTE-based capacity, organizational hierarchy, utilization categories, client/WBS management, and a native chargeability report replacing the current Excel workflow.
## Plan Documents
| # | Plan | File | Core Deliverable |
|---|---|---|---|
| 1 | Country, SAH & FTE | [plan-country-sah-fte.md](plan-country-sah-fte.md) | Country/MetroCity models, SAH calculator, FTE-scaled daily hours |
| 2 | Org Unit Hierarchy | [plan-org-unit-hierarchy.md](plan-org-unit-hierarchy.md) | 3-level OrgUnit tree (L5→L6→L7), resource assignment, admin UI |
| 3 | Utilization Categories | [plan-utilization-categories.md](plan-utilization-categories.md) | UtilizationCategory model on projects (Chg, BD, MD&I, M&O, PD&R, Absence) |
| 4 | Client & WBS | [plan-client-wbs-model.md](plan-client-wbs-model.md) | Client tree (Master→Entity), project-client linking |
| 5 | Resource Extensions | [plan-resource-extensions.md](plan-resource-extensions.md) | EID attributes, ManagementLevel, ResourceType, Chg Responsibility, derivation rules |
| 6 | Chargeability Report | [plan-chargeability-report.md](plan-chargeability-report.md) | Native report replacing Excel, forecast from assignments + SAH, SAP import for actuals |
## Key Design Decisions
### SAH as capacity basis
Standard Available Hours = `(dailyHours * FTE) * (workingDays - publicHolidays - absence)`.
Country drives daily hours (8h most, 9h India, variable Spain). FTE reduces proportionally.
### Resource Type derivation (Option A)
Store only 5 base types in DB: Employee, Freelancer, Apprentice, Intern, Student.
Derive reporting types at query time:
- **Production Studios** = `chgResponsibility = true` AND country = Germany
- **Near&Offshore** = country NOT Germany AND type = Employee/Freelancer
- **Accenture** = `chgResponsibility = false`
- **Long-term absence** = derived from absence system
Derivation rules are configurable in admin (which countries map to which reporting type).
### Utilization on projects, not allocations
Each project carries a utilization category (Chg, BD, MD&I, etc.). Hours assigned to a project inherit its category for reporting. Unassigned hours = SAH minus all categorized hours.
### Forecast chargeability = derived metric
`forecastChg = hours on Chg projects / SAH`. No manual chargeability entry — it comes from what Planarchy already knows about assignments.
## Dependency Order
```
1. Country/SAH ─────────────────┐
2. OrgUnit Hierarchy ───────────┤
3. Utilization Categories ──────┼──→ 5. Resource Extensions ──→ 6. Chargeability Report
4. Client/WBS ──────────────────┘
```
Plans 1-4 are independent and can be implemented in parallel.
Plan 5 (Resource Extensions) depends on 1-4 for the FK targets.
Plan 6 (Chargeability Report) depends on all others.
## New Prisma Models Summary
| Model | Purpose |
|---|---|
| `Country` | Country with daily working hours and schedule rules |
| `MetroCity` | City within a country (for public holidays) |
| `OrgUnit` | Self-referencing 3-level org hierarchy |
| `UtilizationCategory` | Project classification for hour bucketing |
| `Client` | Self-referencing client hierarchy (Master → Entity) |
| `ManagementLevelGroup` | Career level grouping with target chargeability % |
| `ManagementLevel` | Specific level within a group |
| `ChargeabilitySnapshot` | Imported SAP actuals for historical reporting |
## Resource Model Changes Summary
New fields on `Resource`:
- `enterpriseId` (String, unique)
- `countryId` → Country
- `metroCityId` → MetroCity
- `orgUnitId` → OrgUnit (L7)
- `managementLevelGroupId` → ManagementLevelGroup
- `managementLevelId` → ManagementLevel
- `resourceType` (enum: EMPLOYEE, FREELANCER, APPRENTICE, INTERN, STUDENT)
- `chgResponsibility` (Boolean, default true)
- `rolledOff` (Boolean)
- `departed` (Boolean)
- `clientUnitId` → Client (primary client for reporting)
- `lcrCents`, `ucrCents` (Int, placeholder for cost rates)
## Project Model Changes Summary
New fields on `Project`:
- `utilizationCategoryId` → UtilizationCategory
- `clientId` → Client (WBS Client Name)
## Open Questions
1. **Resource Type derivation rules**: The country→reporting-type mapping should be admin-configurable. Exact admin UI TBD.
2. **Win Probability**: The Dispo file mentions it "should contain the value from MMS". Is this relevant for Planarchy? If so, it's a field on Project.
3. **LCR/UCR**: Cost rate definitions are not yet available. Placeholder fields are included.
4. **SAP import format**: What format do SAP period exports come in? CSV? API? Needs clarification for the import mechanism.
5. **FTE history**: Currently single `fte` field. Monthly FTE tracking may be needed if contract changes happen mid-month.
+206
View File
@@ -0,0 +1,206 @@
# Plan: Resource Model Extensions (EID Attributes)
**Date:** 2026-03-13
**Status:** Draft
**Depends on:** Country/SAH, OrgUnit hierarchy
## Problem
The Dispo Categories file defines a rich set of EID (employee) attributes that Planarchy's Resource model currently does not cover. These attributes are needed for chargeability reporting, resource filtering, and organizational grouping.
## Current Resource Model (relevant fields)
```
Resource {
id, name, email, skills (JSONB), isActive, postalCode, federalState,
portfolioUrl, roleId, aiSummary, fte, userId, ...
}
```
## Required New Attributes
### Attributes that need DB storage
| Attribute | Type | Source | Notes |
|---|---|---|---|
| Enterprise ID | String | manual/import | ACN-style username (e.g. "a.kasperovich") |
| Country | FK → Country | manual | See plan-country-sah-fte.md |
| Metro City | FK → MetroCity | manual | See plan-country-sah-fte.md |
| Org Unit (L7) | FK → OrgUnit | manual | See plan-org-unit-hierarchy.md |
| Management Level Group | FK → ManagementLevel | manual | See below |
| Management Level | derived from group | - | Sub-level within group |
| FTE | Float | manual | Already exists, ensure 2+ decimal precision |
| Resource Type | Enum | manual | Apprentice, Employee, Freelancer, Intern, Student |
| Chg Responsibility | Boolean | manual | Default: true. Drives "Accenture" resource type derivation |
| Rolled Off | Boolean | manual | Status flag, default: false |
| Departed | Boolean | manual | Status flag, default: false |
| Client Unit | FK → Client? or String | manual | Primary client assignment for reporting |
### Attributes that are derived (no DB input)
| Attribute | Derivation Rule |
|---|---|
| Long-term absence | Derived from vacation/absence system (extended leave) |
| Chapter | Derived from OrgUnit L7 → name |
| Department | Derived from OrgUnit L6 → name |
| MV Ressource Type (reporting) | Derived: see resource type derivation rules |
### Resource Type Derivation for Reporting
The chargeability report uses a "MV Ressource Type" that differs from the stored Resource Type:
| Reporting Type | Derivation Rule |
|---|---|
| Production Studios | `chgResponsibility = true` AND country is Germany |
| Near&Offshore | Country is NOT Germany AND resource type is Employee/Freelancer |
| Accenture | `chgResponsibility = false` (regardless of country) |
| Long-term absence | Has active long-term absence flag |
These are computed at query time, not stored. An admin UI can make the country→reporting-type mapping configurable.
## Management Level Model
### New: `ManagementLevelGroup` model
```prisma
model ManagementLevelGroup {
id String @id @default(cuid())
name String @unique // "Analyst", "Consultant", "Manager", etc.
targetPercentage Float // Official chargeability target (e.g. 0.805)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
levels ManagementLevel[]
resources Resource[]
}
```
### New: `ManagementLevel` model
```prisma
model ManagementLevel {
id String @id @default(cuid())
name String @unique // "10-Senior Analyst", "11-Analyst", etc.
groupId String
group ManagementLevelGroup @relation(fields: [groupId], references: [id])
resources Resource[]
}
```
### Seed Data
| Group | Target % | Levels |
|---|---|---|
| Accenture Leadership | 36.5% | *(levels 1-4, names TBD)* |
| Senior Manager | 54.6% | 5-Associate Director, 6-Senior Manager |
| Manager | 74.7% | 7-Manager |
| Consultant | 80.8% | 8-Associate Manager, 9-Team Lead/Consultant |
| Analyst | 80.5% | 10-Senior Analyst, 11-Analyst |
| Associate | 77.0% | 12-Associate, 13-New Associate |
## Schema Changes on Resource
```prisma
model Resource {
// ... existing fields ...
enterpriseId String? @unique // ACN enterprise ID
countryId String? // FK → Country
country Country? @relation(...)
metroCityId String? // FK → MetroCity
metroCity MetroCity? @relation(...)
orgUnitId String? // FK → OrgUnit (L7)
orgUnit OrgUnit? @relation(...)
managementLevelGroupId String? // FK → ManagementLevelGroup
managementLevelGroup ManagementLevelGroup? @relation(...)
managementLevelId String? // FK → ManagementLevel
managementLevel ManagementLevel? @relation(...)
resourceType ResourceType @default(EMPLOYEE)
chgResponsibility Boolean @default(true)
rolledOff Boolean @default(false)
departed Boolean @default(false)
clientUnitId String? // Primary client for reporting
clientUnit Client? @relation("ResourceClientUnit", fields: [clientUnitId], references: [id])
}
enum ResourceType {
EMPLOYEE
FREELANCER
APPRENTICE
INTERN
STUDENT
}
```
## UI Changes
### Resource Modal Extensions
Add to `ResourceModal.tsx`:
| Field | UI Element | Notes |
|---|---|---|
| Enterprise ID | Text input | Optional, unique |
| Country | Dropdown | Required for SAH |
| Metro City | Dropdown (filtered by country) | Optional |
| Org Unit | Cascading L5→L6→L7 picker | Stores L7 |
| Management Level Group | Dropdown | Drives target % |
| Management Level | Dropdown (filtered by group) | Specific level |
| Resource Type | Dropdown (5 values) | Default: Employee |
| Chg Responsibility | Toggle | Default: on |
| Client Unit | Client picker | Primary client for reporting |
| Rolled Off | Toggle | Status |
| Departed | Toggle | Status |
### Admin: Management Level Management (`/admin/management-levels`)
- Table of management level groups with target percentages
- Nested levels within each group
- Editable target % (changes over time)
- Add/edit levels
### Resource List Enhancements
- New columns: Enterprise ID, Country, Org Unit, Management Level, Client Unit
- Filters for: Country, Org Unit (L6/L7), Management Level Group, Resource Type, Client Unit
- Status filter: active / rolled off / departed
## LCR and UCR
The Dispo file mentions LCR (Local Cost Rate) and UCR (Unit Cost Rate) but has no defined values yet. These are likely cost rates per resource used in budget calculations.
Recommendation: Add placeholder fields now, define schema when values become available.
```prisma
model Resource {
// ...
lcrCents Int? // Local Cost Rate in cents per hour
ucrCents Int? // Unit Cost Rate in cents per hour
}
```
Follows the integer-cents pattern from CLAUDE.md.
## Migration
1. Add new columns to Resource (all nullable initially)
2. Create ManagementLevelGroup + ManagementLevel tables with seed data
3. Add ResourceType enum
4. Admin populates existing resources via batch import or manual assignment
## Acceptance Criteria
- [ ] Enterprise ID field on Resource (unique, optional)
- [ ] Resource linked to Country, MetroCity, OrgUnit, ManagementLevel
- [ ] ResourceType enum with 5 values
- [ ] chgResponsibility, rolledOff, departed boolean flags
- [ ] Client Unit FK for primary client assignment
- [ ] ManagementLevelGroup with target percentages
- [ ] ManagementLevel with group membership
- [ ] Resource modal with all new fields
- [ ] Resource list with new columns and filters
- [ ] Management level admin UI
- [ ] LCR/UCR placeholder fields (integer cents)
- [ ] Reporting resource type derivation logic
@@ -0,0 +1,158 @@
# Plan: Utilization Categories on Projects
**Date:** 2026-03-13
**Status:** Draft
**Depends on:** -
## Problem
The chargeability report categorizes all work into utilization buckets. Currently Planarchy projects have no utilization classification. Every project needs a utilization category so that hours booked against it flow into the correct reporting bucket.
## Utilization Categories
From the Dispo Categories file (adapted to ACN naming):
| Short | Name | Description |
|---|---|---|
| Chg | Chargeable | Billable client project work |
| BD | Business Development | Sales, proposals, presales activities |
| MD&I | Market Development and Initiative | R&D, innovation, market development |
| M&O | Management and Operations | Internal admin, management overhead |
| PD&R | Personal Development and Recruitment | Training, hiring, onboarding |
| Absence | Absence & Non Standard | Reduces Standard Available Hours: vacation, illness, non-standard leave |
Notes from the Dispo file:
- "Absent" and "Not available" are merged into "Absence & Non Standard"
- "Unassigned" hours are calculated automatically (SAH minus all categorized hours)
- Categories follow ACN naming convention
## Schema
### New: `UtilizationCategory` model
```prisma
model UtilizationCategory {
id String @id @default(cuid())
code String @unique // "Chg", "BD", "MD&I", etc.
name String // Full display name
description String? // Editable explanation
sortOrder Int @default(0)
isActive Boolean @default(true)
isDefault Boolean @default(false) // One category can be the default for new projects
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
projects Project[]
}
```
### Project model extension
```prisma
model Project {
// ... existing fields ...
utilizationCategoryId String?
utilizationCategory UtilizationCategory? @relation(fields: [utilizationCategoryId], references: [id])
}
```
Why on Project (not on Allocation/Assignment):
- The Dispo model maps categories to projects, not to individual time entries
- A project is either "Chargeable" or "Business Development" — the category is a project-level attribute
- Hours assigned to a project inherit the project's utilization category for reporting
## Shared Types
```typescript
// packages/shared/src/types/utilization-category.ts
interface UtilizationCategory {
id: string;
code: string; // "Chg", "BD", "MD&I", "M&O", "PD&R", "Absence"
name: string;
description?: string;
sortOrder: number;
isActive: boolean;
isDefault: boolean;
}
```
## API
Location: `packages/api/src/router/utilization-category.ts`
| Procedure | Access | Description |
|---|---|---|
| `list` | protected | Returns all active categories (sorted) |
| `getById` | protected | Single category |
| `create` | admin | Create new category |
| `update` | admin | Edit code, name, description, sort order |
| `deactivate` | admin | Soft-delete |
## UI
### Admin: Utilization Category Management (`/admin/utilization-categories`)
- Table listing all categories with code, name, description
- Inline editing of name and description
- Add new categories (for future extensibility)
- Reorder via sort order
- Mark one as default for new projects
### Project: Category Assignment
- `ProjectModal.tsx` gets a `UtilizationCategory` dropdown (required or defaults to "Chg")
- `ProjectWizard.tsx` Step 1 includes category selection
- Project list shows category badge
### Chargeability Report
Hours are bucketed by looking up `project.utilizationCategory.code`:
- `Chg` hours → chargeability numerator
- `BD` hours → business development column
- `MD&I` hours → market development column
- `M&O` hours → management overhead column
- `PD&R` hours → personal development column
- `Absence` hours → reduce SAH (already handled by vacation system, this is the project-side booking)
**Unassigned hours** = SAH - sum of all categorized hours (calculated, not stored)
## Absence Category and Vacation System
The "Absence & Non Standard" category overlaps with Phase 9 Vacation Pro. Relationship:
- Vacation/illness from the vacation system reduces SAH directly (date-based)
- An "Absence" project may additionally exist for booking non-standard absence hours
- The chargeability calculator deducts both: vacation days (from vacation system) and hours booked to Absence-category projects
This avoids double-counting: vacation days reduce available days in SAH, while Absence-project hours capture remaining non-standard time.
## Seed Data
```sql
INSERT INTO "UtilizationCategory" (code, name, description, "sortOrder", "isDefault") VALUES
('Chg', 'Chargeable', 'Billable client project work', 1, true),
('BD', 'Business Development', 'Sales, proposals, presales activities', 2, false),
('MD&I', 'Market Development and Initiative', 'R&D, innovation, market development', 3, false),
('M&O', 'Management and Operations', 'Internal admin, management overhead', 4, false),
('PD&R', 'Personal Development and Recruitment','Training, hiring, onboarding', 5, false),
('Absence', 'Absence & Non Standard', 'Vacation, illness, non-standard leave (reduces SAH)', 6, false);
```
## Migration
1. Create `UtilizationCategory` table with seed data
2. Add `utilizationCategoryId` to Project (nullable initially)
3. Default all existing projects to "Chg" (or let admin assign manually)
4. Make required once all projects are assigned
## Acceptance Criteria
- [ ] `UtilizationCategory` model with code, name, description
- [ ] Project linked to UtilizationCategory
- [ ] Admin UI for category CRUD (code, name, description editable)
- [ ] Project modal/wizard includes category dropdown
- [ ] Seed data for all 6 categories
- [ ] Chargeability report buckets hours by project category
- [ ] Unassigned hours computed as SAH minus all categorized hours