import { z } from "zod"; import { nonNegativeIntFromForm, nonNegativeNumberFromForm, positiveNumberFromForm, nullableIntIdFromForm, booleanFromForm, isoDateString, DocumentSectionSchema, } from "./common"; // Limits are DB-aligned: description VarChar(500), unit VarChar(20); // item_description is Text with the app-level 8000 cap used elsewhere. const QuotationItemSchema = z.object({ description: z.string().max(500).nullish(), item_description: z.string().max(8000).nullish(), quantity: positiveNumberFromForm.default(1), unit: z.string().max(20).nullish(), unit_price: nonNegativeNumberFromForm.default(0), is_included_in_total: booleanFromForm.optional().default(true), // Int column — a float position would 500 at Prisma. position: nonNegativeIntFromForm.optional(), }); // Shared with issued orders (scope_sections ≡ issued_order_sections). const ScopeSectionSchema = DocumentSectionSchema; // NOTE: no top-level `.default()`s here — `UpdateQuotationSchema` derives from // this schema via `.partial()`, and Zod 4 still applies a field default when // the key is absent, which would silently inject status/currency/language // resets into every update payload. Create-time defaults live in // `createOffer` (service) instead. export const CreateQuotationSchema = z.object({ // DB: quotations.quotation_number VarChar(50). quotation_number: z.string().max(50).nullish(), // DB column is VarChar(100) — a longer value would 500 at Prisma. project_code: z.string().max(100).nullish(), customer_id: nullableIntIdFromForm.nullish(), created_at: isoDateString.nullish(), valid_until: isoDateString.nullish(), currency: z.string().max(20).optional(), language: z.string().max(20).optional(), status: z.enum(["draft", "active", "ordered", "invalidated"]).optional(), scope_title: z.string().max(255).nullish(), scope_description: z.string().max(8000).nullish(), items: z.array(QuotationItemSchema).optional(), sections: z.array(ScopeSectionSchema).optional(), }); // Update = partial create MINUS the number: quotation numbers are immutable // (assigned by the sequence on finalize), so the field is stripped before it // can ever reach the service. export const UpdateQuotationSchema = CreateQuotationSchema.partial().omit({ quotation_number: true, }); export type CreateQuotationInput = z.infer; export type UpdateQuotationInput = z.infer;