2026-04-23 23:31:29 +02:00
|
|
|
import { z } from 'zod';
|
2026-05-06 14:56:59 +02:00
|
|
|
import { baseListQuerySchema } from '@/lib/api/list-query';
|
2026-04-23 23:31:29 +02:00
|
|
|
|
|
|
|
|
export const ownerRefSchema = z.object({
|
|
|
|
|
type: z.enum(['client', 'company']),
|
|
|
|
|
id: z.string().min(1),
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-12 14:50:58 +02:00
|
|
|
// Numeric columns on the yachts table accept a stringified decimal or
|
|
|
|
|
// null. The form posts empty strings for unfilled fields, which Postgres
|
|
|
|
|
// rejects with `invalid input syntax for type numeric: ""`. Strip empty
|
|
|
|
|
// strings here so the service can confidently `?? null` them.
|
|
|
|
|
const optionalNumericString = z
|
|
|
|
|
.string()
|
|
|
|
|
.optional()
|
|
|
|
|
.transform((v) => (v === '' || v === undefined ? undefined : v));
|
|
|
|
|
|
2026-04-23 23:31:29 +02:00
|
|
|
export const createYachtSchema = z.object({
|
|
|
|
|
name: z.string().min(1).max(200),
|
|
|
|
|
hullNumber: z.string().optional(),
|
|
|
|
|
registration: z.string().optional(),
|
|
|
|
|
flag: z.string().optional(),
|
|
|
|
|
yearBuilt: z.number().int().min(1800).max(2100).optional(),
|
|
|
|
|
builder: z.string().optional(),
|
|
|
|
|
model: z.string().optional(),
|
|
|
|
|
hullMaterial: z.string().optional(),
|
2026-05-12 14:50:58 +02:00
|
|
|
lengthFt: optionalNumericString,
|
|
|
|
|
widthFt: optionalNumericString,
|
|
|
|
|
draftFt: optionalNumericString,
|
|
|
|
|
lengthM: optionalNumericString,
|
|
|
|
|
widthM: optionalNumericString,
|
|
|
|
|
draftM: optionalNumericString,
|
2026-04-23 23:31:29 +02:00
|
|
|
owner: ownerRefSchema, // required; yacht must have an owner
|
|
|
|
|
status: z.enum(['active', 'retired', 'sold_away']).optional().default('active'),
|
|
|
|
|
notes: z.string().optional(),
|
|
|
|
|
tagIds: z.array(z.string()).optional().default([]),
|
2026-05-18 16:18:03 +02:00
|
|
|
// Phase 3c — origin tracking. Defaults to 'manual'; the EOI spawn flow
|
|
|
|
|
// sends 'eoi-generated' and the migration-0073 CHECK enforces the
|
|
|
|
|
// values at the DB level.
|
|
|
|
|
source: z.enum(['manual', 'imported', 'eoi-generated']).optional(),
|
|
|
|
|
sourceDocumentId: z.string().uuid().optional(),
|
2026-04-23 23:31:29 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const updateYachtSchema = createYachtSchema.partial().omit({ owner: true });
|
|
|
|
|
// Owner changes go through /transfer, not PATCH.
|
|
|
|
|
|
|
|
|
|
export const transferOwnershipSchema = z.object({
|
|
|
|
|
newOwner: ownerRefSchema,
|
|
|
|
|
effectiveDate: z.coerce.date(),
|
|
|
|
|
transferReason: z
|
|
|
|
|
.enum(['sale', 'inheritance', 'gift', 'company_restructure', 'other'])
|
|
|
|
|
.optional(),
|
|
|
|
|
transferNotes: z.string().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-23 23:35:30 +02:00
|
|
|
export const listYachtsSchema = baseListQuerySchema.extend({
|
2026-04-23 23:31:29 +02:00
|
|
|
ownerType: z.enum(['client', 'company']).optional(),
|
|
|
|
|
ownerId: z.string().optional(),
|
|
|
|
|
status: z.enum(['active', 'retired', 'sold_away']).optional(),
|
|
|
|
|
search: z.string().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export type CreateYachtInput = z.infer<typeof createYachtSchema>;
|
|
|
|
|
export type UpdateYachtInput = z.infer<typeof updateYachtSchema>;
|
|
|
|
|
export type TransferOwnershipInput = z.infer<typeof transferOwnershipSchema>;
|
2026-04-23 23:35:30 +02:00
|
|
|
export type ListYachtsInput = z.infer<typeof listYachtsSchema>;
|