import type { Prisma } from "@planarchy/db"; import { EstimateStatus, EstimateVersionStatus } from "@planarchy/shared"; import { buildProjectSnapshot, ESTIMATE_DETAIL_INCLUDE, type EstimateDbClient, type EstimateWithDetails, } from "./shared.js"; export interface CloneEstimateInput { sourceEstimateId: string; /** Override name for the clone (defaults to "Copy of ") */ name?: string | undefined; /** Link to a different project */ projectId?: string | undefined; } export async function cloneEstimate( db: EstimateDbClient, input: CloneEstimateInput, ): Promise { const source = await db.estimate.findUnique({ where: { id: input.sourceEstimateId }, include: { versions: { orderBy: { versionNumber: "desc" as const }, take: 1, include: { assumptions: { orderBy: { sortOrder: "asc" as const } }, scopeItems: { orderBy: { sortOrder: "asc" as const } }, demandLines: { orderBy: { createdAt: "asc" as const } }, resourceSnapshots: { orderBy: { displayName: "asc" as const } }, metrics: { orderBy: { createdAt: "asc" as const } }, }, }, }, }); if (!source) { throw new Error("Source estimate not found"); } const sourceVersion = source.versions[0]; if (!sourceVersion) { throw new Error("Source estimate has no versions"); } const cloneName = input.name ?? `Copy of ${source.name}`; const cloneProjectId = input.projectId ?? source.projectId; const projectSnapshot = await buildProjectSnapshot(db, cloneProjectId); const estimate = await db.estimate.create({ data: { ...(cloneProjectId ? { projectId: cloneProjectId } : {}), name: cloneName, ...(source.opportunityId ? { opportunityId: source.opportunityId } : {}), baseCurrency: source.baseCurrency, status: EstimateStatus.DRAFT, latestVersionNumber: 1, versions: { create: { versionNumber: 1, label: "Cloned from v" + sourceVersion.versionNumber, status: EstimateVersionStatus.WORKING, projectSnapshot, assumptions: { create: sourceVersion.assumptions.map((a) => ({ category: a.category, key: a.key, label: a.label, valueType: a.valueType, value: a.value as Prisma.InputJsonValue, sortOrder: a.sortOrder, ...(a.notes ? { notes: a.notes } : {}), })), }, scopeItems: { create: sourceVersion.scopeItems.map((s) => ({ sequenceNo: s.sequenceNo, scopeType: s.scopeType, ...(s.packageCode ? { packageCode: s.packageCode } : {}), name: s.name, ...(s.description ? { description: s.description } : {}), ...(s.scene ? { scene: s.scene } : {}), ...(s.page ? { page: s.page } : {}), ...(s.location ? { location: s.location } : {}), ...(s.assumptionCategory ? { assumptionCategory: s.assumptionCategory } : {}), technicalSpec: s.technicalSpec as Prisma.InputJsonValue, ...(s.frameCount !== null ? { frameCount: s.frameCount } : {}), ...(s.itemCount !== null ? { itemCount: s.itemCount } : {}), ...(s.unitMode ? { unitMode: s.unitMode } : {}), ...(s.internalComments ? { internalComments: s.internalComments } : {}), ...(s.externalComments ? { externalComments: s.externalComments } : {}), sortOrder: s.sortOrder, metadata: s.metadata as Prisma.InputJsonValue, })), }, demandLines: { create: sourceVersion.demandLines.map((l) => ({ ...(l.roleId ? { roleId: l.roleId } : {}), ...(l.resourceId ? { resourceId: l.resourceId } : {}), lineType: l.lineType, name: l.name, ...(l.chapter ? { chapter: l.chapter } : {}), hours: l.hours, ...(l.days !== null ? { days: l.days } : {}), ...(l.fte !== null ? { fte: l.fte } : {}), ...(l.rateSource ? { rateSource: l.rateSource } : {}), costRateCents: l.costRateCents, billRateCents: l.billRateCents, currency: l.currency, costTotalCents: l.costTotalCents, priceTotalCents: l.priceTotalCents, monthlySpread: l.monthlySpread as Prisma.InputJsonValue, staffingAttributes: l.staffingAttributes as Prisma.InputJsonValue, metadata: l.metadata as Prisma.InputJsonValue, })), }, resourceSnapshots: { create: sourceVersion.resourceSnapshots.map((r) => ({ ...(r.resourceId ? { resourceId: r.resourceId } : {}), ...(r.sourceEid ? { sourceEid: r.sourceEid } : {}), displayName: r.displayName, ...(r.chapter ? { chapter: r.chapter } : {}), ...(r.roleId ? { roleId: r.roleId } : {}), currency: r.currency, lcrCents: r.lcrCents, ucrCents: r.ucrCents, ...(r.fte !== null ? { fte: r.fte } : {}), ...(r.location ? { location: r.location } : {}), ...(r.country ? { country: r.country } : {}), ...(r.level ? { level: r.level } : {}), ...(r.workType ? { workType: r.workType } : {}), attributes: r.attributes as Prisma.InputJsonValue, })), }, metrics: { create: sourceVersion.metrics.map((m) => ({ key: m.key, label: m.label, ...(m.metricGroup ? { metricGroup: m.metricGroup } : {}), valueDecimal: Number(m.valueDecimal), ...(m.valueCents !== null ? { valueCents: m.valueCents } : {}), ...(m.currency ? { currency: m.currency } : {}), metadata: m.metadata as Prisma.InputJsonValue, })), }, }, }, }, include: ESTIMATE_DETAIL_INCLUDE, }); return estimate; }