import { CreateRateCardLineSchema, CreateRateCardSchema, UpdateRateCardLineSchema, UpdateRateCardSchema, } from "@capakraken/shared"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; import { makeAuditLogger } from "../lib/audit-helpers.js"; import type { TRPCContext } from "../trpc.js"; import { lookupBestRateMatch, rateCardLineSelect, resolveBestRate, resolveBestRateLineMatch, } from "./rate-card-read.js"; import { buildRateCardCreateAuditAfter, buildRateCardCreateData, buildRateCardLineCreateAuditAfter, buildRateCardLineCreateData, buildRateCardLineUpdateData, buildRateCardListWhere, buildRateCardNestedLineCreateData, buildRateCardReplaceLinesAuditAfter, buildRateCardUpdateData, rateCardCreateInclude, rateCardDetailInclude, rateCardSummaryInclude, } from "./rate-card-write-support.js"; type RateCardProcedureContext = Pick; export const rateCardListInputSchema = z.object({ isActive: z.boolean().optional(), search: z.string().optional(), clientId: z.string().optional(), effectiveAt: z.coerce.date().optional(), }).optional(); export const rateCardByIdInputSchema = z.object({ id: z.string() }); export const rateCardLookupBestMatchInputSchema = z.object({ clientId: z.string().optional(), chapter: z.string().optional(), managementLevelId: z.string().optional(), roleName: z.string().optional(), seniority: z.string().optional(), }); export const rateCardResolveBestRateInputSchema = z.object({ resourceId: z.string().optional(), roleName: z.string().optional(), date: z.coerce.date().optional(), }); export const rateCardUpdateInputSchema = z.object({ id: z.string(), data: UpdateRateCardSchema, }); export const rateCardAddLineInputSchema = z.object({ rateCardId: z.string(), line: CreateRateCardLineSchema, }); export const rateCardUpdateLineInputSchema = z.object({ lineId: z.string(), data: UpdateRateCardLineSchema, }); export const rateCardDeleteLineInputSchema = z.object({ lineId: z.string() }); export const rateCardReplaceLinesInputSchema = z.object({ rateCardId: z.string(), lines: z.array(CreateRateCardLineSchema), }); export const rateCardResolveLineInputSchema = z.object({ rateCardId: z.string(), roleId: z.string().optional(), chapter: z.string().optional(), location: z.string().optional(), seniority: z.string().optional(), workType: z.string().optional(), }); type RateCardListInput = z.infer; type RateCardByIdInput = z.infer; type RateCardLookupBestMatchInput = z.infer; type RateCardResolveBestRateInput = z.infer; type RateCardCreateInput = z.infer; type RateCardUpdateInput = z.infer; type RateCardDeactivateInput = z.infer; type RateCardAddLineInput = z.infer; type RateCardUpdateLineInput = z.infer; type RateCardDeleteLineInput = z.infer; type RateCardReplaceLinesInput = z.infer; type RateCardResolveLineInput = z.infer; export async function listRateCards( ctx: RateCardProcedureContext, input: RateCardListInput, ) { return ctx.db.rateCard.findMany({ where: buildRateCardListWhere(input), include: rateCardSummaryInclude, orderBy: [{ isActive: "desc" }, { effectiveFrom: "desc" }, { name: "asc" }], }); } export async function getRateCardById( ctx: RateCardProcedureContext, input: RateCardByIdInput, ) { return findUniqueOrThrow( ctx.db.rateCard.findUnique({ where: { id: input.id }, include: rateCardDetailInclude, }), "Rate card", ); } export async function lookupRateCardBestMatch( ctx: RateCardProcedureContext, input: RateCardLookupBestMatchInput, ) { return lookupBestRateMatch(ctx.db, input); } export async function resolveRateCard( ctx: RateCardProcedureContext, input: RateCardResolveBestRateInput, ) { return resolveBestRate(ctx.db, input); } export async function createRateCard( ctx: RateCardProcedureContext, input: RateCardCreateInput, ) { const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id); const { lines, name, currency } = input; const rateCard = await ctx.db.rateCard.create({ data: buildRateCardCreateData(input), include: rateCardCreateInclude, }); audit({ entityType: "RateCard", entityId: rateCard.id, entityName: rateCard.name, action: "CREATE", after: buildRateCardCreateAuditAfter({ name, currency, lines }), }); return rateCard; } export async function updateRateCard( ctx: RateCardProcedureContext, input: RateCardUpdateInput, ) { const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id); const before = await findUniqueOrThrow( ctx.db.rateCard.findUnique({ where: { id: input.id } }), "Rate card", ); const updated = await ctx.db.rateCard.update({ where: { id: input.id }, data: buildRateCardUpdateData(input.data), include: rateCardSummaryInclude, }); audit({ entityType: "RateCard", entityId: input.id, entityName: updated.name, action: "UPDATE", before: before as unknown as Record, after: updated as unknown as Record, }); return updated; } export async function deactivateRateCard( ctx: RateCardProcedureContext, input: RateCardDeactivateInput, ) { const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id); const deactivated = await ctx.db.rateCard.update({ where: { id: input.id }, data: { isActive: false }, }); audit({ entityType: "RateCard", entityId: input.id, entityName: deactivated.name, action: "DELETE", summary: "Deactivated rate card", }); return deactivated; } export async function addRateCardLine( ctx: RateCardProcedureContext, input: RateCardAddLineInput, ) { const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id); const rateCard = await findUniqueOrThrow( ctx.db.rateCard.findUnique({ where: { id: input.rateCardId } }), "Rate card", ); const line = await ctx.db.rateCardLine.create({ data: buildRateCardLineCreateData(input.rateCardId, input.line), select: rateCardLineSelect, }); audit({ entityType: "RateCardLine", entityId: line.id, entityName: `${rateCard.name} - ${input.line.chapter ?? "line"}`, action: "CREATE", after: buildRateCardLineCreateAuditAfter({ rateCardId: input.rateCardId, line: input.line, }), }); return line; } export async function updateRateCardLine( ctx: RateCardProcedureContext, input: RateCardUpdateLineInput, ) { const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id); const before = await findUniqueOrThrow( ctx.db.rateCardLine.findUnique({ where: { id: input.lineId } }), "Rate card line", ); const updated = await ctx.db.rateCardLine.update({ where: { id: input.lineId }, data: buildRateCardLineUpdateData(input.data), select: rateCardLineSelect, }); audit({ entityType: "RateCardLine", entityId: input.lineId, action: "UPDATE", before: before as unknown as Record, after: updated as unknown as Record, }); return updated; } export async function deleteRateCardLine( ctx: RateCardProcedureContext, input: RateCardDeleteLineInput, ) { const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id); const line = await findUniqueOrThrow( ctx.db.rateCardLine.findUnique({ where: { id: input.lineId } }), "Rate card line", ); await ctx.db.rateCardLine.delete({ where: { id: input.lineId } }); audit({ entityType: "RateCardLine", entityId: input.lineId, action: "DELETE", before: line as unknown as Record, }); return { deleted: true }; } export async function replaceRateCardLines( ctx: RateCardProcedureContext, input: RateCardReplaceLinesInput, ) { const audit = makeAuditLogger(ctx.db, ctx.dbUser?.id); const rateCard = await findUniqueOrThrow( ctx.db.rateCard.findUnique({ where: { id: input.rateCardId } }), "Rate card", ); const result = await ctx.db.$transaction(async (tx) => { await tx.rateCardLine.deleteMany({ where: { rateCardId: input.rateCardId } }); return Promise.all( input.lines.map((line) => tx.rateCardLine.create({ data: { rateCardId: input.rateCardId, ...buildRateCardNestedLineCreateData(line), }, select: rateCardLineSelect, }), ), ); }); audit({ entityType: "RateCard", entityId: input.rateCardId, entityName: rateCard.name, action: "UPDATE", after: buildRateCardReplaceLinesAuditAfter(result.length), summary: `Replaced all lines with ${result.length} new lines`, }); return result; } export async function resolveRateCardLine( ctx: RateCardProcedureContext, input: RateCardResolveLineInput, ) { const { rateCardId, ...criteria } = input; const lines = await ctx.db.rateCardLine.findMany({ where: { rateCardId }, select: rateCardLineSelect, }); return resolveBestRateLineMatch(lines, criteria); }