Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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<typeof requestReportSchema>;
|
|
|
|
|
export type ListReportsInput = z.infer<typeof listReportsSchema>;
|
2026-05-25 14:26:18 +02:00
|
|
|
|
|
|
|
|
// ─── 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'] 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<typeof listReportRunsSchema>;
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
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<typeof createReportRunSchema>;
|
|
|
|
|
|
|
|
|
|
// ─── 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<typeof listReportSchedulesSchema>;
|
|
|
|
|
|
|
|
|
|
export const createReportScheduleSchema = z.object({
|
|
|
|
|
templateId: z.string().min(1),
|
|
|
|
|
cadence: z.enum(REPORT_SCHEDULE_CADENCES),
|
|
|
|
|
recipients: z.array(recipientSchema).min(1).max(50),
|
|
|
|
|
outputFormat: z.enum(REPORT_OUTPUT_FORMATS).default('pdf'),
|
|
|
|
|
enabled: z.boolean().default(true),
|
|
|
|
|
});
|
|
|
|
|
export type CreateReportScheduleInput = z.infer<typeof createReportScheduleSchema>;
|
|
|
|
|
|
|
|
|
|
export const updateReportScheduleSchema = z.object({
|
|
|
|
|
cadence: z.enum(REPORT_SCHEDULE_CADENCES).optional(),
|
|
|
|
|
recipients: z.array(recipientSchema).min(1).max(50).optional(),
|
|
|
|
|
outputFormat: z.enum(REPORT_OUTPUT_FORMATS).optional(),
|
|
|
|
|
enabled: z.boolean().optional(),
|
|
|
|
|
});
|
|
|
|
|
export type UpdateReportScheduleInput = z.infer<typeof updateReportScheduleSchema>;
|