import { z } from 'zod'; export const requestReportSchema = z.object({ reportType: z.enum(['pipeline', 'revenue', 'activity', 'occupancy']), name: z.string().min(1).max(200), parameters: z .object({ dateFrom: z.string().optional(), dateTo: z.string().optional(), }) .optional() .default({}), }); export const listReportsSchema = z.object({ page: z.coerce.number().int().positive().default(1), limit: z.coerce.number().int().positive().max(100).default(20), status: z.enum(['queued', 'processing', 'ready', 'failed']).optional(), }); export type RequestReportInput = z.infer; export type ListReportsInput = z.infer; // ─── Reports P2: report_runs + report_schedules CRUD ───────────────────────── // // Reports page (`/{portSlug}/reports`) — the dedicated builder + history + // schedules surface. P1 shipped the schema (migration 0084 + Drizzle); P2 // adds the CRUD layer on the new tables. The legacy `generatedReports` flow // above stays for the existing dashboard-export button until it migrates. export const REPORT_KINDS = [ 'dashboard', 'clients', 'berths', 'interests', 'sales', 'operational', 'custom', ] as const; export type ReportKind = (typeof REPORT_KINDS)[number]; export const REPORT_OUTPUT_FORMATS = ['pdf', 'csv', 'png'] as const; export type ReportOutputFormat = (typeof REPORT_OUTPUT_FORMATS)[number]; export const REPORT_RUN_STATUSES = ['pending', 'rendering', 'complete', 'failed'] as const; export type ReportRunStatus = (typeof REPORT_RUN_STATUSES)[number]; export const REPORT_SCHEDULE_CADENCES = [ 'weekly_monday_9', 'monthly_first_9', 'quarterly_first_9', ] as const; export type ReportScheduleCadence = (typeof REPORT_SCHEDULE_CADENCES)[number]; // ─── report_runs ──────────────────────────────────────────────────────────── export const listReportRunsSchema = z.object({ page: z.coerce.number().int().positive().default(1), pageSize: z.coerce.number().int().positive().max(100).default(20), kind: z.enum(REPORT_KINDS).optional(), status: z.enum(REPORT_RUN_STATUSES).optional(), templateId: z.string().optional(), }); export type ListReportRunsInput = z.infer; const recipientSchema = z.object({ name: z.string().max(120).optional(), email: z.string().email(), }); export const createReportRunSchema = z.object({ kind: z.enum(REPORT_KINDS), templateId: z.string().optional(), // Same opaque shape report_templates accepts — the render queue // re-validates per-kind at use time. NOTE: the optional // `config.coverBrandPortId` (cover-page brand swap) is intentionally NOT // shape-validated here — it requires a runtime per-user membership check // that Zod can't express. `createReportRun` rejects an override the // triggering user can't access (H11), and the renderer strips a // stale/forged one as defense-in-depth. config: z.record(z.string(), z.unknown()), outputFormat: z.enum(REPORT_OUTPUT_FORMATS).default('pdf'), emailTo: z.array(recipientSchema).max(50).optional(), }); export type CreateReportRunInput = z.infer; // ─── report_schedules ──────────────────────────────────────────────────────── export const listReportSchedulesSchema = z.object({ page: z.coerce.number().int().positive().default(1), pageSize: z.coerce.number().int().positive().max(100).default(50), enabled: z .union([z.literal('true'), z.literal('false')]) .optional() .transform((v) => (v === undefined ? undefined : v === 'true')), templateId: z.string().optional(), }); export type ListReportSchedulesInput = z.infer; export const createReportScheduleSchema = z.object({ templateId: z.string().min(1), cadence: z.enum(REPORT_SCHEDULE_CADENCES), // Empty recipients list = "run + archive, don't email". Per locked // decision (2026-05-27): auto-email is OPTIONAL — an admin can // schedule a run that just appears in /reports/runs without // forcing an email blast. recipients: z.array(recipientSchema).max(50).default([]), outputFormat: z.enum(REPORT_OUTPUT_FORMATS).default('pdf'), enabled: z.boolean().default(true), }); export type CreateReportScheduleInput = z.infer; export const updateReportScheduleSchema = z.object({ cadence: z.enum(REPORT_SCHEDULE_CADENCES).optional(), recipients: z.array(recipientSchema).max(50).optional(), outputFormat: z.enum(REPORT_OUTPUT_FORMATS).optional(), enabled: z.boolean().optional(), }); export type UpdateReportScheduleInput = z.infer;