import type { Prisma } from "@capakraken/db"; import type { EffortRuleInput, ScopeItemInput, } from "@capakraken/engine"; import { CreateEffortRuleSetSchema, UpdateEffortRuleSetSchema, } from "@capakraken/shared"; import { z } from "zod"; type CreateEffortRuleSetInput = z.infer; type UpdateEffortRuleSetInput = z.infer; type EffortRuleRowInput = | CreateEffortRuleSetInput["rules"][number] | NonNullable[number]; type EffortRuleRecord = { scopeType: string; discipline: string; chapter: string | null; unitMode: string; hoursPerUnit: number; sortOrder: number; }; type ScopeItemRecord = { name: string; scopeType: string; frameCount: number | null; itemCount: number | null; unitMode: string | null; }; type DemandLineRuleSetRecord = { id: string; name: string; }; type ExpandedEffortLineRecord = { scopeItemName: string; discipline: string; chapter?: string | null; hours: number; unitMode: string; unitCount: number; hoursPerUnit: number; }; export const effortRuleInclude = { rules: { orderBy: { sortOrder: "asc" as const } }, } as const; function buildEffortRuleRow(input: EffortRuleRowInput, index: number) { return { scopeType: input.scopeType, discipline: input.discipline, ...(input.chapter ? { chapter: input.chapter } : {}), unitMode: input.unitMode, hoursPerUnit: input.hoursPerUnit, ...(input.description ? { description: input.description } : {}), sortOrder: input.sortOrder ?? index, }; } export function buildEffortRuleNestedCreateRows( rules: EffortRuleRowInput[], ): Prisma.EffortRuleUncheckedCreateWithoutRuleSetInput[] { return rules.map((rule, index) => ({ ...buildEffortRuleRow(rule, index), })); } export function buildEffortRuleCreateManyRows( rules: EffortRuleRowInput[], ruleSetId: string, ): Prisma.EffortRuleCreateManyInput[] { return rules.map((rule, index) => ({ ruleSetId, ...buildEffortRuleRow(rule, index), })); } export function buildEffortRuleSetCreateData( input: CreateEffortRuleSetInput, ): Prisma.EffortRuleSetCreateInput { return { name: input.name, ...(input.description ? { description: input.description } : {}), isDefault: input.isDefault, rules: { create: buildEffortRuleNestedCreateRows(input.rules), }, }; } export function buildEffortRuleSetUpdateData( input: Omit, ): Prisma.EffortRuleSetUncheckedUpdateInput { return { ...(input.name !== undefined ? { name: input.name } : {}), ...(input.description !== undefined ? { description: input.description } : {}), ...(input.isDefault !== undefined ? { isDefault: input.isDefault } : {}), }; } export function toScopeItemInputs( scopeItems: ScopeItemRecord[], ): ScopeItemInput[] { return scopeItems.map((scopeItem) => ({ name: scopeItem.name, scopeType: scopeItem.scopeType as ScopeItemInput["scopeType"], frameCount: scopeItem.frameCount, itemCount: scopeItem.itemCount, unitMode: (scopeItem.unitMode ?? null) as Exclude, }) as ScopeItemInput); } export function toEffortRuleEngineInputs( rules: EffortRuleRecord[], ): EffortRuleInput[] { return rules.map((rule) => ({ scopeType: rule.scopeType as EffortRuleInput["scopeType"], discipline: rule.discipline, chapter: rule.chapter, unitMode: rule.unitMode as EffortRuleInput["unitMode"], hoursPerUnit: rule.hoursPerUnit, sortOrder: rule.sortOrder, })); } export function buildEstimateDemandLineRows(input: { estimateVersionId: string; currency: string; ruleSet: DemandLineRuleSetRecord; lines: ExpandedEffortLineRecord[]; }): Prisma.EstimateDemandLineUncheckedCreateInput[] { return input.lines.map((line) => ({ estimateVersionId: input.estimateVersionId, lineType: "LABOR", name: `${line.discipline} — ${line.scopeItemName}`, ...(line.chapter ? { chapter: line.chapter } : {}), hours: line.hours, costRateCents: 0, billRateCents: 0, currency: input.currency, costTotalCents: 0, priceTotalCents: 0, monthlySpread: {}, staffingAttributes: {}, metadata: { effortRule: { ruleSetId: input.ruleSet.id, ruleSetName: input.ruleSet.name, discipline: line.discipline, unitMode: line.unitMode, unitCount: line.unitCount, hoursPerUnit: line.hoursPerUnit, }, }, })); }