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:
@@ -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 {
|
||||
|
||||
@@ -293,6 +293,7 @@ async function main() {
|
||||
await prisma.resourceRole.deleteMany({});
|
||||
await prisma.vacation.deleteMany({});
|
||||
await prisma.vacationEntitlement.deleteMany({});
|
||||
await prisma.calculationRule.deleteMany({});
|
||||
await prisma.project.deleteMany({});
|
||||
await prisma.resource.deleteMany({});
|
||||
await prisma.role.deleteMany({});
|
||||
@@ -1215,6 +1216,40 @@ async function main() {
|
||||
}
|
||||
console.warn(`Vacations: ${vacationCount} created`);
|
||||
|
||||
// ── Calculation Rules (default set) ──────────────────────────────────────────
|
||||
await prisma.calculationRule.createMany({
|
||||
data: [
|
||||
{
|
||||
name: "Urlaub — Person chargeable, Projekt nicht belastet",
|
||||
description: "Vacation days count toward chargeability but are not charged to the project.",
|
||||
triggerType: "VACATION",
|
||||
costEffect: "ZERO",
|
||||
chargeabilityEffect: "COUNT",
|
||||
priority: 0,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
name: "Krankheit — Person chargeable, Projekt nicht belastet",
|
||||
description: "Sick days count toward chargeability but are not charged to the project.",
|
||||
triggerType: "SICK",
|
||||
costEffect: "ZERO",
|
||||
chargeabilityEffect: "COUNT",
|
||||
priority: 0,
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
name: "Feiertag — kein Effekt",
|
||||
description: "Public holidays are neither chargeable nor charged to projects.",
|
||||
triggerType: "PUBLIC_HOLIDAY",
|
||||
costEffect: "ZERO",
|
||||
chargeabilityEffect: "SKIP",
|
||||
priority: 0,
|
||||
isActive: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
console.warn("Calculation rules: 3 default rules created");
|
||||
|
||||
console.warn("Seed complete!");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user