import { approveEstimateVersion, createEstimateRevision, submitEstimateVersion, } from "@capakraken/application"; import type { Prisma } from "@capakraken/db"; import { ApproveEstimateVersionSchema, CreateEstimateRevisionSchema, PermissionKey, SubmitEstimateVersionSchema, } from "@capakraken/shared"; import { TRPCError } from "@trpc/server"; import { managerProcedure, requirePermission } from "../trpc.js"; type EstimateRouterErrorCode = "NOT_FOUND" | "PRECONDITION_FAILED"; type EstimateRouterErrorRule = { code: EstimateRouterErrorCode; messages?: readonly string[]; predicates?: readonly ((message: string) => boolean)[]; }; function rethrowEstimateRouterError( error: unknown, rules: readonly EstimateRouterErrorRule[], ): never { if (!(error instanceof Error)) { throw error; } const matchingRule = rules.find( (rule) => rule.messages?.includes(error.message) === true || rule.predicates?.some((predicate) => predicate(error.message)) === true, ); if (matchingRule) { throw new TRPCError({ code: matchingRule.code, message: error.message, }); } throw error; } export const estimateVersionWorkflowProcedures = { submitVersion: managerProcedure .input(SubmitEstimateVersionSchema) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_PROJECTS); let estimate; try { estimate = await submitEstimateVersion( ctx.db as unknown as Parameters[0], input, ); } catch (error) { rethrowEstimateRouterError(error, [ { code: "NOT_FOUND", messages: ["Estimate not found", "Estimate version not found"], }, { code: "PRECONDITION_FAILED", messages: [ "Estimate has no working version", "Only working versions can be submitted", ], }, ]); } await ctx.db.auditLog.create({ data: { entityType: "Estimate", entityId: estimate.id, action: "UPDATE", ...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}), changes: { after: { id: estimate.id, status: estimate.status, submittedVersionId: estimate.versions.find( (version) => version.status === "SUBMITTED", )?.id, }, } as Prisma.InputJsonValue, }, }); return estimate; }), approveVersion: managerProcedure .input(ApproveEstimateVersionSchema) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_PROJECTS); let estimate; try { estimate = await approveEstimateVersion( ctx.db as unknown as Parameters[0], input, ); } catch (error) { rethrowEstimateRouterError(error, [ { code: "NOT_FOUND", messages: ["Estimate not found", "Estimate version not found"], }, { code: "PRECONDITION_FAILED", messages: [ "Estimate has no submitted version", "Only submitted versions can be approved", ], }, ]); } await ctx.db.auditLog.create({ data: { entityType: "Estimate", entityId: estimate.id, action: "UPDATE", ...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}), changes: { after: { id: estimate.id, status: estimate.status, approvedVersionId: estimate.versions.find( (version) => version.status === "APPROVED", )?.id, }, } as Prisma.InputJsonValue, }, }); return estimate; }), createRevision: managerProcedure .input(CreateEstimateRevisionSchema) .mutation(async ({ ctx, input }) => { requirePermission(ctx, PermissionKey.MANAGE_PROJECTS); let estimate; try { estimate = await createEstimateRevision( ctx.db as unknown as Parameters[0], input, ); } catch (error) { rethrowEstimateRouterError(error, [ { code: "NOT_FOUND", messages: ["Estimate not found", "Estimate version not found"], }, { code: "PRECONDITION_FAILED", messages: [ "Estimate already has a working version", "Estimate has no locked version to revise", "Source version must be locked before creating a revision", ], }, ]); } await ctx.db.auditLog.create({ data: { entityType: "Estimate", entityId: estimate.id, action: "UPDATE", ...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}), changes: { after: { id: estimate.id, status: estimate.status, latestVersionNumber: estimate.latestVersionNumber, workingVersionId: estimate.versions.find( (version) => version.status === "WORKING", )?.id, }, } as Prisma.InputJsonValue, }, }); return estimate; }), };