pelagia-portal/App/prisma/seed.ts
Hardik 280966a369 refactor: revert cost centre to vessels only, remove vessel-site link
Cost Centre on PO forms now shows only Vessels (plain vesselId field).
Sites are a separate concept and not selectable as cost centres.

- PurchaseOrder.vesselId is required again (NOT NULL restored)
- Vessel.siteId and vessel->site relation removed from schema
- DB migration: drops Vessel.siteId column, restores PO.vesselId NOT NULL
- All PO forms (new/edit/import/manager-edit): plain vessel <select> with
  code-prefixed labels (e.g. "HNR1 — HNR 1")
- History, approvals, dashboard, my-orders, payments: back to vesselId
  filter params and po.vessel.name display
- Admin vessels: removed Site column and site-assignment dropdown
- Admin sites detail page: removed "Assigned Vessels" section
- Sites table: removed Vessels count column (no longer linked)
- seed-prod.ts and seed.ts: vessels created without siteId
- SearchableSelect accounting code picker retained from previous commit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 18:14:24 +05:30

1153 lines
45 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { PrismaClient, Role } from "@prisma/client";
import bcrypt from "bcryptjs";
import { ACCOUNTING_CODES } from "./accounting-codes-data";
const db = new PrismaClient();
const hash = (p: string) => bcrypt.hash(p, 12);
const d = (daysAgo: number) => new Date(Date.now() - daysAgo * 86_400_000);
async function main() {
console.log("Seeding database…");
// ─── Users ──────────────────────────────────────────────────────────────────
const admin = await db.user.upsert({
where: { email: "admin@pelagia.local" },
update: {},
create: { employeeId: "EMP-001", email: "admin@pelagia.local", name: "System Admin", passwordHash: await hash("admin1234"), role: Role.ADMIN },
});
const manager = await db.user.upsert({
where: { email: "manager@pelagia.local" },
update: {},
create: { employeeId: "EMP-002", email: "manager@pelagia.local", name: "James Hartwell", passwordHash: await hash("manager1234"), role: Role.MANAGER },
});
const manager2 = await db.user.upsert({
where: { email: "manager2@pelagia.local" },
update: {},
create: { employeeId: "EMP-011", email: "manager2@pelagia.local", name: "Lakshmi Iyer", passwordHash: await hash("manager1234"), role: Role.MANAGER },
});
const technical = await db.user.upsert({
where: { email: "tech@pelagia.local" },
update: {},
create: { employeeId: "EMP-003", email: "tech@pelagia.local", name: "Maria Santos", passwordHash: await hash("tech1234"), role: Role.TECHNICAL },
});
const tech2 = await db.user.upsert({
where: { email: "tech2@pelagia.local" },
update: {},
create: { employeeId: "EMP-006", email: "tech2@pelagia.local", name: "Arjun Sharma", passwordHash: await hash("tech1234"), role: Role.TECHNICAL },
});
const tech3 = await db.user.upsert({
where: { email: "tech3@pelagia.local" },
update: {},
create: { employeeId: "EMP-010", email: "tech3@pelagia.local", name: "David Chen", passwordHash: await hash("tech1234"), role: Role.TECHNICAL },
});
const accounts = await db.user.upsert({
where: { email: "accounts@pelagia.local" },
update: {},
create: { employeeId: "EMP-004", email: "accounts@pelagia.local", name: "Chen Wei", passwordHash: await hash("accounts1234"), role: Role.ACCOUNTS },
});
const accounts2 = await db.user.upsert({
where: { email: "accounts2@pelagia.local" },
update: {},
create: { employeeId: "EMP-012", email: "accounts2@pelagia.local", name: "Anita Bose", passwordHash: await hash("accounts1234"), role: Role.ACCOUNTS },
});
const manning = await db.user.upsert({
where: { email: "manning@pelagia.local" },
update: {},
create: { employeeId: "EMP-005", email: "manning@pelagia.local", name: "Raj Patel", passwordHash: await hash("manning1234"), role: Role.MANNING },
});
const manning2 = await db.user.upsert({
where: { email: "manning2@pelagia.local" },
update: {},
create: { employeeId: "EMP-007", email: "manning2@pelagia.local", name: "Fatima Al-Hassan", passwordHash: await hash("manning1234"), role: Role.MANNING },
});
const superuser = await db.user.upsert({
where: { email: "superuser@pelagia.local" },
update: {},
create: { employeeId: "EMP-008", email: "superuser@pelagia.local", name: "Vikram Menon", passwordHash: await hash("super1234"), role: Role.SUPERUSER },
});
await db.user.upsert({
where: { email: "auditor@pelagia.local" },
update: {},
create: { employeeId: "EMP-009", email: "auditor@pelagia.local", name: "Priya Krishnan", passwordHash: await hash("audit1234"), role: Role.AUDITOR },
});
// ─── Sites ───────────────────────────────────────────────────────────────────
const siteBOM = await db.site.upsert({
where: { code: "BOM" },
update: {},
create: { name: "Mumbai Port", code: "BOM", address: "Mumbai Port Trust, Ballard Pier, Mumbai 400001", latitude: 18.9313, longitude: 72.8349 },
});
const siteJNP = await db.site.upsert({
where: { code: "JNP" },
update: {},
create: { name: "JNPT", code: "JNP", address: "Jawaharlal Nehru Port, Nhava Sheva, Navi Mumbai 400707", latitude: 18.9458, longitude: 72.9442 },
});
const siteKDL = await db.site.upsert({
where: { code: "KDL" },
update: {},
create: { name: "Kandla Port", code: "KDL", address: "Deendayal Port, Gandhidham, Kutch, Gujarat 370210", latitude: 23.0275, longitude: 70.2168 },
});
const siteVIZ = await db.site.upsert({
where: { code: "VIZ" },
update: {},
create: { name: "Visakhapatnam Port", code: "VIZ", address: "Port Area, Visakhapatnam, Andhra Pradesh 530035", latitude: 17.6868, longitude: 83.2185 },
});
const siteCHE = await db.site.upsert({
where: { code: "CHE" },
update: {},
create: { name: "Chennai Port", code: "CHE", address: "Rajaji Salai, Royapuram, Chennai, Tamil Nadu 600001", latitude: 13.0921, longitude: 80.2974 },
});
const siteKOC = await db.site.upsert({
where: { code: "KOC" },
update: {},
create: { name: "Kochi Port", code: "KOC", address: "Willingdon Island, Kochi, Kerala 682009", latitude: 9.9614, longitude: 76.2601 },
});
const siteHAL = await db.site.upsert({
where: { code: "HAL" },
update: {},
create: { name: "Haldia Port", code: "HAL", address: "Haldia Dock Complex, Haldia, West Bengal 721607", latitude: 22.0291, longitude: 88.0795 },
});
const sitePAR = await db.site.upsert({
where: { code: "PAR" },
update: {},
create: { name: "Paradip Port", code: "PAR", address: "Paradip Port Trust, Paradip, Odisha 754142", latitude: 20.3162, longitude: 86.6102 },
});
const siteMNG = await db.site.upsert({
where: { code: "MNG" },
update: {},
create: { name: "New Mangalore Port", code: "MNG", address: "Panambur, Mangalore, Karnataka 575010", latitude: 12.9141, longitude: 74.8560 },
});
const siteGOA = await db.site.upsert({
where: { code: "GOA" },
update: {},
create: { name: "Mormugao Port", code: "GOA", address: "Headland Sada, Vasco da Gama, Goa 403802", latitude: 15.4099, longitude: 73.7997 },
});
await db.site.upsert({
where: { code: "TUT" },
update: {},
create: { name: "V.O. Chidambaranar Port", code: "TUT", address: "Port Area, Thoothukudi, Tamil Nadu 628004", latitude: 8.7714, longitude: 78.1348 },
});
await db.site.upsert({
where: { code: "COC" },
update: {},
create: { name: "Cochin Shipyard Dry Dock", code: "COC", address: "Cochin Shipyard Ltd, Perumanoor, Kochi, Kerala 682015", latitude: 9.9419, longitude: 76.2731 },
});
// ─── Vessels (Cost Centres) ──────────────────────────────────────────────────
const findOrCreateVessel = async (name: string, code: string) => {
const vessel = await db.vessel.findFirst({ where: { name } });
if (vessel) return vessel;
return db.vessel.create({ data: { name, code } });
};
const mvStar = await findOrCreateVessel("MV Pelagia Star", "SITE-001");
const mvWind = await findOrCreateVessel("MV Aegean Wind", "SITE-002");
const mvPoseidon = await findOrCreateVessel("MV Poseidon", "SITE-003");
const mvNereid = await findOrCreateVessel("MV Nereid", "SITE-004");
const mvThetis = await findOrCreateVessel("MV Thetis", "SITE-005");
const mvTriton = await findOrCreateVessel("MV Triton", "SITE-006");
const mvAmphitrite = await findOrCreateVessel("MV Amphitrite", "SITE-007");
const mvProteus = await findOrCreateVessel("MV Proteus", "SITE-008");
const mvGalatea = await findOrCreateVessel("MV Galatea", "SITE-009");
const mvCallisto = await findOrCreateVessel("MV Callisto", "SITE-010");
await findOrCreateVessel("MV Doris", "SITE-011");
// ─── Accounting Codes (hierarchical) ─────────────────────────────────────────
// Seed in two passes: first create all entries without parentId, then link parents
const codeIdMap = new Map<string, string>();
// Pass 1: upsert all entries without parentId to get their IDs
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: link 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 },
});
}
}
}
// Convenience variables for PO seed data below (map to real leaf codes)
const accTechOps = { id: codeIdMap.get("401012")! }; // Spares- Others
const accCrewMgt = { id: codeIdMap.get("500101")! }; // Salary
const accFuel = { id: codeIdMap.get("700101")! }; // Diesel
const accSafety = { id: codeIdMap.get("401023")! }; // Stores- Safety Equipment & PPE
const accPaint = { id: codeIdMap.get("401027")! }; // Stores- Paints
const accElect = { id: codeIdMap.get("401028")! }; // Stores- Electrical
const accNavig = { id: codeIdMap.get("600201")! }; // Flag Survey fee
const accStores = { id: codeIdMap.get("401031")! }; // Stores- Others
const accDeck = { id: codeIdMap.get("401030")! }; // Stores- Tools
// ─── Vendors ─────────────────────────────────────────────────────────────────
const v1 = await db.vendor.upsert({
where: { vendorId: "VND-0001" },
update: {},
create: {
name: "Marine Parts International",
vendorId: "VND-0001",
address: "Plot 12, MIDC Industrial Area, Turbhe, Navi Mumbai 400705",
pincode: "400705",
gstin: "27AABCM1234A1Z5",
isVerified: true,
latitude: 19.0144,
longitude: 73.0297,
contacts: {
create: [
{ name: "Tony Nguyen", role: "Procurement Head", mobile: "9820123456", email: "tony@marinepartsinternational.com", isPrimary: true },
{ name: "Aisha Patel", role: "Sales Executive", mobile: "9820654321", email: "aisha@marinepartsinternational.com", isPrimary: false },
],
},
},
});
const v2 = await db.vendor.upsert({
where: { vendorId: "VND-0002" },
update: {},
create: {
name: "Global Crew Supplies",
vendorId: "VND-0002",
address: "14B, Harbour Street, Mazgaon, Mumbai 400010",
pincode: "400010",
isVerified: true,
latitude: 18.9710,
longitude: 72.8431,
contacts: {
create: [
{ name: "Sarah Kim", role: "Director", mobile: "9821234567", email: "sarah@globalcrewsupplies.com", isPrimary: true },
],
},
},
});
const v3 = await db.vendor.upsert({
where: { vendorId: "VND-0003" },
update: {},
create: {
name: "Atlas Ship Chandlers",
vendorId: "VND-0003",
address: "Unit 7, Sassoon Dock, Colaba, Mumbai 400005",
pincode: "400005",
isVerified: false,
latitude: 18.9056,
longitude: 72.8265,
contacts: {
create: [
{ name: "Marco Rossi", role: "Manager", mobile: "9833456789", email: "marco@atlaschandlers.com", isPrimary: true },
],
},
},
});
const v4 = await db.vendor.upsert({
where: { vendorId: "VND-0004" },
update: {},
create: {
name: "Apar Industries Ltd",
vendorId: "VND-0004",
address: "18, TTC MIDC Industrial Area, Thane Belapur Road, Navi Mumbai 400701",
pincode: "400701",
gstin: "27AAACG1840M1ZL",
isVerified: true,
latitude: 19.0804,
longitude: 73.0112,
contacts: {
create: [
{ name: "Nikhil Mumbaikar", role: "Sales Head", mobile: "7208055636", email: "nikhil.mumbaikar@apar.com", isPrimary: true },
{ name: "Pradeep Shetty", role: "Accounts", mobile: "7208055637", email: "pradeep.shetty@apar.com", isPrimary: false },
],
},
},
});
const v5 = await db.vendor.upsert({
where: { vendorId: "VND-0005" },
update: {},
create: {
name: "Neptune Marine Stores",
vendorId: "VND-0005",
address: "B-204, Jogeshwari Industrial Estate, Mumbai 400060",
pincode: "400060",
gstin: "27AADCN5678B1ZK",
isVerified: true,
latitude: 19.1379,
longitude: 72.8450,
contacts: {
create: [
{ name: "Ravi Sharma", role: "Owner", mobile: "9821234567", email: "ravi@neptunemarine.in", isPrimary: true },
{ name: "Pooja Sharma", role: "Operations", mobile: "9821234568", email: "pooja@neptunemarine.in", isPrimary: false },
],
},
},
});
const v6 = await db.vendor.upsert({
where: { vendorId: "VND-0006" },
update: {},
create: {
name: "Seaview Hydraulics Pvt Ltd",
vendorId: "VND-0006",
address: "Plot 45, Phase II, Ambad MIDC, Nashik 422010",
pincode: "422010",
gstin: "27AABCS9876C1ZM",
isVerified: true,
latitude: 20.0113,
longitude: 73.7902,
contacts: {
create: [
{ name: "Anand Pillai", role: "Managing Director", mobile: "9867543210", email: "anand@seaviewhydraulics.com", isPrimary: true },
],
},
},
});
const v7 = await db.vendor.upsert({
where: { vendorId: "VND-0007" },
update: {},
create: {
name: "Pacific Safety Equipment",
vendorId: "VND-0007",
address: "22, Linking Road, Bandra West, Mumbai 400050",
pincode: "400050",
isVerified: true,
latitude: 19.0543,
longitude: 72.8295,
contacts: {
create: [
{ name: "Priya Nair", role: "Sales Manager", mobile: "9821098765", email: "priya@pacificsafety.com", isPrimary: true },
{ name: "Sunil D'Cruz", role: "Technical Support", mobile: "9821098766", email: "sunil@pacificsafety.com", isPrimary: false },
],
},
},
});
const v8 = await db.vendor.upsert({
where: { vendorId: "VND-0008" },
update: {},
create: {
name: "Mumbai Ship Stores",
vendorId: "VND-0008",
address: "78, Ballard Estate, Fort, Mumbai 400001",
pincode: "400001",
gstin: "27AAACM4567D1ZJ",
isVerified: true,
latitude: 18.9314,
longitude: 72.8353,
contacts: {
create: [
{ name: "Deepak Mehta", role: "Owner", mobile: "9876543210", email: "deepak@mumbaishipstores.com", isPrimary: true },
{ name: "Kavita Mehta", role: "Operations", mobile: "9876543211", email: "kavita@mumbaishipstores.com", isPrimary: false },
],
},
},
});
const v9 = await db.vendor.upsert({
where: { vendorId: "VND-0009" },
update: {},
create: {
name: "Bharat Navigation Systems",
vendorId: "VND-0009",
address: "67, M.G. Road, Pune 411001",
pincode: "411001",
isVerified: false,
latitude: 18.5204,
longitude: 73.8567,
contacts: {
create: [
{ name: "Suresh Kumar", role: "Director", mobile: "9823456780", email: "suresh@bharatnav.in", isPrimary: true },
],
},
},
});
const v10 = await db.vendor.upsert({
where: { vendorId: "VND-0010" },
update: {},
create: {
name: "Coastal Rope & Rigging",
vendorId: "VND-0010",
address: "Sector 12, Kandivali East, Mumbai 400101",
pincode: "400101",
gstin: "27AABCC2345E1ZP",
isVerified: true,
latitude: 19.2081,
longitude: 72.8656,
contacts: {
create: [
{ name: "James D'Souza", role: "Partner", mobile: "9823456789", email: "james@coastalrope.com", isPrimary: true },
],
},
},
});
const v11 = await db.vendor.upsert({
where: { vendorId: "VND-0011" },
update: {},
create: {
name: "Indotech Filters & Fluids",
vendorId: "VND-0011",
address: "E-12, Peenya Industrial Area Phase 1, Bengaluru 560058",
pincode: "560058",
gstin: "29AABCI5678F1ZL",
isVerified: true,
latitude: 13.0315,
longitude: 77.5260,
contacts: {
create: [
{ name: "Meenakshi Srinivasan", role: "VP Sales", mobile: "9987654321", email: "msrinivasan@indotech.co.in", isPrimary: true },
{ name: "Kiran Reddy", role: "Technical", mobile: "9987654322", email: "kiran@indotech.co.in", isPrimary: false },
],
},
},
});
const v12 = await db.vendor.upsert({
where: { vendorId: "VND-0012" },
update: {},
create: {
name: "Eastern Electro Marine",
vendorId: "VND-0012",
address: "12A, Strand Road, Kolkata 700001",
pincode: "700001",
gstin: "19AABCE6789G1ZK",
isVerified: false,
latitude: 22.5726,
longitude: 88.3639,
contacts: {
create: [
{ name: "Rahul Das", role: "Director", mobile: "9432167890", email: "rahul.das@easternelectro.com", isPrimary: true },
{ name: "Moumita Sen", role: "Technical Sales", mobile: "9432167891", email: "moumita.sen@easternelectro.com", isPrimary: false },
],
},
},
});
// ─── Products ─────────────────────────────────────────────────────────────────
const pTurboSeal = await db.product.upsert({
where: { code: "PART-TURBO-SEAL" },
update: {},
create: { code: "PART-TURBO-SEAL", name: "Turbocharger Seal Kit", description: "Replacement seal kit for main engine turbocharger", lastPrice: 1200, lastVendorId: v1.id },
});
const pFpPump = await db.product.upsert({
where: { code: "PART-FP-PUMP" },
update: {},
create: { code: "PART-FP-PUMP", name: "High-Pressure Fuel Pump", description: "Main engine high-pressure fuel pump assembly", lastPrice: 4800, lastVendorId: v1.id },
});
const pLifeJkt = await db.product.upsert({
where: { code: "SAFE-LIFEJKT" },
update: {},
create: { code: "SAFE-LIFEJKT", name: "Life Jacket (SOLAS)", description: "SOLAS-approved adult life jacket", lastPrice: 120, lastVendorId: v7.id },
});
const pFireExt = await db.product.upsert({
where: { code: "SAFE-EXTG-9KG" },
update: {},
create: { code: "SAFE-EXTG-9KG", name: "Fire Extinguisher 9kg", description: "Dry powder fire extinguisher, 9kg", lastPrice: 200, lastVendorId: v7.id },
});
const pGearOil = await db.product.upsert({
where: { code: "LUBE-EP80W90" },
update: {},
create: { code: "LUBE-EP80W90", name: "Gear Oil EP 80W90", description: "Eni EP 80W90 gear oil for marine gearboxes", lastPrice: 182, lastVendorId: v5.id },
});
const pHydOil = await db.product.upsert({
where: { code: "LUBE-HYD46" },
update: {},
create: { code: "LUBE-HYD46", name: "Hydraulic Oil ISO 46", description: "Anti-wear hydraulic oil ISO VG 46 for deck machinery", lastPrice: 155, lastVendorId: v5.id },
});
const pMeOil = await db.product.upsert({
where: { code: "LUBE-MEO30" },
update: {},
create: { code: "LUBE-MEO30", name: "Main Engine Oil SAE 30", description: "Trunk piston engine oil SAE 30 for 4-stroke engines", lastPrice: 210, lastVendorId: v5.id },
});
const pOilFilter = await db.product.upsert({
where: { code: "FILT-OIL-ME" },
update: {},
create: { code: "FILT-OIL-ME", name: "Main Engine Lube Oil Filter", description: "Spin-on lube oil filter for main engine", lastPrice: 850, lastVendorId: v11.id },
});
const pFuelFilter = await db.product.upsert({
where: { code: "FILT-FUEL-ME" },
update: {},
create: { code: "FILT-FUEL-ME", name: "Main Engine Fuel Filter", description: "Duplex fuel oil filter element for main engine", lastPrice: 1100, lastVendorId: v11.id },
});
const pAirFilter = await db.product.upsert({
where: { code: "FILT-AIR-ME" },
update: {},
create: { code: "FILT-AIR-ME", name: "Air Filter Element", description: "Air cleaner filter element for main engine air intake", lastPrice: 650, lastVendorId: v11.id },
});
const pORing = await db.product.upsert({
where: { code: "PART-ORING-ASST" },
update: {},
create: { code: "PART-ORING-ASST", name: "O-Ring Assortment Pack", description: "Mixed O-ring kit with Nitrile and Viton rings, 500 pcs", lastPrice: 250, lastVendorId: v1.id },
});
const pGasket = await db.product.upsert({
where: { code: "PART-GASKET-SET" },
update: {},
create: { code: "PART-GASKET-SET", name: "Exhaust Gasket Set", description: "Full set of exhaust manifold gaskets for main engine", lastPrice: 3200, lastVendorId: v1.id },
});
const pNavLamp = await db.product.upsert({
where: { code: "ELEC-LAMP-LED" },
update: {},
create: { code: "ELEC-LAMP-LED", name: "LED Navigation Lamp", description: "SOLAS-compliant LED navigation light, white masthead", lastPrice: 4500, lastVendorId: v12.id },
});
const pBattery = await db.product.upsert({
where: { code: "ELEC-BATT-12V" },
update: {},
create: { code: "ELEC-BATT-12V", name: "Starting Battery 12V 100Ah", description: "Sealed lead-acid starting battery for emergency equipment", lastPrice: 6500, lastVendorId: v4.id },
});
const pCable = await db.product.upsert({
where: { code: "ELEC-CABLE-3C" },
update: {},
create: { code: "ELEC-CABLE-3C", name: "Marine Cable 3-Core 2.5mm²", description: "Tinned copper 3-core marine electrical cable, per metre", lastPrice: 85, lastVendorId: v4.id },
});
const pMooringRope = await db.product.upsert({
where: { code: "ROPE-MOORING-40" },
update: {},
create: { code: "ROPE-MOORING-40", name: "Mooring Rope 40mm × 200m", description: "3-strand polypropylene mooring rope, 40mm dia, 200m coil", lastPrice: 18500, lastVendorId: v10.id },
});
const pPilotRope = await db.product.upsert({
where: { code: "ROPE-PILOT-18" },
update: {},
create: { code: "ROPE-PILOT-18", name: "Pilot Ladder Rope 18mm", description: "Manilla pilot ladder rope, 18mm, certified per metre", lastPrice: 320, lastVendorId: v10.id },
});
const pImmSuit = await db.product.upsert({
where: { code: "SAFE-IMMSUIT" },
update: {},
create: { code: "SAFE-IMMSUIT", name: "Immersion Suit (SOLAS)", description: "SOLAS-approved adult immersion suit, insulated", lastPrice: 5500, lastVendorId: v7.id },
});
const pEpirb = await db.product.upsert({
where: { code: "SAFE-EPIRB" },
update: {},
create: { code: "SAFE-EPIRB", name: "EPIRB (406 MHz)", description: "Category I float-free EPIRB, 406 MHz COSPAS-SARSAT", lastPrice: 45000, lastVendorId: v7.id },
});
const pFlare = await db.product.upsert({
where: { code: "SAFE-FLARE-HAND" },
update: {},
create: { code: "SAFE-FLARE-HAND", name: "Hand Flare (SOLAS)", description: "Red hand flare for distress signalling, pack of 6", lastPrice: 1800, lastVendorId: v7.id },
});
const pAntifoul = await db.product.upsert({
where: { code: "PAINT-ANTIFOUL" },
update: {},
create: { code: "PAINT-ANTIFOUL", name: "Antifouling Paint 20L", description: "Self-polishing antifouling paint for hull below waterline", lastPrice: 8200, lastVendorId: v3.id },
});
const pPrimer = await db.product.upsert({
where: { code: "PAINT-PRIMER" },
update: {},
create: { code: "PAINT-PRIMER", name: "Epoxy Primer 5L", description: "Two-component epoxy primer for steel and aluminium surfaces", lastPrice: 3400, lastVendorId: v3.id },
});
await db.product.upsert({
where: { code: "TOOL-GRINDER-4" },
update: {},
create: { code: "TOOL-GRINDER-4", name: "Angle Grinder 4-inch", description: "Heavy-duty 4-inch angle grinder, 850W, with guard", lastPrice: 2800, lastVendorId: v8.id },
});
const pChart = await db.product.upsert({
where: { code: "CHART-INT-1" },
update: {},
create: { code: "CHART-INT-1", name: "INT Chart Folio Update", description: "Annual update pack for navigational charts, Indian Ocean folio", lastPrice: 950, lastVendorId: v9.id },
});
const pBoilerChem = await db.product.upsert({
where: { code: "CHEM-BOWTREATER" },
update: {},
create: { code: "CHEM-BOWTREATER", name: "Boiler Water Treatment Chemical 25L", description: "Liquid boiler water treatment and scale inhibitor", lastPrice: 3600, lastVendorId: v5.id },
});
// ─── ProductVendorPrice ───────────────────────────────────────────────────────
const pvp = async (productId: string, vendorId: string, price: number) =>
db.productVendorPrice.upsert({
where: { productId_vendorId: { productId, vendorId } },
update: { price },
create: { productId, vendorId, price },
});
// Turbocharger Seal Kit
await pvp(pTurboSeal.id, v1.id, 1200);
await pvp(pTurboSeal.id, v8.id, 1350);
await pvp(pTurboSeal.id, v5.id, 1180);
// High-Pressure Fuel Pump
await pvp(pFpPump.id, v1.id, 4800);
await pvp(pFpPump.id, v8.id, 5100);
// Life Jacket
await pvp(pLifeJkt.id, v7.id, 120);
await pvp(pLifeJkt.id, v2.id, 115);
await pvp(pLifeJkt.id, v3.id, 130);
// Fire Extinguisher
await pvp(pFireExt.id, v7.id, 200);
await pvp(pFireExt.id, v3.id, 220);
// Gear Oil EP 80W90
await pvp(pGearOil.id, v5.id, 182);
await pvp(pGearOil.id, v6.id, 175);
// Hydraulic Oil ISO 46
await pvp(pHydOil.id, v5.id, 155);
await pvp(pHydOil.id, v6.id, 148);
// Main Engine Oil SAE 30
await pvp(pMeOil.id, v5.id, 210);
await pvp(pMeOil.id, v6.id, 205);
// Lube Oil Filter
await pvp(pOilFilter.id, v1.id, 850);
await pvp(pOilFilter.id, v11.id, 820);
await pvp(pOilFilter.id, v8.id, 890);
// Fuel Filter
await pvp(pFuelFilter.id, v1.id, 1100);
await pvp(pFuelFilter.id, v11.id, 1050);
// Air Filter
await pvp(pAirFilter.id, v11.id, 650);
await pvp(pAirFilter.id, v1.id, 680);
// O-Ring Assortment
await pvp(pORing.id, v1.id, 250);
await pvp(pORing.id, v8.id, 270);
await pvp(pORing.id, v11.id, 240);
// Exhaust Gasket Set
await pvp(pGasket.id, v1.id, 3200);
await pvp(pGasket.id, v8.id, 3500);
// LED Navigation Lamp
await pvp(pNavLamp.id, v12.id, 4500);
await pvp(pNavLamp.id, v8.id, 4800);
// Starting Battery
await pvp(pBattery.id, v4.id, 6500);
await pvp(pBattery.id, v12.id, 6800);
// Marine Cable
await pvp(pCable.id, v4.id, 85);
await pvp(pCable.id, v12.id, 80);
// Mooring Rope
await pvp(pMooringRope.id, v10.id, 18500);
await pvp(pMooringRope.id, v3.id, 19000);
// Pilot Ladder Rope
await pvp(pPilotRope.id, v10.id, 320);
await pvp(pPilotRope.id, v3.id, 340);
// Immersion Suit
await pvp(pImmSuit.id, v7.id, 5500);
await pvp(pImmSuit.id, v2.id, 5200);
// EPIRB
await pvp(pEpirb.id, v7.id, 45000);
await pvp(pEpirb.id, v9.id, 47000);
// Hand Flare
await pvp(pFlare.id, v7.id, 1800);
await pvp(pFlare.id, v2.id, 1750);
// Antifouling Paint
await pvp(pAntifoul.id, v3.id, 8200);
await pvp(pAntifoul.id, v8.id, 8500);
// Epoxy Primer
await pvp(pPrimer.id, v3.id, 3400);
await pvp(pPrimer.id, v8.id, 3600);
// Chart Folio
await pvp(pChart.id, v9.id, 950);
// Boiler Water Treatment
await pvp(pBoilerChem.id, v5.id, 3600);
await pvp(pBoilerChem.id, v11.id, 3450);
// ─── ItemInventory ────────────────────────────────────────────────────────────
const inv = async (productId: string, siteId: string, quantity: number) =>
db.itemInventory.upsert({
where: { productId_siteId: { productId, siteId } },
update: { quantity },
create: { productId, siteId, quantity },
});
await inv(pTurboSeal.id, siteBOM.id, 3);
await inv(pLifeJkt.id, siteBOM.id, 25);
await inv(pHydOil.id, siteBOM.id, 200);
await inv(pMeOil.id, siteBOM.id, 150);
await inv(pOilFilter.id, siteJNP.id, 8);
await inv(pFuelFilter.id,siteJNP.id, 4);
await inv(pCable.id, siteCHE.id, 500);
await inv(pMooringRope.id,siteKOC.id,2);
await inv(pMeOil.id, siteVIZ.id, 100);
await inv(pImmSuit.id, siteKDL.id, 15);
await inv(pAntifoul.id, siteHAL.id, 4);
await inv(pBoilerChem.id,sitePAR.id, 3);
await inv(pFuelFilter.id,siteMNG.id, 4);
await inv(pBattery.id, siteGOA.id, 2);
await inv(pORing.id, siteBOM.id, 10);
// ─── ItemConsumption ─────────────────────────────────────────────────────────
const con = async (productId: string, siteId: string, date: Date, quantity: number, note?: string) =>
db.itemConsumption.upsert({
where: { productId_siteId_date: { productId, siteId, date } },
update: { quantity, note },
create: { productId, siteId, date, quantity, note, recordedById: technical.id },
});
await con(pHydOil.id, siteBOM.id, new Date("2026-01-10"), 20, "Regular deck machinery top-up");
await con(pMeOil.id, siteBOM.id, new Date("2026-01-15"), 15, "ME running hours service");
await con(pOilFilter.id, siteBOM.id, new Date("2026-02-01"), 2, "500-hr ME service");
await con(pHydOil.id, siteVIZ.id, new Date("2026-01-20"), 30, "Crane hydraulic system fill");
await con(pMeOil.id, siteVIZ.id, new Date("2026-01-25"), 25, "ME periodic change");
await con(pLifeJkt.id, siteJNP.id, new Date("2026-02-10"), 5, "Expired — disposed per drill");
await con(pPilotRope.id, siteKOC.id, new Date("2026-02-15"), 50, "Pilot ladder recertification cut");
await con(pCable.id, siteCHE.id, new Date("2026-01-30"), 100, "Accommodation wiring repair");
await con(pBoilerChem.id,siteBOM.id, new Date("2026-02-20"), 1, "Monthly boiler dosing");
await con(pAntifoul.id, siteHAL.id, new Date("2026-03-01"), 1, "Drydock touch-up");
await con(pFuelFilter.id,siteJNP.id, new Date("2026-02-25"), 2, "Duplex filter element change");
await con(pORing.id, siteBOM.id, new Date("2026-03-05"), 1, "Pump overhaul seals");
await con(pGearOil.id, siteCHE.id, new Date("2026-03-10"), 40, "Shaft gearbox oil change");
await con(pAirFilter.id, siteBOM.id, new Date("2026-03-15"), 1, "3000-hr air cleaner service");
// ─── Purchase Orders ──────────────────────────────────────────────────────────
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00001" },
update: {},
create: {
poNumber: "PO-2026-00001",
title: "Engine Room Spare Parts — MV Pelagia Star",
status: "MGR_REVIEW",
totalAmount: 9971.0, // (2×1200 + 1×4800 + 5×250) × 1.18
currency: "INR",
submittedAt: d(1),
submitterId: technical.id,
vesselId: mvStar.id,
accountId: accTechOps.id,
vendorId: v1.id,
siteId: siteBOM.id,
lineItems: {
create: [
{ name: "Turbocharger seal kit", quantity: 2, unit: "set", unitPrice: 1200, totalPrice: 2400, sortOrder: 0, productId: pTurboSeal.id },
{ name: "High-pressure fuel pump", quantity: 1, unit: "pc", unitPrice: 4800, totalPrice: 4800, sortOrder: 1, productId: pFpPump.id },
{ name: "O-ring assortment pack", quantity: 5, unit: "pk", unitPrice: 250, totalPrice: 1250, sortOrder: 2, productId: pORing.id },
],
},
actions: {
create: [
{ actionType: "CREATED", actorId: technical.id, createdAt: d(3) },
{ actionType: "SUBMITTED", actorId: technical.id, createdAt: d(1) },
],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00002" },
update: {},
create: {
poNumber: "PO-2026-00002",
title: "Crew Safety Equipment — MV Aegean Wind",
status: "DRAFT",
totalAmount: 3776.0, // (20×120 + 4×200) × 1.18
currency: "INR",
submitterId: technical.id,
vesselId: mvWind.id,
accountId: accSafety.id,
siteId: siteJNP.id,
lineItems: {
create: [
{ name: "Life jackets (SOLAS)", quantity: 20, unit: "pc", unitPrice: 120, totalPrice: 2400, sortOrder: 0, productId: pLifeJkt.id },
{ name: "Fire extinguisher — 9kg", quantity: 4, unit: "pc", unitPrice: 200, totalPrice: 800, sortOrder: 1, productId: pFireExt.id },
],
},
actions: {
create: [{ actionType: "CREATED", actorId: technical.id, createdAt: d(2) }],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00003" },
update: {},
create: {
poNumber: "PO-2026-00003",
title: "Navigation Charts Update — Fleet",
status: "MGR_APPROVED",
totalAmount: 1121.0, // 950 × 1.18
currency: "INR",
submittedAt: d(5),
approvedAt: d(2),
submitterId: technical.id,
vesselId: mvStar.id,
accountId: accNavig.id,
siteId: siteBOM.id,
lineItems: {
create: [
{ name: "INT chart folio update", quantity: 1, unit: "set", unitPrice: 950, totalPrice: 950, sortOrder: 0, productId: pChart.id },
],
},
actions: {
create: [
{ actionType: "CREATED", actorId: technical.id, createdAt: d(7) },
{ actionType: "SUBMITTED", actorId: technical.id, createdAt: d(5) },
{ actionType: "APPROVED", actorId: manager.id, createdAt: d(2), note: "Approved — update due before next voyage." },
],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00004" },
update: {},
create: {
poNumber: "PO-2026-00004",
title: "Lubricants & Oils Restock — MV Thetis",
status: "SUBMITTED",
totalAmount: 13440.2, // (50×155 + 20×182) × 1.18 = 11390 × 1.18
currency: "INR",
submittedAt: d(1),
submitterId: tech2.id,
vesselId: mvThetis.id,
accountId: accTechOps.id,
vendorId: v5.id,
siteId: siteKOC.id,
lineItems: {
create: [
{ name: "Hydraulic Oil ISO 46", quantity: 50, unit: "L", unitPrice: 155, totalPrice: 7750, sortOrder: 0, productId: pHydOil.id },
{ name: "Gear Oil EP 80W90", quantity: 20, unit: "L", unitPrice: 182, totalPrice: 3640, sortOrder: 1, productId: pGearOil.id },
],
},
actions: {
create: [
{ actionType: "CREATED", actorId: tech2.id, createdAt: d(2) },
{ actionType: "SUBMITTED", actorId: tech2.id, createdAt: d(1) },
],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00005" },
update: {},
create: {
poNumber: "PO-2026-00005",
title: "Life Saving Appliances — MV Triton",
status: "SENT_FOR_PAYMENT",
totalAmount: 181720.0, // (10×5500 + 2×45000 + 5×1800) × 1.18 = 154000 × 1.18
currency: "INR",
submittedAt: d(10),
approvedAt: d(7),
submitterId: technical.id,
vesselId: mvTriton.id,
accountId: accSafety.id,
vendorId: v7.id,
siteId: siteVIZ.id,
lineItems: {
create: [
{ name: "Immersion suits (SOLAS)", quantity: 10, unit: "pc", unitPrice: 5500, totalPrice: 55000, sortOrder: 0, productId: pImmSuit.id },
{ name: "EPIRB 406 MHz", quantity: 2, unit: "pc", unitPrice: 45000, totalPrice: 90000, sortOrder: 1, productId: pEpirb.id },
{ name: "Hand flares (SOLAS) — pack of 6", quantity: 5, unit: "pk", unitPrice: 1800, totalPrice: 9000, sortOrder: 2, productId: pFlare.id },
],
},
actions: {
create: [
{ actionType: "CREATED", actorId: technical.id, createdAt: d(12) },
{ actionType: "SUBMITTED", actorId: technical.id, createdAt: d(10) },
{ actionType: "APPROVED", actorId: manager.id, createdAt: d(7), note: "Annual LSA renewal — proceed." },
{ actionType: "PAYMENT_SENT", actorId: accounts.id, createdAt: d(3) },
],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00006" },
update: {},
create: {
poNumber: "PO-2026-00006",
title: "Deck Mooring Equipment — MV Poseidon",
status: "PAID_DELIVERED",
totalAmount: 81420.0, // (2×18500 + 100×320) × 1.18 = 69000 × 1.18
currency: "INR",
submittedAt: d(20),
approvedAt: d(15),
paidAt: d(8),
submitterId: tech3.id,
vesselId: mvPoseidon.id,
accountId: accDeck.id,
vendorId: v10.id,
siteId: siteKDL.id,
lineItems: {
create: [
{ name: "Mooring rope 40mm × 200m", quantity: 2, unit: "coil", unitPrice: 18500, totalPrice: 37000, sortOrder: 0, productId: pMooringRope.id },
{ name: "Pilot ladder rope 18mm", quantity: 100, unit: "m", unitPrice: 320, totalPrice: 32000, sortOrder: 1, productId: pPilotRope.id },
],
},
actions: {
create: [
{ actionType: "CREATED", actorId: tech3.id, createdAt: d(22) },
{ actionType: "SUBMITTED", actorId: tech3.id, createdAt: d(20) },
{ actionType: "APPROVED", actorId: manager2.id, createdAt: d(15) },
{ actionType: "PAYMENT_SENT", actorId: accounts.id, createdAt: d(10) },
{ actionType: "RECEIPT_CONFIRMED", actorId: tech3.id, createdAt: d(8) },
],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00007" },
update: {},
create: {
poNumber: "PO-2026-00007",
title: "Electrical Spares — MV Nereid",
status: "MGR_REVIEW",
totalAmount: 51920.0, // (5×4500 + 2×6500 + 100×85) × 1.18 = 44000 × 1.18
currency: "INR",
submittedAt: d(2),
submitterId: tech2.id,
vesselId: mvNereid.id,
accountId: accElect.id,
vendorId: v12.id,
siteId: siteCHE.id,
lineItems: {
create: [
{ name: "LED navigation lamp (masthead)", quantity: 5, unit: "pc", unitPrice: 4500, totalPrice: 22500, sortOrder: 0, productId: pNavLamp.id },
{ name: "Starting battery 12V 100Ah", quantity: 2, unit: "pc", unitPrice: 6500, totalPrice: 13000, sortOrder: 1, productId: pBattery.id },
{ name: "Marine cable 3-core 2.5mm²", quantity: 100, unit: "m", unitPrice: 85, totalPrice: 8500, sortOrder: 2, productId: pCable.id },
],
},
actions: {
create: [
{ actionType: "CREATED", actorId: tech2.id, createdAt: d(4) },
{ actionType: "SUBMITTED", actorId: tech2.id, createdAt: d(2) },
],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00008" },
update: {},
create: {
poNumber: "PO-2026-00008",
title: "Rope & Rigging Restock — MV Callisto",
status: "DRAFT",
totalAmount: 84370.0, // (3×18500 + 50×320) × 1.18 = 71500 × 1.18
currency: "INR",
submitterId: manning.id,
vesselId: mvCallisto.id,
accountId: accStores.id,
siteId: siteGOA.id,
lineItems: {
create: [
{ name: "Mooring rope 40mm × 200m", quantity: 3, unit: "coil", unitPrice: 18500, totalPrice: 55500, sortOrder: 0, productId: pMooringRope.id },
{ name: "Pilot ladder rope 18mm", quantity: 50, unit: "m", unitPrice: 320, totalPrice: 16000, sortOrder: 1, productId: pPilotRope.id },
],
},
actions: {
create: [{ actionType: "CREATED", actorId: manning.id, createdAt: d(1) }],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00009" },
update: {},
create: {
poNumber: "PO-2026-00009",
title: "Engine Filter Kit — MV Proteus",
status: "SUBMITTED",
totalAmount: 10443.0, // (5×850 + 3×1100 + 2×650) × 1.18 = 8850 × 1.18
currency: "INR",
submittedAt: d(3),
submitterId: technical.id,
vesselId: mvProteus.id,
accountId: accTechOps.id,
vendorId: v11.id,
siteId: sitePAR.id,
lineItems: {
create: [
{ name: "Main engine lube oil filter", quantity: 5, unit: "pc", unitPrice: 820, totalPrice: 4100, sortOrder: 0, productId: pOilFilter.id },
{ name: "Main engine fuel filter", quantity: 3, unit: "pc", unitPrice: 1050, totalPrice: 3150, sortOrder: 1, productId: pFuelFilter.id },
{ name: "Air filter element", quantity: 2, unit: "pc", unitPrice: 650, totalPrice: 1300, sortOrder: 2, productId: pAirFilter.id },
],
},
actions: {
create: [
{ actionType: "CREATED", actorId: technical.id, createdAt: d(5) },
{ actionType: "SUBMITTED", actorId: technical.id, createdAt: d(3) },
],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00010" },
update: {},
create: {
poNumber: "PO-2026-00010",
title: "Hull Maintenance — MV Amphitrite Drydock",
status: "CLOSED",
totalAmount: 116820.0, // (10×8200 + 5×3400) × 1.18 = 99000 × 1.18
currency: "INR",
submittedAt: d(30),
approvedAt: d(25),
paidAt: d(18),
closedAt: d(10),
submitterId: tech3.id,
vesselId: mvAmphitrite.id,
accountId: accPaint.id,
vendorId: v3.id,
siteId: siteHAL.id,
lineItems: {
create: [
{ name: "Antifouling paint 20L", quantity: 10, unit: "drum", unitPrice: 8200, totalPrice: 82000, sortOrder: 0, productId: pAntifoul.id },
{ name: "Epoxy primer 5L", quantity: 5, unit: "can", unitPrice: 3400, totalPrice: 17000, sortOrder: 1, productId: pPrimer.id },
],
},
actions: {
create: [
{ actionType: "CREATED", actorId: tech3.id, createdAt: d(32) },
{ actionType: "SUBMITTED", actorId: tech3.id, createdAt: d(30) },
{ actionType: "APPROVED", actorId: manager.id, createdAt: d(25), note: "Drydock schedule confirmed." },
{ actionType: "PAYMENT_SENT", actorId: accounts2.id, createdAt: d(20) },
{ actionType: "RECEIPT_CONFIRMED", actorId: tech3.id, createdAt: d(18) },
{ actionType: "CLOSED", actorId: manager.id, createdAt: d(10), note: "All items delivered and consumed." },
],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00011" },
update: {},
create: {
poNumber: "PO-2026-00011",
title: "Gasket & Seal Kit — MV Galatea",
status: "DRAFT",
totalAmount: 7789.6, // (2×3200 + 3×250) × 1.18 = 6750 × 1.18 ≈ 7965 — recalc: (2×3200+3×250)=7150 × 1.18 = 8437
currency: "INR",
submitterId: manning2.id,
vesselId: mvGalatea.id,
accountId: accTechOps.id,
siteId: siteMNG.id,
lineItems: {
create: [
{ name: "Exhaust gasket set", quantity: 2, unit: "set", unitPrice: 3200, totalPrice: 6400, sortOrder: 0, productId: pGasket.id },
{ name: "O-ring assortment pack", quantity: 3, unit: "pk", unitPrice: 250, totalPrice: 750, sortOrder: 1, productId: pORing.id },
],
},
actions: {
create: [{ actionType: "CREATED", actorId: manning2.id, createdAt: d(1) }],
},
},
});
await db.purchaseOrder.upsert({
where: { poNumber: "PO-2026-00012" },
update: {},
create: {
poNumber: "PO-2026-00012",
title: "Boiler Water Treatment — MV Proteus & Galatea",
status: "MGR_APPROVED",
totalAmount: 12744.0, // (3×3600 + 1×3450) × 1.18 = 14250 × 1.18 — recalc: 3×3600=10800 + 1×3450=3450 = 14250 × 1.18 = 16815
currency: "INR",
submittedAt: d(8),
approvedAt: d(3),
submitterId: superuser.id,
vesselId: mvProteus.id,
accountId: accTechOps.id,
vendorId: v11.id,
siteId: sitePAR.id,
lineItems: {
create: [
{ name: "Boiler water treatment chemical 25L", quantity: 3, unit: "can", unitPrice: 3450, totalPrice: 10350, sortOrder: 0, productId: pBoilerChem.id },
{ name: "Main engine lube oil filter", quantity: 4, unit: "pc", unitPrice: 820, totalPrice: 3280, sortOrder: 1, productId: pOilFilter.id },
],
},
actions: {
create: [
{ actionType: "CREATED", actorId: superuser.id, createdAt: d(10) },
{ actionType: "SUBMITTED", actorId: superuser.id, createdAt: d(8) },
{ actionType: "APPROVED", actorId: manager2.id, createdAt: d(3), note: "Monthly consumables — approve." },
],
},
},
});
console.log("Seed complete.\n");
console.log("Dev login credentials:");
console.log(" Admin: admin@pelagia.local / admin1234");
console.log(" Manager: manager@pelagia.local / manager1234");
console.log(" Manager 2: manager2@pelagia.local / manager1234");
console.log(" Tech: tech@pelagia.local / tech1234");
console.log(" Tech 2: tech2@pelagia.local / tech1234");
console.log(" Tech 3: tech3@pelagia.local / tech1234");
console.log(" Accounts: accounts@pelagia.local / accounts1234");
console.log(" Manning: manning@pelagia.local / manning1234");
console.log(" Manning 2: manning2@pelagia.local / manning1234");
console.log(" Superuser: superuser@pelagia.local / super1234");
console.log(" Auditor: auditor@pelagia.local / audit1234");
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(() => db.$disconnect());