import { z } from "zod"; /** * L14 Abrechnungsübersicht — input schemas and constants (client-safe). * Scope: overview for the external accounting. No prices, amounts, taxes, invoices or payments. */ export const BILLING_KINDS = ["order_completion", "milestone", "daily_report"] as const; export type BillingKind = (typeof BILLING_KINDS)[number]; export const BILLING_STATUSES = ["open", "billed", "voided"] as const; export type BillingStatus = (typeof BILLING_STATUSES)[number]; export const MILESTONE_STATUSES = ["open", "reached", "confirmed", "billed"] as const; export type MilestoneStatus = (typeof MILESTONE_STATUSES)[number]; const id = z.string().trim().min(1).max(64); const optionalText = (max: number) => z .string() .trim() .max(max) .nullish() .transform((v) => (v ? v : null)); export const invoiceNumberField = optionalText(100); export const markBilledSchema = z.object({ recordId: id, invoiceNumber: invoiceNumberField, /** required when approved-pending time entries exist: they are NOT part of the billed section */ confirmPendingExcluded: z.boolean().optional(), }); export type MarkBilledInput = z.input; export const voidBillingSchema = z.object({ recordId: id, reason: z.string().trim().min(1).max(2000) }); export type VoidBillingInput = z.input; export const updateInvoiceNumberSchema = z.object({ recordId: id, invoiceNumber: invoiceNumberField }); export const createMilestoneSchema = z.object({ workOrderId: id, title: z.string().trim().min(1).max(200), description: optionalText(2000), }); export type CreateMilestoneInput = z.input; export const updateMilestoneSchema = z.object({ milestoneId: id, title: z.string().trim().min(1).max(200).optional(), description: optionalText(2000).optional(), }); export const moveMilestoneSchema = z.object({ milestoneId: id, direction: z.enum(["up", "down"]) }); export const rejectMilestoneSchema = z.object({ milestoneId: id, reason: z.string().trim().min(1).max(2000) }); /** Sync op `milestone.reach` (offline-capable, idempotent: an already reached milestone stays unchanged). */ export const milestoneReachPayload = z.object({ workOrderId: id, milestoneId: id, note: optionalText(2000), }); export const billingListFilterSchema = z.object({ status: z.enum(BILLING_STATUSES).catch("open").default("open"), kind: z.enum(BILLING_KINDS).optional().catch(undefined), customerId: id.optional().catch(undefined), teamId: id.optional().catch(undefined), /** period overlap (YYYY-MM-DD, tenant time zone) */ from: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().catch(undefined), to: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().catch(undefined), q: z.string().trim().max(100).optional().catch(undefined), page: z.coerce.number().int().min(1).catch(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).catch(25).default(25), }); export type BillingListFilter = z.input;