feat: calculation rules engine for decoupled cost attribution and chargeability

Introduces an admin-configurable rules engine that determines per-day cost
attribution (CHARGE/ZERO/REDUCE) and chargeability reporting (COUNT/SKIP)
for absence types (sick, vacation, public holiday). Includes shared types,
Zod schemas, Prisma model, rule matching with specificity scoring, default
rules, calculator integration, CRUD API router, seed data, chargeability
report integration, and admin UI.

283/283 engine tests, 209/209 API tests, 0 TS errors.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-15 09:29:12 +01:00
parent a83edb2f9d
commit 368fd6d7ad
23 changed files with 1753 additions and 53 deletions
+50
View File
@@ -135,6 +135,24 @@ enum DispoImportSourceKind {
ROSTER
}
enum AbsenceTrigger {
SICK
VACATION
PUBLIC_HOLIDAY
CUSTOM
}
enum CostEffect {
CHARGE
ZERO
REDUCE
}
enum ChargeabilityEffect {
COUNT
SKIP
}
enum DispoStagedRecordType {
RESOURCE
CLIENT
@@ -805,6 +823,7 @@ model Project {
demandRequirements DemandRequirement[]
assignments Assignment[]
estimates Estimate[]
calculationRules CalculationRule[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -1315,6 +1334,37 @@ model SystemSettings {
@@map("system_settings")
}
// ─── Calculation Rules ────────────────────────────────────────────────────────
model CalculationRule {
id String @id @default(cuid())
name String
description String?
// ── Matching ──
triggerType AbsenceTrigger
projectId String?
orderType OrderType?
// ── Effects ──
costEffect CostEffect
costReductionPercent Int? // only for REDUCE (0-100)
chargeabilityEffect ChargeabilityEffect
// ── Ordering ──
priority Int @default(0)
isActive Boolean @default(true)
project Project? @relation(fields: [projectId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([triggerType, isActive])
@@index([projectId])
@@map("calculation_rules")
}
// ─── Audit Log ────────────────────────────────────────────────────────────────
model AuditLog {