diff --git a/packages/api/src/__tests__/rate-card-write-support.test.ts b/packages/api/src/__tests__/rate-card-write-support.test.ts index c23416e..0e62806 100644 --- a/packages/api/src/__tests__/rate-card-write-support.test.ts +++ b/packages/api/src/__tests__/rate-card-write-support.test.ts @@ -1,13 +1,61 @@ import { describe, expect, it } from "vitest"; import { + buildRateCardCreateAuditAfter, buildRateCardCreateData, + buildRateCardLineCreateAuditAfter, buildRateCardLineCreateData, buildRateCardLineUpdateData, buildRateCardListWhere, + buildRateCardReplaceLinesAuditAfter, buildRateCardUpdateData, + rateCardCreateInclude, + rateCardDetailInclude, + rateCardLineOrderBy, + rateCardSummaryInclude, } from "../router/rate-card-write-support.js"; describe("rate card write support", () => { + it("exposes shared include definitions", () => { + expect(rateCardSummaryInclude).toEqual({ + _count: { select: { lines: true } }, + client: { select: { id: true, name: true, code: true } }, + }); + + expect(rateCardLineOrderBy).toEqual([ + { chapter: "asc" }, + { seniority: "asc" }, + { createdAt: "asc" }, + ]); + + expect(rateCardDetailInclude).toEqual({ + client: { select: { id: true, name: true, code: true } }, + lines: { + select: { + id: true, + rateCardId: true, + roleId: true, + chapter: true, + seniority: true, + location: true, + workType: true, + serviceGroup: true, + costRateCents: true, + billRateCents: true, + machineRateCents: true, + attributes: true, + role: { select: { id: true, name: true } }, + }, + orderBy: rateCardLineOrderBy, + }, + }); + + expect(rateCardCreateInclude).toEqual({ + lines: { + select: rateCardDetailInclude.lines.select, + }, + }); + }); + it("builds rate card list filters including effective date windows", () => { expect(buildRateCardListWhere({ isActive: true, @@ -97,4 +145,37 @@ describe("rate card write support", () => { attributes: { region: "de" }, }); }); + + it("builds compact audit payloads", () => { + expect(buildRateCardCreateAuditAfter({ + name: "Q1 2026", + currency: "EUR", + lines: [ + { costRateCents: 9500, attributes: {} }, + { costRateCents: 8000, attributes: {} }, + ], + })).toEqual({ + name: "Q1 2026", + currency: "EUR", + lineCount: 2, + }); + + expect(buildRateCardLineCreateAuditAfter({ + rateCardId: "rc_1", + line: { + chapter: "Delivery", + costRateCents: 9500, + billRateCents: 14000, + }, + })).toEqual({ + rateCardId: "rc_1", + chapter: "Delivery", + costRateCents: 9500, + billRateCents: 14000, + }); + + expect(buildRateCardReplaceLinesAuditAfter(3)).toEqual({ + replacedLineCount: 3, + }); + }); }); diff --git a/packages/api/src/router/rate-card-write-support.ts b/packages/api/src/router/rate-card-write-support.ts index 026f372..42f27c9 100644 --- a/packages/api/src/router/rate-card-write-support.ts +++ b/packages/api/src/router/rate-card-write-support.ts @@ -19,6 +19,45 @@ type CreateRateCardLineInput = z.infer; type UpdateRateCardInput = z.infer; type UpdateRateCardLineInput = z.infer; +export const rateCardSummaryInclude = { + _count: { select: { lines: true } }, + client: { select: { id: true, name: true, code: true } }, +} as const; + +export const rateCardLineOrderBy: Prisma.RateCardLineOrderByWithRelationInput[] = [ + { chapter: "asc" }, + { seniority: "asc" }, + { createdAt: "asc" }, +]; + +export const rateCardDetailInclude = { + client: { select: { id: true, name: true, code: true } }, + lines: { + select: { + id: true, + rateCardId: true, + roleId: true, + chapter: true, + seniority: true, + location: true, + workType: true, + serviceGroup: true, + costRateCents: true, + billRateCents: true, + machineRateCents: true, + attributes: true, + role: { select: { id: true, name: true } }, + }, + orderBy: rateCardLineOrderBy, + }, +} as const; + +export const rateCardCreateInclude = { + lines: { + select: rateCardDetailInclude.lines.select, + }, +} as const; + function buildRateCardLineCoreData( line: CreateRateCardLineInput, ) { @@ -134,3 +173,33 @@ export function buildRateCardLineUpdateData( return updateData; } + +export function buildRateCardCreateAuditAfter( + input: Pick, +) { + return { + name: input.name, + currency: input.currency, + lineCount: input.lines.length, + }; +} + +export function buildRateCardLineCreateAuditAfter( + input: { + rateCardId: string; + line: Pick; + }, +) { + return { + rateCardId: input.rateCardId, + costRateCents: input.line.costRateCents, + billRateCents: input.line.billRateCents, + chapter: input.line.chapter ?? null, + }; +} + +export function buildRateCardReplaceLinesAuditAfter( + replacedLineCount: number, +) { + return { replacedLineCount }; +} diff --git a/packages/api/src/router/rate-card.ts b/packages/api/src/router/rate-card.ts index 6853bdb..326d9e8 100644 --- a/packages/api/src/router/rate-card.ts +++ b/packages/api/src/router/rate-card.ts @@ -15,12 +15,18 @@ import { resolveBestRateLineMatch, } from "./rate-card-read.js"; import { + buildRateCardCreateAuditAfter, buildRateCardCreateData, + buildRateCardLineCreateAuditAfter, buildRateCardLineCreateData, buildRateCardLineUpdateData, buildRateCardListWhere, + buildRateCardReplaceLinesAuditAfter, buildRateCardUpdateData, + rateCardCreateInclude, + rateCardDetailInclude, buildRateCardNestedLineCreateData, + rateCardSummaryInclude, } from "./rate-card-write-support.js"; export const rateCardRouter = createTRPCRouter({ @@ -36,10 +42,7 @@ export const rateCardRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { return ctx.db.rateCard.findMany({ where: buildRateCardListWhere(input), - include: { - _count: { select: { lines: true } }, - client: { select: { id: true, name: true, code: true } }, - }, + include: rateCardSummaryInclude, orderBy: [{ isActive: "desc" }, { effectiveFrom: "desc" }, { name: "asc" }], }); }), @@ -50,13 +53,7 @@ export const rateCardRouter = createTRPCRouter({ const rateCard = await findUniqueOrThrow( ctx.db.rateCard.findUnique({ where: { id: input.id }, - include: { - client: { select: { id: true, name: true, code: true } }, - lines: { - select: rateCardLineSelect, - orderBy: [{ chapter: "asc" }, { seniority: "asc" }, { createdAt: "asc" }], - }, - }, + include: rateCardDetailInclude, }), "Rate card", ); @@ -88,9 +85,7 @@ export const rateCardRouter = createTRPCRouter({ const rateCard = await ctx.db.rateCard.create({ data: buildRateCardCreateData(input), - include: { - lines: { select: rateCardLineSelect }, - }, + include: rateCardCreateInclude, }); void createAuditEntry({ @@ -100,7 +95,7 @@ export const rateCardRouter = createTRPCRouter({ entityName: rateCard.name, action: "CREATE", userId: ctx.dbUser?.id, - after: { name, currency, lineCount: lines.length }, + after: buildRateCardCreateAuditAfter({ name, currency, lines }), source: "ui", }); @@ -118,10 +113,7 @@ export const rateCardRouter = createTRPCRouter({ const updated = await ctx.db.rateCard.update({ where: { id: input.id }, data: buildRateCardUpdateData(input.data), - include: { - _count: { select: { lines: true } }, - client: { select: { id: true, name: true, code: true } }, - }, + include: rateCardSummaryInclude, }); void createAuditEntry({ @@ -183,7 +175,10 @@ export const rateCardRouter = createTRPCRouter({ entityName: `${rateCard.name} — ${input.line.chapter ?? "line"}`, action: "CREATE", userId: ctx.dbUser?.id, - after: { rateCardId: input.rateCardId, costRateCents: input.line.costRateCents, billRateCents: input.line.billRateCents }, + after: buildRateCardLineCreateAuditAfter({ + rateCardId: input.rateCardId, + line: input.line, + }), source: "ui", }); @@ -279,7 +274,7 @@ export const rateCardRouter = createTRPCRouter({ entityName: rateCard.name, action: "UPDATE", userId: ctx.dbUser?.id, - after: { replacedLineCount: result.length }, + after: buildRateCardReplaceLinesAuditAfter(result.length), source: "ui", summary: `Replaced all lines with ${result.length} new lines`, });