feat: production seed script for Pelagia Marine (pnpm db:seed:prod)

Idempotent seed with real company data:
- 14 users (SSO/no-password): Accounts, Managers, Technical, Manning
- 7 sites: Head Office (HOFC), PMS Kochi, Laccadives, Haldia,
  Thilakkam, Kavaratti, Thinnakara
- 10 vessels assigned to their respective sites:
  HNR1-4, Champion, Hanunam, Sejal, Sejal 2, GD 3000, Thilakkam
- Full accounting code hierarchy (Rev. 01/251227) — 300+ codes across
  7 top categories via two-pass upsert to wire parent links

Run with: pnpm db:seed:prod

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hardik 2026-05-30 04:26:38 +05:30
parent a1b77d8b00
commit 0e3a79ecd4
2 changed files with 158 additions and 0 deletions

View file

@ -20,6 +20,7 @@
"db:migrate": "prisma migrate dev",
"db:migrate:deploy": "prisma migrate deploy",
"db:seed": "tsx prisma/seed.ts",
"db:seed:prod": "tsx prisma/seed-prod.ts",
"db:studio": "prisma studio",
"db:reset": "prisma migrate reset",
"email:preview": "email dev --dir emails --port 3001"

157
App/prisma/seed-prod.ts Normal file
View file

