feat(seed): replace 12 hand-rolled berths with 117-row NocoDB snapshot
The old seed only had 12 berths with made-up area names ("North Pier",
"Central Basin", etc.) and placeholder dimensions. Devs now get the real
117 berths exported from the legacy NocoDB Berths table — every editable
column populated with real production values.
What's in the snapshot (src/lib/db/seed-data/berths.json):
- 117 berths total (61 available / 45 under_offer / 11 sold)
- Areas A through E (matches NocoDB single-select)
- All numeric fields filled: length / width / draft (ft + m), water depth,
nominal boat size, power capacity (kW), voltage (V)
- All NocoDB single-selects filled where present: side pontoon,
mooring type, cleat/bollard type+capacity, access
- Bow facing, status_override_mode, berth_approved carried forward as-is
- Status normalized to lowercase snake_case ("Under Offer" -> "under_offer")
- Mooring numbers reformatted A1 -> A-01 to keep the existing "Letter-NN"
convention used elsewhere in the codebase
Pre-sorted to preserve seed semantics:
idx 0..4 -> 5 available (small) -- "open" / "details_sent" interests
idx 5..9 -> 5 under_offer (medium) -- "eoi_signed" / "deposit" / "contract"
idx 10..11 -> 2 sold (large) -- "completed" interests
This means existing interest/reservation seeds that index berthRows[0..11]
keep their semantic alignment without code changes.
End-to-end verified by clearing Marina Azzurra and re-seeding:
Port "Marina Azzurra" -- 117 berths, 8 clients, 3 companies, 12 yachts,
15 interests, 8 reservations
Future devs running `pnpm db:seed` on a fresh DB will now get realistic
berth data automatically.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,13 @@
|
|||||||
* Exports `seedPortData(portId, portSlug)` — creates a realistic,
|
* Exports `seedPortData(portId, portSlug)` — creates a realistic,
|
||||||
* multi-cardinality data fixture for one port:
|
* multi-cardinality data fixture for one port:
|
||||||
*
|
*
|
||||||
* - 12 berths (5 available / 5 reserved-active / 2 sold)
|
* - 117 berths imported from a snapshot of the legacy NocoDB Berths
|
||||||
|
* table (`src/lib/db/seed-data/berths.json`). The snapshot is reordered
|
||||||
|
* so the first 12 entries satisfy the index assumptions used further
|
||||||
|
* down for interest/reservation linkage:
|
||||||
|
* idx 0..4 — available (small)
|
||||||
|
* idx 5..9 — under_offer (medium)
|
||||||
|
* idx 10..11 — sold (large)
|
||||||
* - 3 companies (2 active, 1 dissolved) with primary billing addresses
|
* - 3 companies (2 active, 1 dissolved) with primary billing addresses
|
||||||
* - 8 clients + contacts + primary addresses
|
* - 8 clients + contacts + primary addresses
|
||||||
* - Memberships tying clients to companies (incl. multi-company + ended)
|
* - Memberships tying clients to companies (incl. multi-company + ended)
|
||||||
@@ -39,6 +45,44 @@ import {
|
|||||||
getStandardEoiTemplateHtml,
|
getStandardEoiTemplateHtml,
|
||||||
STANDARD_EOI_MERGE_FIELDS,
|
STANDARD_EOI_MERGE_FIELDS,
|
||||||
} from '@/lib/pdf/templates/eoi-standard-inapp';
|
} from '@/lib/pdf/templates/eoi-standard-inapp';
|
||||||
|
import berthSnapshot from './seed-data/berths.json';
|
||||||
|
|
||||||
|
// ─── Berth snapshot ──────────────────────────────────────────────────────────
|
||||||
|
// 117 rows imported from the legacy NocoDB Berths table on 2026-05-03.
|
||||||
|
// Refresh by re-running the snapshot script (see git history of this file).
|
||||||
|
type SeedBerth = {
|
||||||
|
legacyId: number;
|
||||||
|
mooringNumber: string;
|
||||||
|
legacyMooringNumber: string;
|
||||||
|
area: string | null;
|
||||||
|
status: 'available' | 'under_offer' | 'sold';
|
||||||
|
lengthFt: number | null;
|
||||||
|
widthFt: number | null;
|
||||||
|
draftFt: number | null;
|
||||||
|
lengthM: number | null;
|
||||||
|
widthM: number | null;
|
||||||
|
draftM: number | null;
|
||||||
|
widthIsMinimum: boolean;
|
||||||
|
nominalBoatSize: number | null;
|
||||||
|
nominalBoatSizeM: number | null;
|
||||||
|
waterDepth: number | null;
|
||||||
|
waterDepthM: number | null;
|
||||||
|
waterDepthIsMinimum: boolean;
|
||||||
|
sidePontoon: string | null;
|
||||||
|
powerCapacity: number | null;
|
||||||
|
voltage: number | null;
|
||||||
|
mooringType: string | null;
|
||||||
|
cleatType: string | null;
|
||||||
|
cleatCapacity: string | null;
|
||||||
|
bollardType: string | null;
|
||||||
|
bollardCapacity: string | null;
|
||||||
|
access: string | null;
|
||||||
|
price: number | null;
|
||||||
|
bowFacing: string | null;
|
||||||
|
berthApproved: boolean;
|
||||||
|
statusOverrideMode: string | null;
|
||||||
|
};
|
||||||
|
const BERTH_SNAPSHOT = berthSnapshot as SeedBerth[];
|
||||||
|
|
||||||
// ─── Tunables ────────────────────────────────────────────────────────────────
|
// ─── Tunables ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -77,144 +121,44 @@ export async function seedPortData(portId: string, portSlug: string): Promise<Se
|
|||||||
|
|
||||||
return withTransaction(async (tx) => {
|
return withTransaction(async (tx) => {
|
||||||
// ── 1. Berths ──────────────────────────────────────────────────────────
|
// ── 1. Berths ──────────────────────────────────────────────────────────
|
||||||
// 12 berths: [0..4] available, [5..9] will be reserved-active, [10..11] sold.
|
// 117 berths seeded from the legacy NocoDB Berths snapshot.
|
||||||
// We mark 5..9 as 'under_offer' (closest to "reserved via active reservation")
|
// The JSON file is pre-sorted so the first 12 indexes satisfy the
|
||||||
// and 10..11 as 'sold'; 0..4 remain 'available'.
|
// status semantics expected by the interest/reservation seeds:
|
||||||
const BERTH_SPECS: Array<{
|
// idx 0..4 available, idx 5..9 under_offer, idx 10..11 sold.
|
||||||
mooring: string;
|
|
||||||
area: string;
|
|
||||||
lengthM: string;
|
|
||||||
widthM: string;
|
|
||||||
draftM: string;
|
|
||||||
price: string;
|
|
||||||
status: 'available' | 'under_offer' | 'sold';
|
|
||||||
}> = [
|
|
||||||
{
|
|
||||||
mooring: 'A-01',
|
|
||||||
area: 'North Pier',
|
|
||||||
lengthM: '15',
|
|
||||||
widthM: '5',
|
|
||||||
draftM: '2.5',
|
|
||||||
price: '250000',
|
|
||||||
status: 'available',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'A-02',
|
|
||||||
area: 'North Pier',
|
|
||||||
lengthM: '18',
|
|
||||||
widthM: '5.5',
|
|
||||||
draftM: '2.8',
|
|
||||||
price: '320000',
|
|
||||||
status: 'available',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'A-03',
|
|
||||||
area: 'North Pier',
|
|
||||||
lengthM: '20',
|
|
||||||
widthM: '6',
|
|
||||||
draftM: '3.0',
|
|
||||||
price: '420000',
|
|
||||||
status: 'available',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'B-01',
|
|
||||||
area: 'Central Basin',
|
|
||||||
lengthM: '25',
|
|
||||||
widthM: '7',
|
|
||||||
draftM: '3.5',
|
|
||||||
price: '580000',
|
|
||||||
status: 'available',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'B-02',
|
|
||||||
area: 'Central Basin',
|
|
||||||
lengthM: '30',
|
|
||||||
widthM: '8',
|
|
||||||
draftM: '4.0',
|
|
||||||
price: '780000',
|
|
||||||
status: 'available',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'B-03',
|
|
||||||
area: 'Central Basin',
|
|
||||||
lengthM: '35',
|
|
||||||
widthM: '8.5',
|
|
||||||
draftM: '4.2',
|
|
||||||
price: '950000',
|
|
||||||
status: 'under_offer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'C-01',
|
|
||||||
area: 'South Marina',
|
|
||||||
lengthM: '40',
|
|
||||||
widthM: '9',
|
|
||||||
draftM: '4.5',
|
|
||||||
price: '1250000',
|
|
||||||
status: 'under_offer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'C-02',
|
|
||||||
area: 'South Marina',
|
|
||||||
lengthM: '45',
|
|
||||||
widthM: '10',
|
|
||||||
draftM: '4.8',
|
|
||||||
price: '1600000',
|
|
||||||
status: 'under_offer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'C-03',
|
|
||||||
area: 'South Marina',
|
|
||||||
lengthM: '50',
|
|
||||||
widthM: '11',
|
|
||||||
draftM: '5.0',
|
|
||||||
price: '2100000',
|
|
||||||
status: 'under_offer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'D-01',
|
|
||||||
area: 'Superyacht Dock',
|
|
||||||
lengthM: '60',
|
|
||||||
widthM: '13',
|
|
||||||
draftM: '5.5',
|
|
||||||
price: '3200000',
|
|
||||||
status: 'under_offer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'D-02',
|
|
||||||
area: 'Superyacht Dock',
|
|
||||||
lengthM: '70',
|
|
||||||
widthM: '14',
|
|
||||||
draftM: '6.0',
|
|
||||||
price: '4500000',
|
|
||||||
status: 'sold',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
mooring: 'D-03',
|
|
||||||
area: 'Superyacht Dock',
|
|
||||||
lengthM: '80',
|
|
||||||
widthM: '15',
|
|
||||||
draftM: '6.5',
|
|
||||||
price: '6800000',
|
|
||||||
status: 'sold',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const berthRows = await tx
|
const berthRows = await tx
|
||||||
.insert(berths)
|
.insert(berths)
|
||||||
.values(
|
.values(
|
||||||
BERTH_SPECS.map((b) => ({
|
BERTH_SNAPSHOT.map((b) => ({
|
||||||
portId,
|
portId,
|
||||||
mooringNumber: b.mooring,
|
mooringNumber: b.mooringNumber,
|
||||||
area: b.area,
|
area: b.area,
|
||||||
status: b.status,
|
status: b.status,
|
||||||
lengthM: b.lengthM,
|
lengthFt: b.lengthFt != null ? String(b.lengthFt) : null,
|
||||||
widthM: b.widthM,
|
widthFt: b.widthFt != null ? String(b.widthFt) : null,
|
||||||
draftM: b.draftM,
|
draftFt: b.draftFt != null ? String(b.draftFt) : null,
|
||||||
lengthFt: (Number(b.lengthM) * 3.28084).toFixed(2),
|
lengthM: b.lengthM != null ? String(b.lengthM) : null,
|
||||||
widthFt: (Number(b.widthM) * 3.28084).toFixed(2),
|
widthM: b.widthM != null ? String(b.widthM) : null,
|
||||||
draftFt: (Number(b.draftM) * 3.28084).toFixed(2),
|
draftM: b.draftM != null ? String(b.draftM) : null,
|
||||||
price: b.price,
|
widthIsMinimum: b.widthIsMinimum,
|
||||||
|
nominalBoatSize: b.nominalBoatSize != null ? String(b.nominalBoatSize) : null,
|
||||||
|
nominalBoatSizeM: b.nominalBoatSizeM != null ? String(b.nominalBoatSizeM) : null,
|
||||||
|
waterDepth: b.waterDepth != null ? String(b.waterDepth) : null,
|
||||||
|
waterDepthM: b.waterDepthM != null ? String(b.waterDepthM) : null,
|
||||||
|
waterDepthIsMinimum: b.waterDepthIsMinimum,
|
||||||
|
sidePontoon: b.sidePontoon,
|
||||||
|
powerCapacity: b.powerCapacity != null ? String(b.powerCapacity) : null,
|
||||||
|
voltage: b.voltage != null ? String(b.voltage) : null,
|
||||||
|
mooringType: b.mooringType,
|
||||||
|
cleatType: b.cleatType,
|
||||||
|
cleatCapacity: b.cleatCapacity,
|
||||||
|
bollardType: b.bollardType,
|
||||||
|
bollardCapacity: b.bollardCapacity,
|
||||||
|
access: b.access,
|
||||||
|
price: b.price != null ? String(b.price) : null,
|
||||||
priceCurrency: 'USD',
|
priceCurrency: 'USD',
|
||||||
|
bowFacing: b.bowFacing,
|
||||||
|
berthApproved: b.berthApproved,
|
||||||
|
statusOverrideMode: b.statusOverrideMode,
|
||||||
tenureType: 'permanent' as const,
|
tenureType: 'permanent' as const,
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
|
|||||||
3746
src/lib/db/seed-data/berths.json
Normal file
3746
src/lib/db/seed-data/berths.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user