// Runtime validation schemas coupled to the wire types in ./index. // Separate entrypoint by design: consumers that only need types (mobile) // never pull zod. Each schema is compile-time-checked against its type via // `satisfies z.ZodType` — if a DTO and its schema drift, tsc fails here. import { z } from 'zod'; import type { ProposalStatus, ServiceCategory, Priority, LineItemSource, PricingMode, UserRole, ProposalListItem, ProposalDetail, CreateProposalRequest, UpdateProposalRequest, ProposalVersionRequest, ConcurrencyConflict, LineItem, CreateLineItemRequest, UpdateLineItemEntry, BulkUpdateLineItemsRequest, Customer, CreateCustomerRequest, UpdateCustomerRequest, PricingLibraryItem, CreatePricingLibraryItemRequest, UpdatePricingLibraryItemRequest, DashboardStats, AuditEntry, Site, AuthUser, UserProfile, ProposalStats, PdfVersion, PresignedUpload, ApiProblem, } from './index'; // ── Enums ───────────────────────────────────────────────────────────────── export const proposalStatusSchema = z.enum(['Draft', 'InReview', 'Approved', 'Sent', 'Revised']) satisfies z.ZodType; export const serviceCategorySchema = z.enum(['HVAC', 'Plumbing', 'Electrical', 'General', 'Renovation', 'Other']) satisfies z.ZodType; export const prioritySchema = z.enum(['Standard', 'Urgent', 'Emergency']) satisfies z.ZodType; export const lineItemSourceSchema = z.enum(['AI', 'Vendor', 'Manual', 'Historical']) satisfies z.ZodType; export const pricingModeSchema = z.enum(['UnitPrice', 'TotalPrice', 'Both']) satisfies z.ZodType; export const userRoleSchema = z.enum(['Dispatcher', 'Admin', 'SysAdmin']) satisfies z.ZodType; // ── Proposals ───────────────────────────────────────────────────────────── export const proposalListItemSchema = z.object({ id: z.string(), proposalNumber: z.string(), customerName: z.string(), workOrderNumber: z.string(), serviceCategory: serviceCategorySchema, priority: prioritySchema, status: proposalStatusSchema, totalBidAmount: z.number(), submittedAt: z.string(), submittedByName: z.string().nullable(), assignedAdminName: z.string().nullable(), rowVersion: z.string(), }) satisfies z.ZodType; export const proposalDetailSchema = z.object({ id: z.string(), proposalNumber: z.string(), workOrderNumber: z.string(), poNumber: z.string().nullable(), customerName: z.string(), customerAddress: z.string(), scopeOfWork: z.string(), refinedScope: z.string().nullable(), serviceCategory: serviceCategorySchema, priority: prioritySchema, status: proposalStatusSchema, totalBidAmount: z.number(), vendorTotalCost: z.number().nullable(), notes: z.string(), submittedById: z.string(), submittedByName: z.string().nullable(), submittedAt: z.string(), assignedAdminId: z.string().nullable(), approvedById: z.string().nullable(), approvedAt: z.string().nullable(), sentAt: z.string().nullable(), currentRevision: z.number(), parentProposalId: z.string().nullable(), createdAt: z.string(), updatedAt: z.string(), rowVersion: z.string(), }) satisfies z.ZodType; export const createProposalRequestSchema = z.object({ workOrderNumber: z.string().min(1), poNumber: z.string().optional(), customerName: z.string().min(1), customerAddress: z.string().min(1), scopeOfWork: z.string().min(1), serviceCategory: serviceCategorySchema, priority: prioritySchema, notes: z.string().optional(), }) satisfies z.ZodType; export const updateProposalRequestSchema = z.object({ refinedScope: z.string().optional(), notes: z.string().optional(), poNumber: z.string().optional(), workOrderNumber: z.string().optional(), assignedAdminId: z.string().optional(), proposalVersion: z.string().optional(), }) satisfies z.ZodType; export const proposalVersionRequestSchema = z.object({ proposalVersion: z.string().optional(), }) satisfies z.ZodType; export const proposalStatsSchema = z.object({ totalCount: z.number(), inReviewCount: z.number(), approvedCount: z.number(), sentCount: z.number(), }) satisfies z.ZodType; // ── Line items ──────────────────────────────────────────────────────────── export const lineItemSchema = z.object({ id: z.string(), proposalId: z.string(), description: z.string(), quantity: z.number(), unit: z.string(), unitPrice: z.number().nullable(), totalPrice: z.number(), pricingMode: pricingModeSchema, sortOrder: z.number(), source: lineItemSourceSchema, createdAt: z.string(), updatedAt: z.string(), rowVersion: z.string(), }) satisfies z.ZodType; export const createLineItemRequestSchema = z.object({ description: z.string().min(1), quantity: z.number(), unit: z.string(), unitPrice: z.number().nullable(), totalPrice: z.number(), pricingMode: pricingModeSchema, sortOrder: z.number(), source: lineItemSourceSchema, }) satisfies z.ZodType; export const updateLineItemEntrySchema = z.object({ id: z.string().nullable(), description: z.string().min(1), quantity: z.number(), unit: z.string(), unitPrice: z.number().nullable(), totalPrice: z.number(), pricingMode: pricingModeSchema, sortOrder: z.number(), source: lineItemSourceSchema, }) satisfies z.ZodType; export const bulkUpdateLineItemsRequestSchema = z.object({ lineItems: z.array(updateLineItemEntrySchema), proposalVersion: z.string().optional(), }) satisfies z.ZodType; // ── Customers ───────────────────────────────────────────────────────────── export const customerSchema = z.object({ id: z.string(), name: z.string(), addresses: z.array(z.string()), contactEmail: z.string().nullable(), createdAt: z.string(), }) satisfies z.ZodType; export const createCustomerRequestSchema = z.object({ name: z.string().min(1), addresses: z.array(z.string()).optional(), contactEmail: z.string().optional(), }) satisfies z.ZodType; export const updateCustomerRequestSchema = z.object({ name: z.string().optional(), addresses: z.array(z.string()).optional(), contactEmail: z.string().nullable().optional(), }) satisfies z.ZodType; // ── Pricing library ─────────────────────────────────────────────────────── export const pricingLibraryItemSchema = z.object({ id: z.string(), title: z.string(), description: z.string().nullable(), serviceCategory: serviceCategorySchema, unit: z.string().nullable(), unitPrice: z.number().nullable(), keywords: z.string().nullable(), source: z.string(), createdAt: z.string(), updatedAt: z.string(), }) satisfies z.ZodType; export const createPricingLibraryItemRequestSchema = z.object({ title: z.string().min(1), description: z.string().optional(), serviceCategory: serviceCategorySchema, unit: z.string().optional(), unitPrice: z.number().nullable().optional(), keywords: z.string().optional(), source: z.string().optional(), }) satisfies z.ZodType; export const updatePricingLibraryItemRequestSchema = z.object({ title: z.string().optional(), description: z.string().optional(), serviceCategory: serviceCategorySchema.optional(), unit: z.string().optional(), unitPrice: z.number().nullable().optional(), keywords: z.string().optional(), }) satisfies z.ZodType; // ── Admin ───────────────────────────────────────────────────────────────── export const dashboardStatsSchema = z.object({ pendingCount: z.number(), approvedThisWeek: z.number(), avgTurnaroundHours: z.number(), totalProposals: z.number(), }) satisfies z.ZodType; export const auditEntrySchema = z.object({ id: z.string(), proposalId: z.string().nullable(), userId: z.string(), userName: z.string().nullable(), action: z.string(), details: z.string().nullable(), timestamp: z.string(), ipAddress: z.string().nullable(), }) satisfies z.ZodType; // ── Sites ───────────────────────────────────────────────────────────────── export const siteSchema = z.object({ siteCode: z.string(), fullAddress: z.string().nullable(), address: z.string().nullable(), city: z.string().nullable(), state: z.string().nullable(), zip: z.string().nullable(), }) satisfies z.ZodType; // ── Auth / users ────────────────────────────────────────────────────────── export const authUserSchema = z.object({ id: z.string(), email: z.string(), displayName: z.string(), role: userRoleSchema, token: z.string(), }) satisfies z.ZodType; export const userProfileSchema = z.object({ id: z.string(), email: z.string(), displayName: z.string(), role: userRoleSchema, }) satisfies z.ZodType; // ── Files / PDFs ────────────────────────────────────────────────────────── export const pdfVersionSchema = z.object({ revision: z.number(), generatedAt: z.string(), }) satisfies z.ZodType; export const presignedUploadSchema = z.object({ uploadUrl: z.string(), s3Key: z.string(), expiresAt: z.string(), vendorProposalId: z.string(), }) satisfies z.ZodType; // ── Errors ──────────────────────────────────────────────────────────────── export const apiProblemSchema = z.object({ status: z.number(), title: z.string(), detail: z.string(), code: z.string(), }) satisfies z.ZodType; // 409 concurrency-conflict envelope. The proposal aggregate is the only // guarded resource today, so the concrete schema is coupled to ProposalDetail; // build others via the same pattern when new aggregates gain guards. export const proposalConcurrencyConflictSchema = z.object({ message: z.string(), currentState: proposalDetailSchema.nullable(), }) satisfies z.ZodType>;