feat(seed): rewrite seed for multi-cardinality refactor
Split seed into orchestrator (seed.ts) + per-port fixture builder (seed-data.ts). Creates three ports (Port Nimara, Marina Azzurra, Harbor Royale) and seeds each with a realistic multi-cardinality dataset: 12 berths (5 available / 5 reserved / 2 sold), 8 clients with contacts and primary addresses, 3 companies (2 active / 1 dissolved) with billing addresses, memberships exercising dual- company ownership and ended state, 12 yachts (7 client-owned / 5 company-owned) plus matching open ownership-history rows, 3 completed ownership transfers per port (client <-> company), 15 interests spanning all pipeline stages, and 8 reservations (5 active on distinct berths / 2 ended / 1 cancelled). Seed wraps per-port work in withTransaction and is idempotent: re-running detects existing company rows and skips. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1105
src/lib/db/seed-data.ts
Normal file
1105
src/lib/db/seed-data.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,29 @@
|
|||||||
/**
|
/**
|
||||||
* Seed script for Port Nimara CRM.
|
* Seed script for Port Nimara CRM.
|
||||||
*
|
*
|
||||||
* Seeds:
|
* Top-level orchestrator:
|
||||||
* - 1 Port: Port Nimara
|
* 1. Create 3 ports (idempotent):
|
||||||
* - 5 System roles with full permission maps
|
* - Port Nimara
|
||||||
* - 1 Super admin user profile (matt@portnimara.com)
|
* - Marina Azzurra
|
||||||
|
* - Harbor Royale
|
||||||
|
* 2. Create 5 system roles with full permission maps
|
||||||
|
* 3. Create the super admin user profile placeholder (matt@portnimara.com)
|
||||||
|
* 4. For each port, call `seedPortData(portId, portSlug)` from seed-data.ts
|
||||||
|
* to produce the realistic multi-cardinality fixture
|
||||||
|
* (berths, clients, companies, yachts, memberships, interests,
|
||||||
|
* reservations, ownership-transfer history).
|
||||||
|
* 5. Print a summary.
|
||||||
*
|
*
|
||||||
* Run with: npm run db:seed
|
* Run with: pnpm db:seed
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
import { eq } from 'drizzle-orm';
|
||||||
import { db } from './index';
|
import { db } from './index';
|
||||||
import { ports } from './schema/ports';
|
import { ports } from './schema/ports';
|
||||||
import { roles, userProfiles } from './schema/users';
|
import { roles, userProfiles } from './schema/users';
|
||||||
import type { RolePermissions } from './schema/users';
|
import type { RolePermissions } from './schema/users';
|
||||||
|
import { seedPortData, type SeedSummary } from './seed-data';
|
||||||
|
|
||||||
// ─── Permission Maps ─────────────────────────────────────────────────────────
|
// ─── Permission Maps ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -347,34 +357,77 @@ const VIEWER_PERMISSIONS: RolePermissions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ─── Port Definitions ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const PORT_DEFINITIONS: Array<{
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
primaryColor: string;
|
||||||
|
defaultCurrency: string;
|
||||||
|
timezone: string;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
name: 'Port Nimara',
|
||||||
|
slug: 'port-nimara',
|
||||||
|
primaryColor: '#0F4C81',
|
||||||
|
defaultCurrency: 'USD',
|
||||||
|
timezone: 'America/Anguilla',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Marina Azzurra',
|
||||||
|
slug: 'marina-azzurra',
|
||||||
|
primaryColor: '#2E86AB',
|
||||||
|
defaultCurrency: 'EUR',
|
||||||
|
timezone: 'Europe/Rome',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Harbor Royale',
|
||||||
|
slug: 'harbor-royale',
|
||||||
|
primaryColor: '#8B1E3F',
|
||||||
|
defaultCurrency: 'GBP',
|
||||||
|
timezone: 'Europe/London',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// ─── Seed Function ────────────────────────────────────────────────────────────
|
// ─── Seed Function ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function seed() {
|
async function seed() {
|
||||||
console.log('Seeding Port Nimara CRM...');
|
console.log('Seeding Port Nimara CRM...');
|
||||||
|
|
||||||
// ── 1. Port ─────────────────────────────────────────────────────────────────
|
// ── 1. Ports ────────────────────────────────────────────────────────────────
|
||||||
console.log('Creating Port Nimara...');
|
console.log('Creating ports...');
|
||||||
const [port] = await db
|
const portIds: Array<{ id: string; name: string; slug: string }> = [];
|
||||||
.insert(ports)
|
|
||||||
.values({
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
name: 'Port Nimara',
|
|
||||||
slug: 'port-nimara',
|
|
||||||
logoUrl: null,
|
|
||||||
primaryColor: '#0F4C81',
|
|
||||||
defaultCurrency: 'USD',
|
|
||||||
timezone: 'America/Anguilla',
|
|
||||||
settings: {},
|
|
||||||
isActive: true,
|
|
||||||
})
|
|
||||||
.onConflictDoNothing()
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
const portId = port?.id;
|
for (const def of PORT_DEFINITIONS) {
|
||||||
if (!portId) {
|
const [inserted] = await db
|
||||||
console.log('Port already exists, skipping...');
|
.insert(ports)
|
||||||
} else {
|
.values({
|
||||||
console.log(`Port created: ${portId}`);
|
id: crypto.randomUUID(),
|
||||||
|
name: def.name,
|
||||||
|
slug: def.slug,
|
||||||
|
logoUrl: null,
|
||||||
|
primaryColor: def.primaryColor,
|
||||||
|
defaultCurrency: def.defaultCurrency,
|
||||||
|
timezone: def.timezone,
|
||||||
|
settings: {},
|
||||||
|
isActive: true,
|
||||||
|
})
|
||||||
|
.onConflictDoNothing()
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
if (inserted) {
|
||||||
|
console.log(` Port created: ${def.name} (${inserted.id})`);
|
||||||
|
portIds.push({ id: inserted.id, name: def.name, slug: def.slug });
|
||||||
|
} else {
|
||||||
|
// Port already existed — look it up so we can still seed fixtures for it.
|
||||||
|
const [existing] = await db.select().from(ports).where(eq(ports.slug, def.slug)).limit(1);
|
||||||
|
if (existing) {
|
||||||
|
console.log(` Port exists: ${def.name} (${existing.id})`);
|
||||||
|
portIds.push({ id: existing.id, name: def.name, slug: def.slug });
|
||||||
|
} else {
|
||||||
|
console.warn(` Port insert conflict but lookup returned no row: ${def.slug}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 2. System Roles ─────────────────────────────────────────────────────────
|
// ── 2. System Roles ─────────────────────────────────────────────────────────
|
||||||
@@ -426,7 +479,7 @@ async function seed() {
|
|||||||
|
|
||||||
for (const role of systemRoles) {
|
for (const role of systemRoles) {
|
||||||
await db.insert(roles).values(role).onConflictDoNothing();
|
await db.insert(roles).values(role).onConflictDoNothing();
|
||||||
console.log(`Role created: ${role.name}`);
|
console.log(` Role: ${role.name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 3. Super Admin User Profile ─────────────────────────────────────────────
|
// ── 3. Super Admin User Profile ─────────────────────────────────────────────
|
||||||
@@ -453,7 +506,32 @@ async function seed() {
|
|||||||
})
|
})
|
||||||
.onConflictDoNothing();
|
.onConflictDoNothing();
|
||||||
|
|
||||||
console.log(`Super admin profile created for user_id: ${superAdminUserId}`);
|
console.log(` Super admin profile for user_id: ${superAdminUserId}`);
|
||||||
|
|
||||||
|
// ── 4. Per-port fixtures ────────────────────────────────────────────────────
|
||||||
|
console.log('');
|
||||||
|
console.log('Seeding per-port fixtures...');
|
||||||
|
|
||||||
|
const summaries: Array<{ name: string; summary: SeedSummary | null }> = [];
|
||||||
|
for (const p of portIds) {
|
||||||
|
console.log(` [${p.slug}] seeding fixture data...`);
|
||||||
|
const summary = await seedPortData(p.id, p.slug);
|
||||||
|
summaries.push({ name: p.name, summary });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 5. Summary ─────────────────────────────────────────────────────────────
|
||||||
|
console.log('');
|
||||||
|
console.log('─── Summary ───────────────────────────────────────────────');
|
||||||
|
for (const s of summaries) {
|
||||||
|
if (s.summary === null) {
|
||||||
|
console.log(` ✓ Port "${s.name}" — already seeded (skipped)`);
|
||||||
|
} else {
|
||||||
|
const x = s.summary;
|
||||||
|
console.log(
|
||||||
|
` ✓ Port "${s.name}" — ${x.berths} berths, ${x.clients} clients, ${x.companies} companies, ${x.yachts} yachts, ${x.interests} interests, ${x.reservations} reservations`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('Seed complete!');
|
console.log('Seed complete!');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|||||||
Reference in New Issue
Block a user