Files
pn-new-crm/src/lib/validators/document-templates.ts
Matt Ciaccio 0eff6050ae feat(documents): Phase A schema + service skeletons
Adds Phase A data model deltas to documents/templates and the new
document_watchers table. Introduces createFromWizard/createFromUpload
stubs, getDocumentDetail aggregator, cancelDocument flow, signed-doc
email composer, reservation agreement context, and notifyDocumentEvent
fan-out. Validator update accepts new template formats with html-only
bodyHtml requirement. EOI cadence backfilled to 1 day to preserve
current effective behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 02:12:05 +02:00

130 lines
4.1 KiB
TypeScript

import { z } from 'zod';
import { baseListQuerySchema } from '@/lib/api/route-helpers';
import { VALID_MERGE_TOKENS } from '@/lib/templates/merge-fields';
const mergeFieldsSchema = z
.array(z.string())
.optional()
.default([])
.refine(
(tokens) => tokens.every((t) => VALID_MERGE_TOKENS.has(t)),
(tokens) => {
const unknown = tokens?.filter((t) => !VALID_MERGE_TOKENS.has(t)) ?? [];
return { message: `Unknown merge tokens: ${unknown.join(', ')}` };
},
);
export const templateFormats = ['html', 'pdf_form', 'pdf_overlay', 'documenso_render'] as const;
const createTemplateBaseSchema = z.object({
name: z.string().min(1).max(200),
description: z.string().max(500).optional(),
templateType: z.enum([
'eoi',
'welcome_letter',
'handover_checklist',
'acknowledgment',
'correspondence',
'custom',
]),
templateFormat: z.enum(templateFormats).default('html'),
bodyHtml: z.string().min(1).optional(),
mergeFields: mergeFieldsSchema,
isActive: z.boolean().default(true),
});
export const createTemplateSchema = createTemplateBaseSchema.refine(
(data) => data.templateFormat !== 'html' || (data.bodyHtml && data.bodyHtml.length > 0),
{ path: ['bodyHtml'], message: 'bodyHtml is required when templateFormat is html' },
);
export const updateTemplateSchema = createTemplateBaseSchema.partial();
export const listTemplatesSchema = baseListQuerySchema.extend({
templateType: z.string().optional(),
isActive: z
.enum(['true', 'false'])
.transform((v) => v === 'true')
.optional(),
});
export const generateSchema = z.object({
clientId: z.string().optional(),
interestId: z.string().optional(),
berthId: z.string().optional(),
});
export const generateAndSendSchema = generateSchema.extend({
recipientEmail: z.string().email(),
});
export const generateAndSignSchema = generateSchema.extend({
pathway: z.enum(['inapp', 'documenso-template']).default('inapp'),
signers: z
.array(
z.object({
name: z.string().min(1),
email: z.string().email(),
role: z.string().min(1),
signingOrder: z.number().int().min(1),
}),
)
.optional()
.default([]),
});
export type CreateTemplateInput = z.infer<typeof createTemplateSchema>;
export type UpdateTemplateInput = z.infer<typeof updateTemplateSchema>;
export type ListTemplatesInput = z.infer<typeof listTemplatesSchema>;
export type GenerateInput = z.infer<typeof generateSchema>;
export type GenerateAndSendInput = z.infer<typeof generateAndSendSchema>;
export type GenerateAndSignInput = z.infer<typeof generateAndSignSchema>;
// ─── TipTap-based Admin Template Schemas ─────────────────────────────────────
// Used by /api/v1/admin/templates — the TipTap JSON document store.
export const tiptapDocumentTypes = [
'eoi',
'contract',
'nda',
'reservation_agreement',
'letter',
'other',
] as const;
export const createAdminTemplateSchema = z.object({
name: z.string().min(1).max(200),
type: z.enum(tiptapDocumentTypes),
content: z.record(z.unknown()), // TipTap JSON document
});
export const updateAdminTemplateSchema = z.object({
name: z.string().min(1).max(200).optional(),
content: z.record(z.unknown()).optional(),
isActive: z.boolean().optional(),
});
export const previewAdminTemplateSchema = z.object({
content: z.record(z.unknown()),
sampleData: z.record(z.string()).optional(),
});
export const rollbackAdminTemplateSchema = z.object({
version: z.number().int().min(1),
});
export const listAdminTemplatesSchema = baseListQuerySchema.extend({
type: z.enum(tiptapDocumentTypes).optional(),
isActive: z
.enum(['true', 'false'])
.transform((v) => v === 'true')
.optional(),
});
export type CreateAdminTemplateInput = z.infer<typeof createAdminTemplateSchema>;
export type UpdateAdminTemplateInput = z.infer<typeof updateAdminTemplateSchema>;
export type PreviewAdminTemplateInput = z.infer<typeof previewAdminTemplateSchema>;
export type RollbackAdminTemplateInput = z.infer<typeof rollbackAdminTemplateSchema>;
export type ListAdminTemplatesInput = z.infer<typeof listAdminTemplatesSchema>;