import type { EstimateDemandLine, EstimateDemandLineCalculationMetadata, EstimateDemandLineRateMode, EstimateDemandSummary, } from "@capakraken/shared"; export interface EstimateDemandLineRateSnapshot { resourceId?: string | null; currency?: string | null; lcrCents: number; ucrCents: number; } export interface EstimateDemandLineForCalculation { resourceId?: string | null | undefined; hours: number; rateSource?: string | null | undefined; costRateCents: number; billRateCents: number; currency?: string | null | undefined; costTotalCents: number; priceTotalCents: number; metadata?: Record | null | undefined; } type ParsedDemandLineMetadata = Record & { calculation?: Partial; }; function parseRateMode(value: unknown): EstimateDemandLineRateMode | undefined { return value === "resource" || value === "manual" ? value : undefined; } function parseDemandLineMetadata( metadata: Record | null | undefined, ): ParsedDemandLineMetadata { const safeMetadata = typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) ? metadata : {}; const rawCalculation = typeof safeMetadata.calculation === "object" && safeMetadata.calculation !== null && !Array.isArray(safeMetadata.calculation) ? (safeMetadata.calculation as Record) : undefined; if (!rawCalculation) { return safeMetadata as ParsedDemandLineMetadata; } const calculation: Partial = {}; const costRateMode = parseRateMode(rawCalculation.costRateMode); if (costRateMode) { calculation.costRateMode = costRateMode; } const billRateMode = parseRateMode(rawCalculation.billRateMode); if (billRateMode) { calculation.billRateMode = billRateMode; } if (rawCalculation.totalMode === "computed") { calculation.totalMode = "computed"; } if (typeof rawCalculation.liveCostRateCents === "number") { calculation.liveCostRateCents = rawCalculation.liveCostRateCents; } if (typeof rawCalculation.liveBillRateCents === "number") { calculation.liveBillRateCents = rawCalculation.liveBillRateCents; } if (typeof rawCalculation.liveCurrency === "string") { calculation.liveCurrency = rawCalculation.liveCurrency; } return { ...safeMetadata, ...(Object.keys(calculation).length > 0 ? { calculation } : {}), }; } function inferRateMode( resourceSnapshot: EstimateDemandLineRateSnapshot | null | undefined, effectiveRateCents: number, liveRateCents: number | undefined, explicitMode: EstimateDemandLineRateMode | undefined, ): EstimateDemandLineRateMode { if (explicitMode) { return explicitMode; } if (!resourceSnapshot || liveRateCents == null) { return "manual"; } return effectiveRateCents === liveRateCents ? "resource" : "manual"; } export function getEstimateDemandLineCalculationMetadata( line: Pick< EstimateDemandLineForCalculation, "resourceId" | "costRateCents" | "billRateCents" | "metadata" >, options?: { resourceSnapshot?: EstimateDemandLineRateSnapshot | null | undefined; }, ): EstimateDemandLineCalculationMetadata { const parsedMetadata = parseDemandLineMetadata(line.metadata); const explicitCalculation = parsedMetadata.calculation; const resourceSnapshot = options?.resourceSnapshot; const liveCostRateCents = resourceSnapshot?.lcrCents; const liveBillRateCents = resourceSnapshot?.ucrCents; return { costRateMode: inferRateMode( resourceSnapshot, line.costRateCents, liveCostRateCents, explicitCalculation?.costRateMode, ), billRateMode: inferRateMode( resourceSnapshot, line.billRateCents, liveBillRateCents, explicitCalculation?.billRateMode, ), totalMode: "computed", liveCostRateCents: liveCostRateCents ?? null, liveBillRateCents: liveBillRateCents ?? null, liveCurrency: resourceSnapshot?.currency ?? null, }; } export function normalizeEstimateDemandLine( line: T, options?: { resourceSnapshot?: EstimateDemandLineRateSnapshot | null | undefined; defaultCurrency?: string; }, ): T { const resourceSnapshot = options?.resourceSnapshot; const calculation = getEstimateDemandLineCalculationMetadata(line, { resourceSnapshot, }); const effectiveCostRateCents = calculation.costRateMode === "resource" && resourceSnapshot ? resourceSnapshot.lcrCents : line.costRateCents; const effectiveBillRateCents = calculation.billRateMode === "resource" && resourceSnapshot ? resourceSnapshot.ucrCents : line.billRateCents; const currency = ((calculation.costRateMode === "resource" || calculation.billRateMode === "resource") && resourceSnapshot?.currency ? resourceSnapshot.currency : line.currency) || resourceSnapshot?.currency || options?.defaultCurrency || "EUR"; const metadata = parseDemandLineMetadata(line.metadata); return { ...line, costRateCents: effectiveCostRateCents, billRateCents: effectiveBillRateCents, currency, costTotalCents: Math.round(line.hours * effectiveCostRateCents), priceTotalCents: Math.round(line.hours * effectiveBillRateCents), metadata: { ...metadata, calculation, }, }; } export function summarizeEstimateDemandLines( demandLines: Pick< EstimateDemandLine, "hours" | "costTotalCents" | "priceTotalCents" >[], ): EstimateDemandSummary { const totalHours = demandLines.reduce((sum, line) => sum + line.hours, 0); const totalCostCents = demandLines.reduce( (sum, line) => sum + line.costTotalCents, 0, ); const totalPriceCents = demandLines.reduce( (sum, line) => sum + line.priceTotalCents, 0, ); const marginCents = totalPriceCents - totalCostCents; const marginPercent = totalPriceCents > 0 ? Math.round((marginCents / totalPriceCents) * 100) : 0; return { totalHours, totalCostCents, totalPriceCents, marginCents, marginPercent, }; }