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>
1153 lines
45 KiB
TypeScript
1153 lines
45 KiB
TypeScript
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());
|