import type { TRPCContext } from "../../trpc.js"; import { CreateHolidayCalendarEntrySchema, CreateHolidayCalendarSchema, PreviewResolvedHolidaysSchema, UpdateHolidayCalendarEntrySchema, UpdateHolidayCalendarSchema, } from "@capakraken/shared"; import { z } from "zod"; import type { ToolContext, ToolDef, ToolExecutor } from "./shared.js"; type AssistantToolErrorResult = { error: string }; type ResolvedResource = { id: string; }; type HolidayCalendarEntryRecord = { id: string; date: Date; name: string; isRecurringAnnual?: boolean | null; source?: string | null; }; type HolidayCalendarRecord = { 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?: HolidayCalendarEntryRecord[] | null; }; type VacationHolidayDeps = { createEntitlementCaller: (ctx: TRPCContext) => { getBalanceDetail: (params: { resourceId: string; year: number }) => Promise; }; createVacationCaller: (ctx: TRPCContext) => { list: (params: { status: "APPROVED"; startDate: Date; endDate: Date; limit: number; }) => Promise>; }; createHolidayCalendarCaller: (ctx: TRPCContext) => { resolveHolidaysDetail: (params: { periodStart: Date; periodEnd: Date; countryCode: string; stateCode?: string; metroCityName?: string; }) => Promise<{ locationContext: unknown; periodStart: string; periodEnd: string; count: number; summary: unknown; holidays: unknown[]; }>; resolveResourceHolidaysDetail: (params: { resourceId: string; periodStart: Date; periodEnd: Date; }) => Promise<{ resource: unknown; periodStart: string; periodEnd: string; count: number; summary: unknown; holidays: unknown[]; }>; listCalendarsDetail: (params: { includeInactive?: boolean; countryCode?: string; scopeType?: "COUNTRY" | "STATE" | "CITY"; stateCode?: string; metroCity?: string; }) => Promise; getCalendarByIdentifierDetail: (params: { identifier: string }) => Promise; previewResolvedHolidaysDetail: ( params: z.input, ) => Promise; createCalendar: ( params: z.input, ) => Promise; updateCalendar: (params: { id: string; data: z.input; }) => Promise; deleteCalendar: (params: { id: string }) => Promise<{ name: string }>; createEntry: ( params: z.input, ) => Promise; updateEntry: (params: { id: string; data: z.input; }) => Promise; deleteEntry: (params: { id: string }) => Promise<{ name: string }>; }; createScopedCallerContext: (ctx: ToolContext) => TRPCContext; resolveResourceIdentifier: ( ctx: ToolContext, identifier: string, ) => Promise; resolveHolidayPeriod: (input: { year?: number; periodStart?: string; periodEnd?: string; }) => { year: number | null; periodStart: Date; periodEnd: Date }; resolveEntityOrAssistantError: ( resolve: () => Promise, notFoundMessage: string, ) => Promise; assertAdminRole: (ctx: ToolContext) => void; fmtDate: (value: Date | null | undefined) => string | null; formatHolidayCalendar: (calendar: HolidayCalendarRecord) => unknown; formatHolidayCalendarEntry: (entry: HolidayCalendarEntryRecord) => unknown; toAssistantHolidayCalendarMutationError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantHolidayCalendarNotFoundError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantHolidayEntryMutationError: ( error: unknown, ) => AssistantToolErrorResult | null; toAssistantHolidayEntryNotFoundError: ( error: unknown, ) => AssistantToolErrorResult | null; }; export const vacationHolidayReadToolDefinitions: ToolDef[] = [ { type: "function", function: { name: "get_vacation_balance", description: "Get the holiday-aware vacation balance for a resource via the real entitlement workflow. Authenticated users can read their own balance; manager/admin/controller can read broader balances.", parameters: { type: "object", properties: { resourceId: { type: "string", description: "Resource ID, EID, or display name" }, year: { type: "integer", description: "Year. Default: current year" }, }, required: ["resourceId"], }, }, }, { type: "function", function: { name: "list_vacations_upcoming", description: "List upcoming vacations across all resources, or for a specific resource/team. Shows who is off when.", parameters: { type: "object", properties: { resourceName: { type: "string", description: "Filter by resource name (partial match)" }, chapter: { type: "string", description: "Filter by chapter/team" }, daysAhead: { type: "integer", description: "How many days ahead to look. Default: 30" }, limit: { type: "integer", description: "Max results. Default: 30" }, }, }, }, }, { type: "function", function: { name: "list_holidays_by_region", description: "List resolved public holidays for a country, federal state, and optionally a city in a given year or date range. Use this to compare regions such as Bayern vs Hamburg.", parameters: { type: "object", properties: { countryCode: { type: "string", description: "Country code such as DE, ES, US, IN." }, federalState: { type: "string", description: "Federal state / region code, e.g. BY, HH, NRW." }, metroCity: { type: "string", description: "Optional city name for local city-specific holidays, e.g. Augsburg." }, year: { type: "integer", description: "Full year, e.g. 2026. Default: current year." }, periodStart: { type: "string", description: "Optional start date in YYYY-MM-DD. Requires periodEnd." }, periodEnd: { type: "string", description: "Optional end date in YYYY-MM-DD. Requires periodStart." }, }, required: ["countryCode"], }, }, }, { type: "function", function: { name: "get_resource_holidays", description: "List resolved public holidays for a specific resource based on that person's country, federal state, and city context.", parameters: { type: "object", properties: { identifier: { type: "string", description: "Resource ID, EID, or display name." }, year: { type: "integer", description: "Full year, e.g. 2026. Default: current year." }, periodStart: { type: "string", description: "Optional start date in YYYY-MM-DD. Requires periodEnd." }, periodEnd: { type: "string", description: "Optional end date in YYYY-MM-DD. Requires periodStart." }, }, required: ["identifier"], }, }, }, { type: "function", function: { name: "list_holiday_calendars", description: "List holiday calendars including scope, assignment, active flag, priority, and entry count. Useful to inspect the calendar-editor configuration context.", parameters: { type: "object", properties: { includeInactive: { type: "boolean", description: "Include inactive calendars. Default: false." }, countryCode: { type: "string", description: "Optional country code filter such as DE or ES." }, scopeType: { type: "string", description: "Optional scope filter: COUNTRY, STATE, CITY." }, stateCode: { type: "string", description: "Optional state/region code filter such as BY or NRW." }, metroCity: { type: "string", description: "Optional city-name filter." }, }, }, }, }, { type: "function", function: { name: "get_holiday_calendar", description: "Get a single holiday calendar including all entries. Accepts either the calendar ID or its name.", parameters: { type: "object", properties: { identifier: { type: "string", description: "Holiday calendar ID or name." }, }, required: ["identifier"], }, }, }, { type: "function", function: { name: "preview_resolved_holiday_calendar", description: "Preview the resolved holiday result for a country/state/city scope and year, including which calendar each holiday comes from.", parameters: { type: "object", properties: { countryId: { type: "string", description: "Country ID." }, stateCode: { type: "string", description: "Optional state/region code." }, metroCityId: { type: "string", description: "Optional metro city ID for city-specific preview." }, year: { type: "integer", description: "Full year, e.g. 2026." }, }, required: ["countryId", "year"], }, }, }, ]; export const vacationHolidayMutationToolDefinitions: ToolDef[] = [ { type: "function", function: { name: "create_holiday_calendar", description: "Create a holiday calendar for a country, state, or city scope. Admin role required. Always confirm first.", parameters: { type: "object", properties: { name: { type: "string", description: "Calendar name." }, scopeType: { type: "string", description: "COUNTRY, STATE, or CITY." }, countryId: { type: "string", description: "Country ID." }, stateCode: { type: "string", description: "Required for STATE calendars." }, metroCityId: { type: "string", description: "Required for CITY calendars." }, isActive: { type: "boolean", description: "Whether the calendar is active. Default: true." }, priority: { type: "integer", description: "Priority used during calendar resolution. Default: 0." }, }, required: ["name", "scopeType", "countryId"], }, }, }, { type: "function", function: { name: "update_holiday_calendar", description: "Update an existing holiday calendar. Admin role required. Always confirm first.", parameters: { type: "object", properties: { id: { type: "string", description: "Holiday calendar ID." }, data: { type: "object", properties: { name: { type: "string" }, stateCode: { type: "string" }, metroCityId: { type: "string" }, isActive: { type: "boolean" }, priority: { type: "integer" }, }, }, }, required: ["id", "data"], }, }, }, { type: "function", function: { name: "delete_holiday_calendar", description: "Delete a holiday calendar and all of its entries. Admin role required. Always confirm first.", parameters: { type: "object", properties: { id: { type: "string", description: "Holiday calendar ID." }, }, required: ["id"], }, }, }, { type: "function", function: { name: "create_holiday_calendar_entry", description: "Create a holiday entry in an existing calendar. Admin role required. Always confirm first.", parameters: { type: "object", properties: { holidayCalendarId: { type: "string", description: "Holiday calendar ID." }, date: { type: "string", description: "Date in YYYY-MM-DD format." }, name: { type: "string", description: "Holiday name." }, isRecurringAnnual: { type: "boolean", description: "Whether the holiday repeats every year." }, source: { type: "string", description: "Optional source or legal basis." }, }, required: ["holidayCalendarId", "date", "name"], }, }, }, { type: "function", function: { name: "update_holiday_calendar_entry", description: "Update an existing holiday calendar entry. Admin role required. Always confirm first.", parameters: { type: "object", properties: { id: { type: "string", description: "Holiday calendar entry ID." }, data: { type: "object", properties: { date: { type: "string", description: "Date in YYYY-MM-DD format." }, name: { type: "string" }, isRecurringAnnual: { type: "boolean" }, source: { type: "string" }, }, }, }, required: ["id", "data"], }, }, }, { type: "function", function: { name: "delete_holiday_calendar_entry", description: "Delete a holiday calendar entry. Admin role required. Always confirm first.", parameters: { type: "object", properties: { id: { type: "string", description: "Holiday calendar entry ID." }, }, required: ["id"], }, }, }, ]; export function createVacationHolidayExecutors( deps: VacationHolidayDeps, ): Record { return { async get_vacation_balance(params: { resourceId: string; year?: number }, ctx: ToolContext) { const year = params.year ?? new Date().getFullYear(); const resource = await deps.resolveResourceIdentifier(ctx, params.resourceId); if ("error" in resource) { return resource; } const caller = deps.createEntitlementCaller(deps.createScopedCallerContext(ctx)); return caller.getBalanceDetail({ resourceId: resource.id, year }); }, async list_vacations_upcoming(params: { resourceName?: string; chapter?: string; daysAhead?: number; limit?: number; }, ctx: ToolContext) { const daysAhead = params.daysAhead ?? 30; const limit = Math.min(params.limit ?? 30, 50); const caller = deps.createVacationCaller(deps.createScopedCallerContext(ctx)); const now = new Date(); const until = new Date(now.getTime() + daysAhead * 24 * 60 * 60 * 1000); const vacations = await caller.list({ status: "APPROVED", startDate: now, endDate: until, limit, }); return vacations .filter((vacation) => { if (params.resourceName) { const resourceName = vacation.resource.displayName.toLowerCase(); if (!resourceName.includes(params.resourceName.toLowerCase())) { return false; } } if (params.chapter) { const chapter = vacation.resource.chapter?.toLowerCase() ?? ""; if (!chapter.includes(params.chapter.toLowerCase())) { return false; } } return true; }) .slice(0, limit) .map((vacation) => ({ resource: vacation.resource.displayName, eid: vacation.resource.eid, chapter: vacation.resource.chapter ?? null, type: vacation.type, start: deps.fmtDate(vacation.startDate), end: deps.fmtDate(vacation.endDate), isHalfDay: vacation.isHalfDay, halfDayPart: vacation.halfDayPart, })); }, async list_holidays_by_region(params: { countryCode: string; federalState?: string; metroCity?: string; year?: number; periodStart?: string; periodEnd?: string; }, ctx: ToolContext) { const { year, periodStart, periodEnd } = deps.resolveHolidayPeriod(params); const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); const resolved = await caller.resolveHolidaysDetail({ periodStart, periodEnd, countryCode: params.countryCode.trim().toUpperCase(), ...(params.federalState ? { stateCode: params.federalState } : {}), ...(params.metroCity ? { metroCityName: params.metroCity } : {}), }); return { locationContext: resolved.locationContext, year, periodStart: resolved.periodStart, periodEnd: resolved.periodEnd, count: resolved.count, summary: resolved.summary, holidays: resolved.holidays, }; }, async get_resource_holidays(params: { identifier: string; year?: number; periodStart?: string; periodEnd?: string; }, ctx: ToolContext) { const resource = await deps.resolveResourceIdentifier(ctx, params.identifier); if ("error" in resource) { return resource; } const { year, periodStart, periodEnd } = deps.resolveHolidayPeriod(params); const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); const resolved = await caller.resolveResourceHolidaysDetail({ resourceId: resource.id, periodStart, periodEnd, }); return { resource: resolved.resource, year, periodStart: resolved.periodStart, periodEnd: resolved.periodEnd, count: resolved.count, summary: resolved.summary, holidays: resolved.holidays, }; }, async list_holiday_calendars(params: { includeInactive?: boolean; countryCode?: string; scopeType?: "COUNTRY" | "STATE" | "CITY"; stateCode?: string; metroCity?: string; }, ctx: ToolContext) { const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); return caller.listCalendarsDetail(params); }, async get_holiday_calendar(params: { identifier: string }, ctx: ToolContext) { const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); const identifier = params.identifier.trim(); return deps.resolveEntityOrAssistantError( () => caller.getCalendarByIdentifierDetail({ identifier }), `Holiday calendar not found: ${identifier}`, ); }, async preview_resolved_holiday_calendar(params: { countryId: string; stateCode?: string; metroCityId?: string; year: number; }, ctx: ToolContext) { const input = PreviewResolvedHolidaysSchema.parse(params); const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); return caller.previewResolvedHolidaysDetail(input); }, async create_holiday_calendar(params: { name: string; scopeType: "COUNTRY" | "STATE" | "CITY"; countryId: string; stateCode?: string; metroCityId?: string; isActive?: boolean; priority?: number; }, ctx: ToolContext) { deps.assertAdminRole(ctx); const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); let created; try { created = await caller.createCalendar(CreateHolidayCalendarSchema.parse(params)); } catch (error) { const mapped = deps.toAssistantHolidayCalendarMutationError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["holidayCalendar", "vacation"], success: true, calendar: deps.formatHolidayCalendar(created), message: `Created holiday calendar: ${created.name}`, }; }, async update_holiday_calendar(params: { id: string; data: { name?: string; stateCode?: string | null; metroCityId?: string | null; isActive?: boolean; priority?: number; }; }, ctx: ToolContext) { deps.assertAdminRole(ctx); const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); const input = { id: params.id, data: UpdateHolidayCalendarSchema.parse(params.data), }; let updated; try { updated = await caller.updateCalendar(input); } catch (error) { const mapped = deps.toAssistantHolidayCalendarMutationError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["holidayCalendar", "vacation"], success: true, calendar: deps.formatHolidayCalendar(updated), message: `Updated holiday calendar: ${updated.name}`, }; }, async delete_holiday_calendar(params: { id: string }, ctx: ToolContext) { deps.assertAdminRole(ctx); const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); let deleted; try { deleted = await caller.deleteCalendar({ id: params.id }); } catch (error) { const mapped = deps.toAssistantHolidayCalendarNotFoundError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["holidayCalendar", "vacation"], success: true, message: `Deleted holiday calendar: ${deleted.name}`, }; }, async create_holiday_calendar_entry(params: { holidayCalendarId: string; date: string; name: string; isRecurringAnnual?: boolean; source?: string; }, ctx: ToolContext) { deps.assertAdminRole(ctx); const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); let created; try { created = await caller.createEntry(CreateHolidayCalendarEntrySchema.parse(params)); } catch (error) { const mapped = deps.toAssistantHolidayEntryMutationError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["holidayCalendar", "vacation"], success: true, entry: deps.formatHolidayCalendarEntry(created), message: `Created holiday entry: ${created.name}`, }; }, async update_holiday_calendar_entry(params: { id: string; data: { date?: string; name?: string; isRecurringAnnual?: boolean; source?: string | null; }; }, ctx: ToolContext) { deps.assertAdminRole(ctx); const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); const input = { id: params.id, data: UpdateHolidayCalendarEntrySchema.parse(params.data), }; let updated; try { updated = await caller.updateEntry(input); } catch (error) { const mapped = deps.toAssistantHolidayEntryMutationError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["holidayCalendar", "vacation"], success: true, entry: deps.formatHolidayCalendarEntry(updated), message: `Updated holiday entry: ${updated.name}`, }; }, async delete_holiday_calendar_entry(params: { id: string }, ctx: ToolContext) { deps.assertAdminRole(ctx); const caller = deps.createHolidayCalendarCaller(deps.createScopedCallerContext(ctx)); let deleted; try { deleted = await caller.deleteEntry({ id: params.id }); } catch (error) { const mapped = deps.toAssistantHolidayEntryNotFoundError(error); if (mapped) { return mapped; } throw error; } return { __action: "invalidate" as const, scope: ["holidayCalendar", "vacation"], success: true, message: `Deleted holiday entry: ${deleted.name}`, }; }, }; }