@ -0,0 +1,157 @@
/**
* Production seed Pelagia Marine Services
*
* Idempotent: safe to run multiple times (all operations are upsert).
* Run with: pnpm db:seed:prod
*
* Seeds:
* - Users (SSO, no password)
* - Sites
* - Vessels (assigned to sites)
* - Accounting codes (full hierarchy from Rev. 01/251227)
*/
import { PrismaClient, Role } from "@prisma/client";
import { ACCOUNTING_CODES } from "./accounting-codes-data";
const db = new PrismaClient();
// ─── Users ────────────────────────────────────────────────────────────────────
const USERS: { employeeId: string; name: string; email: string; role: Role }[] = [
{ employeeId: "PMS-001", name: "Akshata Teli", email: "akshata@pelagiamarine.com", role: Role.ACCOUNTS },
{ employeeId: "PMS-002", name: "Chhagan Sarang", email: "chhagan.sarang@pelagiamarine.com", role: Role.MANAGER },
{ employeeId: "PMS-003", name: "Dipali K", email: "dipali.k@pelagiamarine.com", role: Role.ACCOUNTS },
{ employeeId: "PMS-004", name: "Eeshan Singh", email: "eeshan.singh@pelagiamarine.com", role: Role.TECHNICAL },
{ employeeId: "PMS-005", name: "Kaushal Pal Singh", email: "kps@pelagiamarine.com", role: Role.MANAGER },
{ employeeId: "PMS-006", name: "Manjuprasad B", email: "manjuprasad.b@pelagiamarine.com", role: Role.TECHNICAL },
{ employeeId: "PMS-007", name: "Mayur Deore", email: "mayur@pelagiamarine.com", role: Role.MANNING },
{ employeeId: "PMS-008", name: "Nikita Accounts", email: "nikita.m@pelagiamarine.com", role: Role.ACCOUNTS },
{ employeeId: "PMS-009", name: "Rakesh Kumar Pandey", email: "rkp@pelagiamarine.com", role: Role.MANAGER },
{ employeeId: "PMS-010", name: "Shailesh B", email: "shailesh.b@pelagiamarine.com", role: Role.ACCOUNTS },
{ employeeId: "PMS-011", name: "Shrikant T", email: "shrikant.t@pelagiamarine.com", role: Role.TECHNICAL },
{ employeeId: "PMS-012", name: "Sunil Gupta", email: "sunil.gupta@pelagiamarine.com", role: Role.MANNING },
{ employeeId: "PMS-013", name: "Supriya Sutar", email: "supriya.s@pelagiamarine.com", role: Role.TECHNICAL },
{ employeeId: "PMS-014", name: "Tajinder Kaur", email: "tajinder.kaur@pelagiamarine.com", role: Role.MANAGER },
];
// ─── Sites ────────────────────────────────────────────────────────────────────
const SITES: { code: string; name: string }[] = [
{ code: "HOFC", name: "Head Office" },
{ code: "PMSK", name: "PMS Kochi" },
{ code: "LACD", name: "Laccadives" },
{ code: "HLDA", name: "Haldia" },
{ code: "THKM", name: "Thilakkam" },
{ code: "KVRT", name: "Kavaratti" },
{ code: "THNK", name: "Thinnakara" },
];
// ─── Vessels (code, name, site code) ─────────────────────────────────────────
const VESSELS: { code: string; name: string; siteCode: string }[] = [
{ code: "HNR1", name: "HNR 1", siteCode: "HLDA" },
{ code: "HNR2", name: "HNR 2", siteCode: "LACD" },
{ code: "HNR3", name: "HNR 3", siteCode: "THKM" },
{ code: "HNR4", name: "HNR 4", siteCode: "THNK" },
{ code: "CHAMPION", name: "Champion", siteCode: "PMSK" },
{ code: "HANUNAM", name: "Hanunam", siteCode: "KVRT" },
{ code: "SEJAL", name: "Sejal", siteCode: "HLDA" },
{ code: "SEJAL2", name: "Sejal 2", siteCode: "LACD" },
{ code: "GD3000", name: "GD 3000", siteCode: "THKM" },
{ code: "THILAKKAM", name: "Thilakkam", siteCode: "THNK" },
];
// ─── Main ─────────────────────────────────────────────────────────────────────
async function main() {
console.log("🌱 Pelagia production seed starting…\n");
// ── Users ──────────────────────────────────────────────────────────────────
console.log("👤 Seeding users…");
for (const u of USERS) {
await db.user.upsert({
where: { email: u.email },
update: { name: u.name, role: u.role },
create: {
employeeId: u.employeeId,
email: u.email,
name: u.name,
role: u.role,
// No passwordHash — SSO-only login
},
});
console.log(`${u.name} <${u.email}> [${u.role}]`);
}
// ── Sites ──────────────────────────────────────────────────────────────────
console.log("\n📍 Seeding sites…");
const siteIdMap = new Map<string, string>();
for (const s of SITES) {
const site = await db.site.upsert({
where: { code: s.code },
update: { name: s.name },
create: { code: s.code, name: s.name },
});
siteIdMap.set(s.code, site.id);
console.log(`${s.name} (${s.code})`);
}
// ── Vessels ────────────────────────────────────────────────────────────────
console.log("\n🚢 Seeding vessels…");
for (const v of VESSELS) {
const siteId = siteIdMap.get(v.siteCode);
if (!siteId) {
console.warn(` ⚠ Unknown site code "${v.siteCode}" for vessel ${v.code} — skipping`);
continue;
}
await db.vessel.upsert({
where: { code: v.code },
update: { name: v.name, siteId },
create: { code: v.code, name: v.name, siteId },
});
console.log(`${v.name} (${v.code}) → ${v.siteCode}`);
}
// ── Accounting Codes ───────────────────────────────────────────────────────
console.log("\n📊 Seeding accounting codes…");
const codeIdMap = new Map<string, string>();
// Pass 1 — upsert every entry without parent links
for (const entry of ACCOUNTING_CODES) {
const rec = await db.account.upsert({
where: { code: entry.code },
update: { name: entry.name },
create: { code: entry.code, name: entry.name },
});
codeIdMap.set(entry.code, rec.id);
}
// Pass 2 — wire up parent relationships
for (const entry of ACCOUNTING_CODES) {
if (entry.parentCode) {
const parentId = codeIdMap.get(entry.parentCode);
if (parentId) {
await db.account.update({
where: { code: entry.code },
data: { parentId },
});
}
}
}
const leafCount = ACCOUNTING_CODES.filter((e) => {
return !ACCOUNTING_CODES.some((other) => other.parentCode === e.code);
}).length;
console.log(`${ACCOUNTING_CODES.length} codes (${leafCount} selectable leaf items)`);
console.log("\n✅ Production seed complete.");
}
main()
.catch((e) => {
console.error("❌ Seed failed:", e);
process.exit(1);
})
.finally(() => db.$disconnect());