import type { Prisma } from "@capakraken/db"; import type { ExperienceMultiplierRule as EngineRule } from "@capakraken/engine"; import { CreateExperienceMultiplierSetSchema, UpdateExperienceMultiplierSetSchema, } from "@capakraken/shared"; import { z } from "zod"; type CreateExperienceMultiplierSetInput = z.infer; type UpdateExperienceMultiplierSetInput = z.infer; type ExperienceMultiplierRuleRowInput = | CreateExperienceMultiplierSetInput["rules"][number] | NonNullable[number]; type ExperienceMultiplierRuleRecord = { chapter: string | null; location: string | null; level: string | null; costMultiplier: number; billMultiplier: number; shoringRatio: number | null; additionalEffortRatio: number | null; description: string | null; }; type DemandLineRecord = { id: string; name: string; chapter: string | null; costRateCents: number; billRateCents: number; hours: number; metadata: unknown; staffingAttributes: unknown; }; type MultiplierSetRecord = { id: string; name: string; }; type ExperienceMultiplierResultRecord = { adjustedCostRateCents: number; adjustedBillRateCents: number; adjustedHours: number; appliedRules: string[]; }; export const experienceMultiplierRuleInclude = { rules: { orderBy: { sortOrder: "asc" as const } }, } as const; function buildExperienceMultiplierRuleRow( input: ExperienceMultiplierRuleRowInput, index: number, ) { return { ...(input.chapter ? { chapter: input.chapter } : {}), ...(input.location ? { location: input.location } : {}), ...(input.level ? { level: input.level } : {}), costMultiplier: input.costMultiplier, billMultiplier: input.billMultiplier, ...(input.shoringRatio !== undefined ? { shoringRatio: input.shoringRatio } : {}), ...(input.additionalEffortRatio !== undefined ? { additionalEffortRatio: input.additionalEffortRatio } : {}), ...(input.description ? { description: input.description } : {}), sortOrder: input.sortOrder ?? index, }; } export function buildExperienceMultiplierNestedCreateRows( rules: ExperienceMultiplierRuleRowInput[], ): Prisma.ExperienceMultiplierRuleUncheckedCreateWithoutMultiplierSetInput[] { return rules.map((rule, index) => ({ ...buildExperienceMultiplierRuleRow(rule, index), })); } export function buildExperienceMultiplierCreateManyRows( rules: ExperienceMultiplierRuleRowInput[], multiplierSetId: string, ): Prisma.ExperienceMultiplierRuleCreateManyInput[] { return rules.map((rule, index) => ({ multiplierSetId, ...buildExperienceMultiplierRuleRow(rule, index), })); } export function buildExperienceMultiplierSetCreateData( input: CreateExperienceMultiplierSetInput, ): Prisma.ExperienceMultiplierSetCreateInput { return { name: input.name, ...(input.description ? { description: input.description } : {}), isDefault: input.isDefault, rules: { create: buildExperienceMultiplierNestedCreateRows(input.rules), }, }; } export function buildExperienceMultiplierSetUpdateData( input: Omit, ): Prisma.ExperienceMultiplierSetUncheckedUpdateInput { return { ...(input.name !== undefined ? { name: input.name } : {}), ...(input.description !== undefined ? { description: input.description } : {}), ...(input.isDefault !== undefined ? { isDefault: input.isDefault } : {}), }; } export function toExperienceMultiplierEngineRules( dbRules: ExperienceMultiplierRuleRecord[], ): EngineRule[] { return dbRules.map((rule) => ({ ...(rule.chapter != null ? { chapter: rule.chapter } : {}), ...(rule.location != null ? { location: rule.location } : {}), ...(rule.level != null ? { level: rule.level } : {}), costMultiplier: rule.costMultiplier, billMultiplier: rule.billMultiplier, ...(rule.shoringRatio != null ? { shoringRatio: rule.shoringRatio } : {}), ...(rule.additionalEffortRatio != null ? { additionalEffortRatio: rule.additionalEffortRatio } : {}), ...(rule.description != null ? { description: rule.description } : {}), })); } export function buildExperienceMultiplierInput( line: DemandLineRecord, ) { return { costRateCents: line.costRateCents, billRateCents: line.billRateCents, hours: line.hours, ...(line.chapter != null ? { chapter: line.chapter } : {}), ...(line.metadata != null && typeof line.metadata === "object" && "location" in (line.metadata as Record) ? { location: (line.metadata as Record).location as string } : {}), ...(line.staffingAttributes != null && typeof line.staffingAttributes === "object" && "level" in (line.staffingAttributes as Record) ? { level: (line.staffingAttributes as Record).level as string } : {}), }; } export function hasExperienceMultiplierChanges( line: Pick, result: ExperienceMultiplierResultRecord, ): boolean { return ( result.adjustedCostRateCents !== line.costRateCents || result.adjustedBillRateCents !== line.billRateCents || result.adjustedHours !== line.hours ); } export function buildExperienceMultiplierDemandLineUpdateData(input: { line: DemandLineRecord; result: ExperienceMultiplierResultRecord; multiplierSet: MultiplierSetRecord; }): Prisma.EstimateDemandLineUncheckedUpdateInput { const newCostTotal = Math.round(input.result.adjustedCostRateCents * input.result.adjustedHours); const newPriceTotal = Math.round(input.result.adjustedBillRateCents * input.result.adjustedHours); return { costRateCents: input.result.adjustedCostRateCents, billRateCents: input.result.adjustedBillRateCents, hours: input.result.adjustedHours, costTotalCents: newCostTotal, priceTotalCents: newPriceTotal, metadata: { ...(typeof input.line.metadata === "object" && input.line.metadata !== null ? input.line.metadata as Record : {}), experienceMultiplier: { setId: input.multiplierSet.id, setName: input.multiplierSet.name, appliedRules: input.result.appliedRules, originalCostRateCents: input.line.costRateCents, originalBillRateCents: input.line.billRateCents, originalHours: input.line.hours, }, }, }; }