import { countPlanningEntries } from "@capakraken/application"; import { FieldType, ProjectStatus } from "@capakraken/shared"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { paginate, PaginationInputSchema } from "../db/pagination.js"; import type { TRPCContext } from "../trpc.js"; import { buildDynamicFieldWhereClauses } from "./custom-field-filters.js"; import { loadProjectPlanningReadModel } from "./project-planning-read-model.js"; import { getProjectShoringRatio } from "./project-shoring-ratio.js"; export const ProjectListInputSchema = PaginationInputSchema.extend({ status: z.nativeEnum(ProjectStatus).optional(), search: z.string().optional(), customFieldFilters: z.array(z.object({ key: z.string(), value: z.string(), type: z.nativeEnum(FieldType), })).optional(), }); export const ProjectIdInputSchema = z.object({ id: z.string(), }); export const ProjectShoringRatioInputSchema = z.object({ projectId: z.string(), }); type ProjectProcedureContext = Pick; export async function listProjects( ctx: ProjectProcedureContext, input: z.infer, ) { const { status, search, cursor, customFieldFilters } = input; const cfConditions = buildDynamicFieldWhereClauses(customFieldFilters) .map((dynamicFields) => ({ dynamicFields })); // eslint-disable-next-line @typescript-eslint/no-explicit-any const where: any = { ...(status ? { status } : {}), ...(search ? { OR: [ { name: { contains: search, mode: "insensitive" as const } }, { shortCode: { contains: search, mode: "insensitive" as const } }, ], } : {}), ...(cfConditions.length > 0 ? { AND: cfConditions } : {}), }; const whereWithCursor = cursor ? { ...where, id: { gt: cursor } } : where; const result = await paginate( ({ skip, take }) => ctx.db.project.findMany({ where: whereWithCursor, skip, take, orderBy: [{ startDate: "asc" }, { id: "asc" }], }), () => ctx.db.project.count({ where }), input, ); const { countsByProjectId } = await countPlanningEntries(ctx.db, { projectIds: result.items.map((project) => project.id), }); return { projects: result.items.map((project) => ({ ...project, _count: { allocations: countsByProjectId.get(project.id) ?? 0, }, })), total: result.total, page: result.page, limit: result.limit, nextCursor: result.nextCursor, }; } export async function getProjectById( ctx: ProjectProcedureContext, input: z.infer, ) { const [project, planningRead] = await Promise.all([ ctx.db.project.findUnique({ where: { id: input.id }, include: { blueprint: true }, }), loadProjectPlanningReadModel(ctx.db, { projectId: input.id }), ]); if (!project) { throw new TRPCError({ code: "NOT_FOUND", message: "Project not found" }); } return { ...project, allocations: planningRead.readModel.assignments, demands: planningRead.readModel.demands, assignments: planningRead.readModel.assignments, }; } export async function getProjectShoringRatioData( ctx: ProjectProcedureContext, input: z.infer, ) { return getProjectShoringRatio(ctx.db, input.projectId); }