import { CreateHolidayCalendarEntrySchema, CreateHolidayCalendarSchema, type HolidayCalendarScopeInput, PreviewResolvedHolidaysSchema, UpdateHolidayCalendarEntrySchema, UpdateHolidayCalendarSchema, } from "@capakraken/shared"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { findUniqueOrThrow } from "../db/helpers.js"; import { createAuditEntry } from "../lib/audit.js"; import { asHolidayResolverDb, getResolvedCalendarHolidays } from "../lib/holiday-availability.js"; import { createTRPCRouter, adminProcedure, protectedProcedure, type TRPCContext } from "../trpc.js"; type HolidayCalendarScope = HolidayCalendarScopeInput; type HolidayReadContext = Pick; const HOLIDAY_SCOPE = { COUNTRY: "COUNTRY", STATE: "STATE", CITY: "CITY", } as const satisfies Record; type HolidayCalendarDb = TRPCContext["db"] & { holidayCalendar: { findFirst: (args: unknown) => Promise<{ id: string } | null>; findMany: (args: unknown) => Promise; findUnique: (args: unknown) => Promise; create: (args: unknown) => Promise; update: (args: unknown) => Promise; delete: (args: unknown) => Promise; }; holidayCalendarEntry: { findFirst: (args: unknown) => Promise<{ id: string } | null>; findUnique: (args: unknown) => Promise; create: (args: unknown) => Promise; update: (args: unknown) => Promise; delete: (args: unknown) => Promise; }; }; function asHolidayCalendarDb(db: TRPCContext["db"]): HolidayCalendarDb { return db as unknown as HolidayCalendarDb; } function clampDate(date: Date): Date { const value = new Date(date); value.setUTCHours(0, 0, 0, 0); return value; } function fmtDate(value: Date | null | undefined): string | null { return value ? value.toISOString().slice(0, 10) : null; } function formatIsoDate(value: Date): string { return value.toISOString().slice(0, 10); } function formatHolidayCalendarEntryDetail(entry: { id: string; date: Date; name: string; isRecurringAnnual?: boolean | null; source?: string | null; }) { return { id: entry.id, date: formatIsoDate(entry.date), name: entry.name, isRecurringAnnual: entry.isRecurringAnnual ?? false, source: entry.source ?? null, }; } function formatHolidayCalendarDetail(calendar: { id: string; name: string; scopeType: string; stateCode?: string | null; isActive?: boolean | null; priority?: number | null; country?: { id: string; code: string; name: string } | null; metroCity?: { id: string; name: string } | null; _count?: { entries?: number | null } | null; entries?: Array<{ id: string; date: Date; name: string; isRecurringAnnual?: boolean | null; source?: string | null; }> | null; }) { const entries = calendar.entries?.map(formatHolidayCalendarEntryDetail) ?? []; return { id: calendar.id, name: calendar.name, scopeType: calendar.scopeType, stateCode: calendar.stateCode ?? null, isActive: calendar.isActive ?? true, priority: calendar.priority ?? 0, country: calendar.country ? { id: calendar.country.id, code: calendar.country.code, name: calendar.country.name, } : null, metroCity: calendar.metroCity ? { id: calendar.metroCity.id, name: calendar.metroCity.name, } : null, entryCount: calendar._count?.entries ?? entries.length, entries, }; } function canManageHolidayResourceReads(ctx: { dbUser: { systemRole: string } | null }): boolean { const role = ctx.dbUser?.systemRole; return role === "ADMIN" || role === "MANAGER"; } async function findOwnedHolidayResourceId(ctx: HolidayReadContext): Promise { if (!ctx.dbUser?.id) { return null; } if (!ctx.db.resource || typeof ctx.db.resource.findFirst !== "function") { return null; } const resource = await ctx.db.resource.findFirst({ where: { userId: ctx.dbUser.id }, select: { id: true }, }); return resource?.id ?? null; } async function assertCanReadHolidayResource( ctx: HolidayReadContext, resourceId: string, ): Promise { if (canManageHolidayResourceReads(ctx)) { return; } const ownedResourceId = await findOwnedHolidayResourceId(ctx); if (!ownedResourceId || ownedResourceId !== resourceId) { throw new TRPCError({ code: "FORBIDDEN", message: "You can only view holiday data for your own resource", }); } } function formatResolvedHolidayDetail(holiday: { date: string; name: string; scopeType: string; calendarName: string; sourceType: string; }) { return { date: holiday.date, name: holiday.name, scope: holiday.scopeType, calendarName: holiday.calendarName, sourceType: holiday.sourceType, }; } function summarizeResolvedHolidaysDetail(holidays: Array<{ date: string; name: string; scope: string; calendarName: string; sourceType: string; }>) { const byScope = new Map(); const bySourceType = new Map(); const byCalendar = new Map(); for (const holiday of holidays) { byScope.set(holiday.scope, (byScope.get(holiday.scope) ?? 0) + 1); bySourceType.set(holiday.sourceType, (bySourceType.get(holiday.sourceType) ?? 0) + 1); byCalendar.set(holiday.calendarName, (byCalendar.get(holiday.calendarName) ?? 0) + 1); } return { byScope: [...byScope.entries()] .sort(([left], [right]) => left.localeCompare(right)) .map(([scope, count]) => ({ scope, count })), bySourceType: [...bySourceType.entries()] .sort(([left], [right]) => left.localeCompare(right)) .map(([sourceType, count]) => ({ sourceType, count })), byCalendar: [...byCalendar.entries()] .sort(([left], [right]) => left.localeCompare(right)) .map(([calendarName, count]) => ({ calendarName, count })), }; } const ResolveHolidaysInputSchema = z.object({ periodStart: z.coerce.date(), periodEnd: z.coerce.date(), countryId: z.string().optional(), countryCode: z.string().trim().min(1).optional(), stateCode: z.string().trim().min(1).optional(), metroCityId: z.string().optional(), metroCityName: z.string().trim().min(1).optional(), }).superRefine((input, issueCtx) => { if (!input.countryId && !input.countryCode) { issueCtx.addIssue({ code: z.ZodIssueCode.custom, message: "Either countryId or countryCode is required.", path: ["countryId"], }); } if (input.periodEnd < input.periodStart) { issueCtx.addIssue({ code: z.ZodIssueCode.custom, message: "periodEnd must be on or after periodStart.", path: ["periodEnd"], }); } }); const ResolveResourceHolidaysInputSchema = z.object({ resourceId: z.string(), periodStart: z.coerce.date(), periodEnd: z.coerce.date(), }).superRefine((input, issueCtx) => { if (input.periodEnd < input.periodStart) { issueCtx.addIssue({ code: z.ZodIssueCode.custom, message: "periodEnd must be on or after periodStart.", path: ["periodEnd"], }); } }); async function readCalendarsSnapshot( ctx: HolidayReadContext, input?: { includeInactive?: boolean | undefined; countryCode?: string | undefined; scopeType?: "COUNTRY" | "STATE" | "CITY" | undefined; stateCode?: string | undefined; metroCity?: string | undefined; }, ) { const db = asHolidayCalendarDb(ctx.db); const where = { ...(input?.includeInactive ? {} : { isActive: true }), ...(input?.countryCode ? { country: { code: { equals: input.countryCode.trim().toUpperCase(), mode: "insensitive" as const } }, } : {}), ...(input?.scopeType ? { scopeType: input.scopeType } : {}), ...(input?.stateCode ? { stateCode: input.stateCode.trim().toUpperCase() } : {}), ...(input?.metroCity ? { metroCity: { name: { contains: input.metroCity.trim(), mode: "insensitive" as const } }, } : {}), }; return db.holidayCalendar.findMany({ where, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, _count: { select: { entries: true } }, entries: { orderBy: [{ date: "asc" }, { name: "asc" }] }, }, orderBy: [ { country: { name: "asc" } }, { scopeType: "asc" }, { priority: "desc" }, { name: "asc" }, ], }); } async function readCalendarByIdentifierSnapshot(ctx: HolidayReadContext, identifier: string) { const db = asHolidayCalendarDb(ctx.db); const trimmedIdentifier = identifier.trim(); let calendar = await db.holidayCalendar.findUnique({ where: { id: trimmedIdentifier }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, entries: { orderBy: [{ date: "asc" }, { name: "asc" }] }, }, }); if (!calendar) { calendar = await db.holidayCalendar.findFirst({ where: { name: { equals: trimmedIdentifier, mode: "insensitive" } }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, entries: { orderBy: [{ date: "asc" }, { name: "asc" }] }, }, }); } if (!calendar) { calendar = await db.holidayCalendar.findFirst({ where: { name: { contains: trimmedIdentifier, mode: "insensitive" } }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, entries: { orderBy: [{ date: "asc" }, { name: "asc" }] }, }, }); } if (!calendar) { throw new TRPCError({ code: "NOT_FOUND", message: `Holiday calendar not found: ${trimmedIdentifier}` }); } return calendar; } async function readPreviewResolvedHolidaysSnapshot( ctx: HolidayReadContext, input: z.infer, ) { const country = await findUniqueOrThrow( ctx.db.country.findUnique({ where: { id: input.countryId }, select: { id: true, code: true, name: true }, }), "Country", ); const metroCity = input.metroCityId ? await ctx.db.metroCity.findUnique({ where: { id: input.metroCityId }, select: { id: true, name: true, countryId: true }, }) : null; const resolved = await getResolvedCalendarHolidays(asHolidayResolverDb(ctx.db), { periodStart: new Date(`${input.year}-01-01T00:00:00.000Z`), periodEnd: new Date(`${input.year}-12-31T00:00:00.000Z`), countryId: input.countryId, countryCode: country.code, federalState: input.stateCode?.trim().toUpperCase() ?? null, metroCityId: input.metroCityId ?? null, metroCityName: metroCity?.name ?? null, }); return { locationContext: { countryId: input.countryId, countryCode: country.code, stateCode: input.stateCode?.trim().toUpperCase() ?? null, metroCityId: input.metroCityId ?? null, metroCity: metroCity?.name ?? null, year: input.year, }, holidays: resolved.map((holiday) => ({ date: holiday.date, name: holiday.name, scopeType: holiday.scope, calendarName: holiday.calendarName, sourceType: holiday.sourceType, })), }; } async function readResolvedHolidaysSnapshot( ctx: HolidayReadContext, input: z.infer, ) { let resolvedCountryCode = input.countryCode?.trim().toUpperCase() ?? null; if (!resolvedCountryCode && input.countryId) { const country = await findUniqueOrThrow( ctx.db.country.findUnique({ where: { id: input.countryId }, select: { code: true }, }), "Country", ); resolvedCountryCode = country.code; } const metroCityName = input.metroCityId ? (await ctx.db.metroCity.findUnique({ where: { id: input.metroCityId }, select: { name: true }, }))?.name ?? null : input.metroCityName?.trim() ?? null; const resolved = await getResolvedCalendarHolidays(asHolidayResolverDb(ctx.db), { periodStart: input.periodStart, periodEnd: input.periodEnd, countryId: input.countryId ?? null, countryCode: resolvedCountryCode, federalState: input.stateCode?.trim().toUpperCase() ?? null, metroCityId: input.metroCityId ?? null, metroCityName, }); return { periodStart: input.periodStart.toISOString().slice(0, 10), periodEnd: input.periodEnd.toISOString().slice(0, 10), locationContext: { countryId: input.countryId ?? null, countryCode: resolvedCountryCode, federalState: input.stateCode?.trim().toUpperCase() ?? null, metroCityId: input.metroCityId ?? null, metroCity: metroCityName, }, holidays: resolved.map((holiday) => ({ date: holiday.date, name: holiday.name, scopeType: holiday.scope, calendarName: holiday.calendarName, sourceType: holiday.sourceType, })), }; } async function readResolvedResourceHolidaysSnapshot( ctx: HolidayReadContext, input: z.infer, ) { await assertCanReadHolidayResource(ctx, input.resourceId); const resource = await findUniqueOrThrow( ctx.db.resource.findUnique({ where: { id: input.resourceId }, select: { id: true, eid: true, displayName: true, federalState: true, countryId: true, metroCityId: true, country: { select: { code: true, name: true } }, metroCity: { select: { name: true } }, }, }), "Resource", ); const resolved = await getResolvedCalendarHolidays(asHolidayResolverDb(ctx.db), { periodStart: input.periodStart, periodEnd: input.periodEnd, countryId: resource.countryId ?? null, countryCode: resource.country?.code ?? null, federalState: resource.federalState ?? null, metroCityId: resource.metroCityId ?? null, metroCityName: resource.metroCity?.name ?? null, }); return { periodStart: input.periodStart.toISOString().slice(0, 10), periodEnd: input.periodEnd.toISOString().slice(0, 10), resource: { id: resource.id, eid: resource.eid, name: resource.displayName, country: resource.country?.name ?? resource.country?.code ?? null, countryCode: resource.country?.code ?? null, federalState: resource.federalState ?? null, metroCity: resource.metroCity?.name ?? null, }, holidays: resolved.map((holiday) => ({ date: holiday.date, name: holiday.name, scopeType: holiday.scope, calendarName: holiday.calendarName, sourceType: holiday.sourceType, })), }; } async function assertEntryDateAvailable( db: HolidayCalendarDb, input: { holidayCalendarId: string; date: Date; }, ignoreId?: string, ) { const existing = await db.holidayCalendarEntry.findFirst({ where: { holidayCalendarId: input.holidayCalendarId, date: clampDate(input.date), ...(ignoreId ? { id: { not: ignoreId } } : {}), }, select: { id: true }, }); if (existing) { throw new TRPCError({ code: "CONFLICT", message: "A holiday entry for this calendar and date already exists", }); } } async function assertScopeConsistency( db: HolidayCalendarDb, input: { scopeType: HolidayCalendarScope; countryId: string; stateCode?: string | null; metroCityId?: string | null; }, ignoreId?: string, ) { if (input.scopeType === HOLIDAY_SCOPE.COUNTRY) { if (input.stateCode || input.metroCityId) { throw new TRPCError({ code: "BAD_REQUEST", message: "Country calendars may not define a state or metro city", }); } } if (input.scopeType === HOLIDAY_SCOPE.STATE) { if (!input.stateCode) { throw new TRPCError({ code: "BAD_REQUEST", message: "State calendars require a state code", }); } if (input.metroCityId) { throw new TRPCError({ code: "BAD_REQUEST", message: "State calendars may not define a metro city", }); } } if (input.scopeType === HOLIDAY_SCOPE.CITY) { if (!input.metroCityId) { throw new TRPCError({ code: "BAD_REQUEST", message: "City calendars require a metro city", }); } const metroCity = await findUniqueOrThrow( db.metroCity.findUnique({ where: { id: input.metroCityId }, select: { id: true, countryId: true }, }), "Metro city", ); if (metroCity.countryId !== input.countryId) { throw new TRPCError({ code: "BAD_REQUEST", message: "Metro city must belong to the selected country", }); } } const existing = await db.holidayCalendar.findFirst({ where: { countryId: input.countryId, scopeType: input.scopeType, ...(input.scopeType === HOLIDAY_SCOPE.STATE ? { stateCode: input.stateCode ?? null } : {}), ...(input.scopeType === HOLIDAY_SCOPE.CITY ? { metroCityId: input.metroCityId ?? null } : {}), ...(ignoreId ? { id: { not: ignoreId } } : {}), }, select: { id: true }, }); if (existing) { throw new TRPCError({ code: "CONFLICT", message: "A holiday calendar for this exact scope already exists", }); } } export const holidayCalendarRouter = createTRPCRouter({ listCalendars: adminProcedure .input(z.object({ includeInactive: z.boolean().optional(), countryCode: z.string().trim().min(1).optional(), scopeType: z.enum(["COUNTRY", "STATE", "CITY"]).optional(), stateCode: z.string().trim().min(1).optional(), metroCity: z.string().trim().min(1).optional(), }).optional()) .query(async ({ ctx, input }) => readCalendarsSnapshot(ctx, input)), listCalendarsDetail: adminProcedure .input(z.object({ includeInactive: z.boolean().optional(), countryCode: z.string().trim().min(1).optional(), scopeType: z.enum(["COUNTRY", "STATE", "CITY"]).optional(), stateCode: z.string().trim().min(1).optional(), metroCity: z.string().trim().min(1).optional(), }).optional()) .query(async ({ ctx, input }) => { const calendars = await readCalendarsSnapshot(ctx, input); return { count: calendars.length, calendars: calendars.map(formatHolidayCalendarDetail), }; }), getCalendarByIdentifier: adminProcedure .input(z.object({ identifier: z.string().trim().min(1) })) .query(async ({ ctx, input }) => readCalendarByIdentifierSnapshot(ctx, input.identifier)), getCalendarByIdentifierDetail: adminProcedure .input(z.object({ identifier: z.string().trim().min(1) })) .query(async ({ ctx, input }) => { const calendar = await readCalendarByIdentifierSnapshot(ctx, input.identifier); return formatHolidayCalendarDetail(calendar); }), getCalendarById: adminProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) => { const db = asHolidayCalendarDb(ctx.db); return findUniqueOrThrow( db.holidayCalendar.findUnique({ where: { id: input.id }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, entries: { orderBy: [{ date: "asc" }, { name: "asc" }] }, }, }), "Holiday calendar", ); }), createCalendar: adminProcedure .input(CreateHolidayCalendarSchema) .mutation(async ({ ctx, input }) => { const db = asHolidayCalendarDb(ctx.db); await findUniqueOrThrow( ctx.db.country.findUnique({ where: { id: input.countryId }, select: { id: true, name: true }, }), "Country", ); await assertScopeConsistency(db, { scopeType: input.scopeType, countryId: input.countryId, stateCode: input.stateCode?.trim().toUpperCase() ?? null, metroCityId: input.metroCityId ?? null, }); const created = await db.holidayCalendar.create({ data: { name: input.name, scopeType: input.scopeType, countryId: input.countryId, ...(input.stateCode ? { stateCode: input.stateCode.trim().toUpperCase() } : {}), ...(input.metroCityId ? { metroCityId: input.metroCityId } : {}), isActive: input.isActive ?? true, priority: input.priority ?? 0, }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, entries: true, }, }); void createAuditEntry({ db: ctx.db, entityType: "HolidayCalendar", entityId: created.id, entityName: created.name, action: "CREATE", userId: ctx.dbUser?.id, after: created as unknown as Record, source: "ui", }); return created; }), updateCalendar: adminProcedure .input(z.object({ id: z.string(), data: UpdateHolidayCalendarSchema })) .mutation(async ({ ctx, input }) => { const db = asHolidayCalendarDb(ctx.db); const existing = await findUniqueOrThrow( db.holidayCalendar.findUnique({ where: { id: input.id } }), "Holiday calendar", ); const stateCode = input.data.stateCode === undefined ? existing.stateCode : input.data.stateCode?.trim().toUpperCase() ?? null; const metroCityId = input.data.metroCityId === undefined ? existing.metroCityId : input.data.metroCityId ?? null; await assertScopeConsistency(db, { scopeType: existing.scopeType, countryId: existing.countryId, stateCode, metroCityId, }, existing.id); const updated = await db.holidayCalendar.update({ where: { id: input.id }, data: { ...(input.data.name !== undefined ? { name: input.data.name } : {}), ...(input.data.stateCode !== undefined ? { stateCode } : {}), ...(input.data.metroCityId !== undefined ? { metroCityId } : {}), ...(input.data.isActive !== undefined ? { isActive: input.data.isActive } : {}), ...(input.data.priority !== undefined ? { priority: input.data.priority } : {}), }, include: { country: { select: { id: true, code: true, name: true } }, metroCity: { select: { id: true, name: true } }, entries: { orderBy: [{ date: "asc" }, { name: "asc" }] }, }, }); void createAuditEntry({ db: ctx.db, entityType: "HolidayCalendar", entityId: updated.id, entityName: updated.name, action: "UPDATE", userId: ctx.dbUser?.id, before: existing as unknown as Record, after: updated as unknown as Record, source: "ui", }); return updated; }), deleteCalendar: adminProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { const db = asHolidayCalendarDb(ctx.db); const existing = await findUniqueOrThrow( db.holidayCalendar.findUnique({ where: { id: input.id }, include: { entries: true }, }), "Holiday calendar", ); await db.holidayCalendar.delete({ where: { id: input.id } }); void createAuditEntry({ db: ctx.db, entityType: "HolidayCalendar", entityId: existing.id, entityName: existing.name, action: "DELETE", userId: ctx.dbUser?.id, before: existing as unknown as Record, source: "ui", }); return { success: true, id: existing.id, name: existing.name }; }), createEntry: adminProcedure .input(CreateHolidayCalendarEntrySchema) .mutation(async ({ ctx, input }) => { const db = asHolidayCalendarDb(ctx.db); await findUniqueOrThrow( db.holidayCalendar.findUnique({ where: { id: input.holidayCalendarId }, select: { id: true, name: true }, }), "Holiday calendar", ); await assertEntryDateAvailable(db, { holidayCalendarId: input.holidayCalendarId, date: input.date, }); const created = await db.holidayCalendarEntry.create({ data: { holidayCalendarId: input.holidayCalendarId, date: clampDate(input.date), name: input.name, isRecurringAnnual: input.isRecurringAnnual ?? false, ...(input.source ? { source: input.source } : {}), }, }); void createAuditEntry({ db: ctx.db, entityType: "HolidayCalendarEntry", entityId: created.id, entityName: created.name, action: "CREATE", userId: ctx.dbUser?.id, after: created as unknown as Record, source: "ui", }); return created; }), updateEntry: adminProcedure .input(z.object({ id: z.string(), data: UpdateHolidayCalendarEntrySchema })) .mutation(async ({ ctx, input }) => { const db = asHolidayCalendarDb(ctx.db); const existing = await findUniqueOrThrow( db.holidayCalendarEntry.findUnique({ where: { id: input.id } }), "Holiday calendar entry", ); const nextDate = input.data.date !== undefined ? clampDate(input.data.date) : existing.date; await assertEntryDateAvailable(db, { holidayCalendarId: existing.holidayCalendarId, date: nextDate, }, existing.id); const updated = await db.holidayCalendarEntry.update({ where: { id: input.id }, data: { ...(input.data.date !== undefined ? { date: nextDate } : {}), ...(input.data.name !== undefined ? { name: input.data.name } : {}), ...(input.data.isRecurringAnnual !== undefined ? { isRecurringAnnual: input.data.isRecurringAnnual } : {}), ...(input.data.source !== undefined ? { source: input.data.source ?? null } : {}), }, }); void createAuditEntry({ db: ctx.db, entityType: "HolidayCalendarEntry", entityId: updated.id, entityName: updated.name, action: "UPDATE", userId: ctx.dbUser?.id, before: existing as unknown as Record, after: updated as unknown as Record, source: "ui", }); return updated; }), deleteEntry: adminProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { const db = asHolidayCalendarDb(ctx.db); const existing = await findUniqueOrThrow( db.holidayCalendarEntry.findUnique({ where: { id: input.id } }), "Holiday calendar entry", ); await db.holidayCalendarEntry.delete({ where: { id: input.id } }); void createAuditEntry({ db: ctx.db, entityType: "HolidayCalendarEntry", entityId: existing.id, entityName: existing.name, action: "DELETE", userId: ctx.dbUser?.id, before: existing as unknown as Record, source: "ui", }); return { success: true, id: existing.id, name: existing.name }; }), previewResolvedHolidays: protectedProcedure .input(PreviewResolvedHolidaysSchema) .query(async ({ ctx, input }) => (await readPreviewResolvedHolidaysSnapshot(ctx, input)).holidays), previewResolvedHolidaysDetail: protectedProcedure .input(PreviewResolvedHolidaysSchema) .query(async ({ ctx, input }) => { const resolved = await readPreviewResolvedHolidaysSnapshot(ctx, input); const holidays = resolved.holidays.map(formatResolvedHolidayDetail); return { count: holidays.length, locationContext: resolved.locationContext, summary: summarizeResolvedHolidaysDetail(holidays), holidays, }; }), resolveHolidays: protectedProcedure .input(ResolveHolidaysInputSchema) .query(async ({ ctx, input }) => readResolvedHolidaysSnapshot(ctx, input)), resolveHolidaysDetail: protectedProcedure .input(ResolveHolidaysInputSchema) .query(async ({ ctx, input }) => { const resolved = await readResolvedHolidaysSnapshot(ctx, input); const holidays = resolved.holidays.map(formatResolvedHolidayDetail); return { periodStart: resolved.periodStart, periodEnd: resolved.periodEnd, locationContext: resolved.locationContext, count: holidays.length, summary: summarizeResolvedHolidaysDetail(holidays), holidays, }; }), resolveResourceHolidays: protectedProcedure .input(ResolveResourceHolidaysInputSchema) .query(async ({ ctx, input }) => readResolvedResourceHolidaysSnapshot(ctx, input)), resolveResourceHolidaysDetail: protectedProcedure .input(ResolveResourceHolidaysInputSchema) .query(async ({ ctx, input }) => { const resolved = await readResolvedResourceHolidaysSnapshot(ctx, input); const holidays = resolved.holidays.map(formatResolvedHolidayDetail); return { periodStart: resolved.periodStart, periodEnd: resolved.periodEnd, resource: resolved.resource, count: holidays.length, summary: summarizeResolvedHolidaysDetail(holidays), holidays, }; }), });