import { Prisma } from "@capakraken/db"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import type { TRPCContext } from "../trpc.js"; import { type EntityKey, ReportTemplateConfigSchema, validateReportInput, } from "./report-query-config.js"; const ReportTemplateEntity = { RESOURCE: "RESOURCE", PROJECT: "PROJECT", ASSIGNMENT: "ASSIGNMENT", RESOURCE_MONTH: "RESOURCE_MONTH", } as const; type ReportTemplateEntity = (typeof ReportTemplateEntity)[keyof typeof ReportTemplateEntity]; type ReportTemplateRecord = { id: string; name: string; description: string | null; entity: ReportTemplateEntity; config: unknown; isShared: boolean; ownerId: string; updatedAt: Date; }; type ReportTemplateContext = Pick; export const SaveReportTemplateInputSchema = z.object({ id: z.string().optional(), name: z.string().trim().min(1).max(120), description: z.string().trim().max(500).optional(), isShared: z.boolean().default(false), config: ReportTemplateConfigSchema, }); export const DeleteReportTemplateInputSchema = z.object({ id: z.string(), }); export async function listReportTemplates(ctx: ReportTemplateContext) { const ownerId = ctx.dbUser!.id; const templates = await ctx.db.reportTemplate.findMany({ where: { OR: [ { ownerId }, { isShared: true }, ], }, orderBy: [{ name: "asc" }], select: { id: true, name: true, description: true, entity: true, config: true, isShared: true, ownerId: true, updatedAt: true, }, }); return templates.map((template) => mapTemplateRecord(template, ownerId)); } export async function saveReportTemplate( ctx: ReportTemplateContext, input: z.infer, ) { const ownerId = ctx.dbUser!.id; validateReportInput(input.config); const payload = input.config as unknown as Prisma.InputJsonValue; const entity = toTemplateEntity(input.config.entity); const writeData = buildTemplateWriteData(input, entity, payload); if (input.id) { const existing = await ctx.db.reportTemplate.findUnique({ where: { id: input.id }, select: { ownerId: true }, }); if (!existing || existing.ownerId !== ownerId) { throw new TRPCError({ code: "FORBIDDEN", message: "Template cannot be updated" }); } return ctx.db.reportTemplate.update({ where: { id: input.id }, data: writeData, select: { id: true, updatedAt: true }, }); } return ctx.db.reportTemplate.upsert({ where: { ownerId_name: { ownerId, name: input.name, }, }, update: omitNameFromTemplateWriteData(writeData), create: { ownerId, ...writeData, }, select: { id: true, updatedAt: true }, }); } export async function deleteReportTemplate( ctx: ReportTemplateContext, input: z.infer, ) { const ownerId = ctx.dbUser!.id; const existing = await ctx.db.reportTemplate.findUnique({ where: { id: input.id }, select: { ownerId: true }, }); if (!existing || existing.ownerId !== ownerId) { throw new TRPCError({ code: "FORBIDDEN", message: "Template cannot be deleted" }); } await ctx.db.reportTemplate.delete({ where: { id: input.id } }); return { ok: true }; } function buildTemplateWriteData( input: z.infer, entity: ReportTemplateEntity, config: Prisma.InputJsonValue, ) { return { name: input.name, entity, config, isShared: input.isShared, ...(input.description !== undefined ? { description: input.description } : {}), }; } function omitNameFromTemplateWriteData( data: ReturnType, ) { const { name: _name, ...updateData } = data; return updateData; } function mapTemplateRecord(template: ReportTemplateRecord, ownerId: string) { return { id: template.id, name: template.name, description: template.description, entity: fromTemplateEntity(template.entity), config: ReportTemplateConfigSchema.parse(template.config), isShared: template.isShared, isOwner: template.ownerId === ownerId, updatedAt: template.updatedAt, }; } function toTemplateEntity(entity: EntityKey): ReportTemplateEntity { switch (entity) { case "resource": return ReportTemplateEntity.RESOURCE; case "project": return ReportTemplateEntity.PROJECT; case "assignment": return ReportTemplateEntity.ASSIGNMENT; case "resource_month": return ReportTemplateEntity.RESOURCE_MONTH; default: throw new TRPCError({ code: "BAD_REQUEST", message: `Unknown entity: ${entity}` }); } } function fromTemplateEntity(entity: ReportTemplateEntity): EntityKey { switch (entity) { case ReportTemplateEntity.RESOURCE: return "resource"; case ReportTemplateEntity.PROJECT: return "project"; case ReportTemplateEntity.ASSIGNMENT: return "assignment"; case ReportTemplateEntity.RESOURCE_MONTH: return "resource_month"; default: throw new TRPCError({ code: "BAD_REQUEST", message: `Unknown entity: ${entity}` }); } }