import { and, eq } from 'drizzle-orm'; import { z } from 'zod'; import { db } from '@/lib/db'; import { berths } from '@/lib/db/schema/berths'; import { createBerth, updateBerth } from '@/lib/services/berths.service'; import type { ImportAdapter, MappedRow } from '../types'; /** Canonicalize a mooring to the unified `^[A-Z]+\d+$` form ("A1", "D32"): * uppercase letters, drop a hyphen + leading zeros on the number. */ function canonMoo(raw: string): string { const m = /^([A-Za-z]+)-?0*(\d+)$/.exec(raw.trim()); return m ? `${m[1]!.toUpperCase()}${parseInt(m[2]!, 10)}` : raw.trim().toUpperCase(); } const num = (s: string | undefined): number | undefined => s === undefined || s === '' ? undefined : Number(s); export const berthsAdapter: ImportAdapter = { key: 'berths', label: 'Berths', order: 4, dependsOn: [], targetFields: [ { key: 'mooringNumber', label: 'Mooring number', required: true, aliases: ['mooring', 'berth', 'berthnumber'], zod: z.string().regex(/^[A-Za-z]+-?0*\d+$/, 'Use a form like A1, B12, E18'), }, { key: 'area', label: 'Area', required: true, aliases: ['dock', 'zone'], zod: z.string().min(1), }, { key: 'lengthFt', label: 'Length (ft)', required: false, zod: z.coerce.number() }, { key: 'widthFt', label: 'Width (ft)', required: false, zod: z.coerce.number() }, { key: 'draftFt', label: 'Draft (ft)', required: false, zod: z.coerce.number() }, { key: 'price', label: 'Price', required: false, zod: z.coerce.number() }, { key: 'priceCurrency', label: 'Currency', required: false, zod: z.string().length(3), }, { key: 'status', label: 'Status', required: false, zod: z.enum(['available', 'under_offer', 'sold']), }, ], matchKey: (row) => (row.mooringNumber ? canonMoo(row.mooringNumber) : null), findExisting: async (portId, key) => { const row = await db.query.berths.findFirst({ where: and(eq(berths.portId, portId), eq(berths.mooringNumber, key)), columns: { id: true }, }); return row ?? null; }, insert: async (row, _resolved, ctx) => { const b = await createBerth(ctx.portId, buildBerthInput(row), ctx.meta); return { id: b.id }; }, update: async (id, row, _resolved, ctx) => { const full = buildBerthInput(row); // Update spec fields only — mooring is the match key, and status has its // own dedicated endpoint (not part of UpdateBerthInput). await updateBerth( id, ctx.portId, { area: full.area, lengthFt: full.lengthFt, widthFt: full.widthFt, draftFt: full.draftFt, price: full.price, priceCurrency: full.priceCurrency, }, ctx.meta, ); }, }; function buildBerthInput(row: MappedRow) { return { mooringNumber: canonMoo(row.mooringNumber!), area: row.area!, // required field — present after validation lengthFt: num(row.lengthFt), widthFt: num(row.widthFt), draftFt: num(row.draftFt), price: num(row.price), priceCurrency: row.priceCurrency, status: (row.status as 'available' | 'under_offer' | 'sold' | undefined) ?? 'available', }; }