From 280966a3699a6372a5ba45e04fea88c09981265d Mon Sep 17 00:00:00 2001 From: Hardik Date: Sat, 30 May 2026 18:14:24 +0530 Subject: [PATCH] refactor: revert cost centre to vessels only, remove vessel-site link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Vessel Name * - {sites.length > 0 && ( -
- - -
- )} ); } -export function AddVesselButton({ sites, suggestedCode }: { sites: SiteOption[]; suggestedCode?: string }) { +export function AddVesselButton({ suggestedCode }: { suggestedCode?: string }) { const router = useRouter(); const [open, setOpen] = useState(false); const [pending, setPending] = useState(false); @@ -88,7 +64,7 @@ export function AddVesselButton({ sites, suggestedCode }: { sites: SiteOption[]; setOpen(false)}>
- + {error &&

{error}

}
diff --git a/App/app/(portal)/approvals/approvals-search.tsx b/App/app/(portal)/approvals/approvals-search.tsx index 857287a..95932ef 100644 --- a/App/app/(portal)/approvals/approvals-search.tsx +++ b/App/app/(portal)/approvals/approvals-search.tsx @@ -4,33 +4,31 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useState } from "react"; interface Props { - costCentres: { ref: string; name: string }[]; + vessels: { id: string; name: string }[]; } -export function ApprovalsSearch({ costCentres }: Props) { +export function ApprovalsSearch({ vessels }: Props) { const router = useRouter(); const sp = useSearchParams(); const [q, setQ] = useState(sp.get("q") ?? ""); - const [costCentreRef, setCostCentreRef] = useState(sp.get("costCentreRef") ?? ""); + const [vesselId, setVesselId] = useState(sp.get("vesselId") ?? ""); const [dateFrom, setDateFrom] = useState(sp.get("dateFrom") ?? ""); - const [dateTo, setDateTo] = useState(sp.get("dateTo") ?? ""); function apply() { const params = new URLSearchParams(); if (q.trim()) params.set("q", q.trim()); - if (costCentreRef) params.set("costCentreRef", costCentreRef); + if (vesselId) params.set("vesselId", vesselId); if (dateFrom) params.set("dateFrom", dateFrom); - if (dateTo) params.set("dateTo", dateTo); router.push(`/approvals?${params.toString()}`); } function clear() { - setQ(""); setCostCentreRef(""); setDateFrom(""); setDateTo(""); + setQ(""); setVesselId(""); setDateFrom(""); router.push("/approvals"); } - const hasFilters = q || costCentreRef || dateFrom || dateTo; + const hasFilters = q || vesselId || dateFrom; return (
@@ -44,12 +42,10 @@ export function ApprovalsSearch({ costCentres }: Props) {
- setVesselId(e.target.value)} className="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20"> - {costCentres.map((c) => ( - - ))} + {vessels.map((v) => )}
diff --git a/App/app/(portal)/approvals/page.tsx b/App/app/(portal)/approvals/page.tsx index 362e11c..067d250 100644 --- a/App/app/(portal)/approvals/page.tsx +++ b/App/app/(portal)/approvals/page.tsx @@ -13,9 +13,8 @@ export const metadata: Metadata = { title: "Approvals" }; interface Props { searchParams: Promise<{ q?: string; - costCentreRef?: string; + vesselId?: string; dateFrom?: string; - dateTo?: string; }>; } @@ -25,7 +24,7 @@ export default async function ApprovalsPage({ searchParams }: Props) { if (!hasPermission(session.user.role, "approve_po")) redirect("/dashboard"); - const { q, costCentreRef, dateFrom } = await searchParams; + const { q, vesselId, dateFrom } = await searchParams; const where: NonNullable[0]>["where"] = { status: "MGR_REVIEW", @@ -38,27 +37,18 @@ export default async function ApprovalsPage({ searchParams }: Props) { { title: { contains: q.trim(), mode: "insensitive" } }, ]; } - if (costCentreRef) { - if (costCentreRef.startsWith("v:")) where.vesselId = costCentreRef.slice(2); - else if (costCentreRef.startsWith("s:")) where.siteId = costCentreRef.slice(2); - } + if (vesselId) where.vesselId = vesselId; if (dateFrom) where.submittedAt = { gte: new Date(dateFrom) }; - const [pending, vessels, sites] = await Promise.all([ + const [pending, vessels] = await Promise.all([ db.purchaseOrder.findMany({ where, - include: { submitter: true, vessel: true, site: { select: { name: true } }, account: true }, + include: { submitter: true, vessel: true, account: true }, orderBy: { submittedAt: "asc" }, }), db.vessel.findMany({ orderBy: { name: "asc" }, select: { id: true, name: true } }), - db.site.findMany({ orderBy: { name: "asc" }, select: { id: true, name: true } }), ]); - const costCentres = [ - ...vessels.map((v) => ({ ref: `v:${v.id}`, name: v.name })), - ...sites.map((s) => ({ ref: `s:${s.id}`, name: s.name })), - ]; - return (
@@ -69,7 +59,7 @@ export default async function ApprovalsPage({ searchParams }: Props) {
- + {pending.length === 0 ? ( @@ -98,7 +88,7 @@ export default async function ApprovalsPage({ searchParams }: Props) { {po.poNumber} {po.title} {po.submitter.name} - {po.vessel?.name ?? po.site?.name ?? "—"} + {po.vessel.name} {formatCurrency(Number(po.totalAmount), po.currency)} @@ -130,7 +120,7 @@ export default async function ApprovalsPage({ searchParams }: Props) {

{po.title}

- {po.submitter.name} · {po.vessel?.name ?? po.site?.name ?? "—"} + {po.submitter.name} · {po.vessel.name} {formatCurrency(Number(po.totalAmount), po.currency)} diff --git a/App/app/(portal)/dashboard/page.tsx b/App/app/(portal)/dashboard/page.tsx index c368c2b..3da8778 100644 --- a/App/app/(portal)/dashboard/page.tsx +++ b/App/app/(portal)/dashboard/page.tsx @@ -206,7 +206,7 @@ async function ManagerDashboard() { {po.title} - {po.vessel?.name ?? po.site?.name ?? "—"} + {po.vessel.name} diff --git a/App/app/(portal)/history/history-filters.tsx b/App/app/(portal)/history/history-filters.tsx index da6fe74..16b4132 100644 --- a/App/app/(portal)/history/history-filters.tsx +++ b/App/app/(portal)/history/history-filters.tsx @@ -18,33 +18,33 @@ const STATUSES = [ ]; interface Props { - costCentres: { ref: string; name: string }[]; + vessels: { id: string; name: string }[]; } -export function HistoryFilters({ costCentres }: Props) { +export function HistoryFilters({ vessels }: Props) { const router = useRouter(); const sp = useSearchParams(); const [dateFrom, setDateFrom] = useState(sp.get("dateFrom") ?? ""); const [dateTo, setDateTo] = useState(sp.get("dateTo") ?? ""); - const [costCentreRef, setCostCentreRef] = useState(sp.get("costCentreRef") ?? ""); + const [vesselId, setVesselId] = useState(sp.get("vesselId") ?? ""); const [status, setStatus] = useState(sp.get("status") ?? ""); function apply() { const params = new URLSearchParams(); if (dateFrom) params.set("dateFrom", dateFrom); if (dateTo) params.set("dateTo", dateTo); - if (costCentreRef) params.set("costCentreRef", costCentreRef); + if (vesselId) params.set("vesselId", vesselId); if (status) params.set("status", status); router.push(`/history?${params.toString()}`); } function clear() { - setDateFrom(""); setDateTo(""); setCostCentreRef(""); setStatus(""); + setDateFrom(""); setDateTo(""); setVesselId(""); setStatus(""); router.push("/history"); } - const hasFilters = dateFrom || dateTo || costCentreRef || status; + const hasFilters = dateFrom || dateTo || vesselId || status; return (
@@ -61,12 +61,10 @@ export function HistoryFilters({ costCentres }: Props) {
- setVesselId(e.target.value)} className="w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20"> - {costCentres.map((c) => ( - - ))} + {vessels.map((v) => )}
diff --git a/App/app/(portal)/history/page.tsx b/App/app/(portal)/history/page.tsx index f507b27..3f47f90 100644 --- a/App/app/(portal)/history/page.tsx +++ b/App/app/(portal)/history/page.tsx @@ -3,7 +3,7 @@ import { db } from "@/lib/db"; import { hasPermission } from "@/lib/permissions"; import { redirect } from "next/navigation"; import Link from "next/link"; -import { formatCurrency, formatDate, PO_STATUS_LABELS } from "@/lib/utils"; +import { formatCurrency, formatDate } from "@/lib/utils"; import { PoStatusBadge } from "@/components/po/po-status-badge"; import { HistoryFilters } from "./history-filters"; import { Suspense } from "react"; @@ -16,7 +16,7 @@ interface Props { searchParams: Promise<{ dateFrom?: string; dateTo?: string; - costCentreRef?: string; + vesselId?: string; status?: string; }>; } @@ -27,7 +27,7 @@ export default async function HistoryPage({ searchParams }: Props) { if (!hasPermission(session.user.role, "export_reports")) redirect("/dashboard"); - const { dateFrom, dateTo, costCentreRef, status } = await searchParams; + const { dateFrom, dateTo, vesselId, status } = await searchParams; const where: NonNullable[0]>["where"] = {}; if (dateFrom || dateTo) { @@ -40,32 +40,23 @@ export default async function HistoryPage({ searchParams }: Props) { } where.createdAt = createdAt; } - if (costCentreRef) { - if (costCentreRef.startsWith("v:")) where.vesselId = costCentreRef.slice(2); - else if (costCentreRef.startsWith("s:")) where.siteId = costCentreRef.slice(2); - } + if (vesselId) where.vesselId = vesselId; if (status) where.status = status as POStatus; - const [orders, vessels, sites] = await Promise.all([ + const [orders, vessels] = await Promise.all([ db.purchaseOrder.findMany({ where, - include: { submitter: true, vessel: true, site: { select: { name: true } }, account: true }, + include: { submitter: true, vessel: true, account: true }, orderBy: { createdAt: "desc" }, take: 200, }), db.vessel.findMany({ orderBy: { name: "asc" }, select: { id: true, name: true } }), - db.site.findMany({ orderBy: { name: "asc" }, select: { id: true, name: true } }), ]); - const costCentres = [ - ...vessels.map((v) => ({ ref: `v:${v.id}`, name: v.name })), - ...sites.map((s) => ({ ref: `s:${s.id}`, name: s.name })), - ]; - const exportParams = new URLSearchParams({ format: "csv" }); if (dateFrom) exportParams.set("dateFrom", dateFrom); if (dateTo) exportParams.set("dateTo", dateTo); - if (costCentreRef) exportParams.set("costCentreRef", costCentreRef); + if (vesselId) exportParams.set("vesselId", vesselId); if (status) exportParams.set("status", status); return ( @@ -91,7 +82,7 @@ export default async function HistoryPage({ searchParams }: Props) {
- +
@@ -116,7 +107,7 @@ export default async function HistoryPage({ searchParams }: Props) { {po.title} - {po.vessel?.name ?? po.site?.name ?? "—"} + {po.vessel.name} {po.submitter.name} diff --git a/App/app/(portal)/my-orders/page.tsx b/App/app/(portal)/my-orders/page.tsx index 4cf5aa2..2bd44a8 100644 --- a/App/app/(portal)/my-orders/page.tsx +++ b/App/app/(portal)/my-orders/page.tsx @@ -23,7 +23,6 @@ export default async function MyOrdersPage() { orderBy: { updatedAt: "desc" }, include: { vessel: { select: { name: true } }, - site: { select: { name: true } }, account: { select: { name: true, code: true } }, actions: { where: { @@ -68,8 +67,7 @@ type PoRow = { title: string; status: import("@prisma/client").POStatus; totalAmount: import("@prisma/client").Prisma.Decimal; - vessel: { name: string } | null; - site: { name: string } | null; + vessel: { name: string }; account: { name: string; code: string }; updatedAt: Date; managerNote: string | null; @@ -111,7 +109,7 @@ function PoTable({ title, rows, className = "" }: { title: string; rows: PoRow[]

)} - {po.vessel?.name ?? po.site?.name ?? "—"} + {po.vessel.name} {formatCurrency(Number(po.totalAmount))} {formatDate(po.updatedAt)} diff --git a/App/app/(portal)/payments/history/page.tsx b/App/app/(portal)/payments/history/page.tsx index ad0e179..28e564b 100644 --- a/App/app/(portal)/payments/history/page.tsx +++ b/App/app/(portal)/payments/history/page.tsx @@ -117,7 +117,7 @@ export default async function PaymentHistoryPage({ searchParams }: Props) { {po.title} - {po.vessel?.name ?? po.site?.name ?? "—"} + {po.vessel.name} {po.vendor?.name ?? "—"} {po.submitter.name} diff --git a/App/app/(portal)/payments/page.tsx b/App/app/(portal)/payments/page.tsx index 55d9dc9..92230e0 100644 --- a/App/app/(portal)/payments/page.tsx +++ b/App/app/(portal)/payments/page.tsx @@ -45,7 +45,7 @@ export default async function PaymentsPage() {

{po.title}

- {po.vessel?.name ?? po.site?.name ?? "—"} + {po.vessel.name} · {po.submitter.name} {po.vendor && ( diff --git a/App/app/(portal)/po/[id]/edit/actions.ts b/App/app/(portal)/po/[id]/edit/actions.ts index 7b2821b..5c2198a 100644 --- a/App/app/(portal)/po/[id]/edit/actions.ts +++ b/App/app/(portal)/po/[id]/edit/actions.ts @@ -45,7 +45,7 @@ export async function updatePo( const parsed = createPoSchema.safeParse({ title: formData.get("title"), - costCentreRef: formData.get("costCentreRef"), + vesselId: formData.get("vesselId"), accountId: formData.get("accountId"), projectCode: formData.get("projectCode") || undefined, dateRequired: formData.get("dateRequired") || undefined, @@ -69,8 +69,6 @@ export async function updatePo( } const data = parsed.data; - const newVesselId = data.costCentreRef.startsWith("v:") ? data.costCentreRef.slice(2) : null; - const newCostCentreSiteId = data.costCentreRef.startsWith("s:") ? data.costCentreRef.slice(2) : null; const total = data.lineItems.reduce( (sum, item) => sum + item.quantity * item.unitPrice * (1 + item.gstRate), 0 @@ -102,7 +100,6 @@ export async function updatePo( include: { lineItems: { orderBy: { sortOrder: "asc" } }, vessel: true, - site: { select: { name: true } }, account: true, vendor: true, }, @@ -120,8 +117,8 @@ export async function updatePo( })), fields: { title: currentPo.title, - vessel: currentPo.vessel?.name ?? currentPo.site?.name ?? null, - vesselId: currentPo.vesselId ?? currentPo.siteId ?? "", + vessel: currentPo.vessel?.name ?? null, + vesselId: currentPo.vesselId, account: `${currentPo.account.name} (${currentPo.account.code})`, accountId: currentPo.accountId, vendor: currentPo.vendor?.name ?? null, @@ -138,8 +135,7 @@ export async function updatePo( where: { id: poId }, data: { title: data.title, - vesselId: newVesselId, - siteId: newCostCentreSiteId, + vesselId: data.vesselId, accountId: data.accountId, vendorId: data.vendorId ?? null, projectCode: data.projectCode ?? null, diff --git a/App/app/(portal)/po/[id]/edit/edit-po-form.tsx b/App/app/(portal)/po/[id]/edit/edit-po-form.tsx index e4ea9ac..580ec5c 100644 --- a/App/app/(portal)/po/[id]/edit/edit-po-form.tsx +++ b/App/app/(portal)/po/[id]/edit/edit-po-form.tsx @@ -4,7 +4,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; import { updatePo } from "./actions"; import type { Vendor, PurchaseOrder } from "@prisma/client"; -import type { CostCentreGroup, AccountGroup } from "@/app/(portal)/po/new/new-po-form"; +import type { VesselOption, AccountGroup } from "@/app/(portal)/po/new/new-po-form"; import { LineItemsEditor } from "@/components/po/po-line-items-editor"; import { SearchableSelect } from "@/components/ui/searchable-select"; import type { LineItemInput } from "@/lib/validations/po"; @@ -36,14 +36,13 @@ type PoWithItems = Omit & { interface Props { po: PoWithItems; - costCentres: CostCentreGroup[]; - initialCostCentreRef: string; + vessels: VesselOption[]; accounts: AccountGroup[]; vendors: Vendor[]; managerNoteAuthor?: string | null; } -export function EditPoForm({ po, costCentres, initialCostCentreRef, accounts, vendors, managerNoteAuthor }: Props) { +export function EditPoForm({ po, vessels, accounts, vendors, managerNoteAuthor }: Props) { const router = useRouter(); const [lineItems, setLineItems] = useState( po.lineItems.map((li) => ({ @@ -131,17 +130,10 @@ export function EditPoForm({ po, costCentres, initialCostCentreRef, accounts, ve - - {costCentres.map((group) => ( - - {group.siteRef && ( - - )} - {group.vessels.map((v) => ( - - ))} - + {vessels.map((v) => ( + ))}
diff --git a/App/app/(portal)/po/[id]/edit/page.tsx b/App/app/(portal)/po/[id]/edit/page.tsx index 19a83b4..17c0bba 100644 --- a/App/app/(portal)/po/[id]/edit/page.tsx +++ b/App/app/(portal)/po/[id]/edit/page.tsx @@ -2,7 +2,7 @@ import { auth } from "@/auth"; import { db } from "@/lib/db"; import { notFound, redirect } from "next/navigation"; import { EditPoForm } from "./edit-po-form"; -import { buildCostCentreGroups, buildAccountGroups } from "@/lib/cost-centre-groups"; +import { buildAccountGroups } from "@/lib/cost-centre-groups"; import type { Metadata } from "next"; interface Props { @@ -23,15 +23,13 @@ export default async function EditPoPage({ params }: Props) { }); if (!po) notFound(); - if (!["DRAFT", "EDITS_REQUESTED"].includes(po.status)) redirect(`/po/${id}`); const canEdit = po.submitterId === session.user.id || session.user.role === "SUPERUSER"; if (!canEdit) redirect(`/po/${id}`); - const [vessels, sites, leafAccounts, vendors, noteAction] = await Promise.all([ - db.vessel.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true, code: true, siteId: true } }), - db.site.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true } }), + const [vessels, leafAccounts, vendors, noteAction] = await Promise.all([ + db.vessel.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true, code: true } }), db.account.findMany({ where: { isActive: true, children: { none: {} } }, orderBy: { code: "asc" }, @@ -47,9 +45,7 @@ export default async function EditPoPage({ params }: Props) { : Promise.resolve(null), ]); - const costCentres = buildCostCentreGroups(vessels, sites); - const accountGroups = buildAccountGroups(leafAccounts); - const initialCostCentreRef = po.vesselId ? `v:${po.vesselId}` : po.siteId ? `s:${po.siteId}` : ""; + const accounts = buildAccountGroups(leafAccounts); const serializedPo = { ...po, @@ -71,9 +67,8 @@ export default async function EditPoPage({ params }: Props) {
diff --git a/App/app/(portal)/po/[id]/page.tsx b/App/app/(portal)/po/[id]/page.tsx index 9e96e7f..4884165 100644 --- a/App/app/(portal)/po/[id]/page.tsx +++ b/App/app/(portal)/po/[id]/page.tsx @@ -26,7 +26,6 @@ export default async function PoDetailPage({ params }: Props) { include: { submitter: true, vessel: true, - site: { select: { id: true, name: true } }, account: true, vendor: true, lineItems: { orderBy: { sortOrder: "asc" } }, diff --git a/App/app/(portal)/po/[id]/receipt/actions.ts b/App/app/(portal)/po/[id]/receipt/actions.ts index ce0fa77..7b413de 100644 --- a/App/app/(portal)/po/[id]/receipt/actions.ts +++ b/App/app/(portal)/po/[id]/receipt/actions.ts @@ -28,7 +28,7 @@ export async function confirmReceipt({ include: { submitter: true, lineItems: true, - vessel: { include: { site: true } }, + vessel: true, }, }); if (!po) return { error: "PO not found" }; @@ -134,7 +134,6 @@ export async function confirmReceipt({ // Auto-update inventory for delivered quantities const siteId = (po as typeof po & { siteId?: string | null }).siteId ?? - po.vessel?.site?.id ?? null; if (siteId) { diff --git a/App/app/(portal)/po/import/actions.ts b/App/app/(portal)/po/import/actions.ts index bb65a48..17200e1 100644 --- a/App/app/(portal)/po/import/actions.ts +++ b/App/app/(portal)/po/import/actions.ts @@ -9,7 +9,7 @@ import type { ParsedImportLine } from "@/app/api/po/import/route"; export type ImportPoInput = { title: string; - costCentreRef: string; + vesselId: string; accountId: string; vendorId?: string; piQuotationNo?: string; @@ -32,9 +32,6 @@ export async function importPo( return { error: "You do not have permission to import purchase orders." }; } - const importVesselId = input.costCentreRef.startsWith("v:") ? input.costCentreRef.slice(2) : null; - const importSiteId = input.costCentreRef.startsWith("s:") ? input.costCentreRef.slice(2) : null; - const total = input.lineItems.reduce( (sum, item) => sum + item.quantity * item.unitPrice * (1 + (item.gstRate ?? 0.18)), 0 @@ -47,8 +44,7 @@ export async function importPo( status: "DRAFT", totalAmount: total, currency: "INR", - vesselId: importVesselId, - siteId: importSiteId, + vesselId: input.vesselId, accountId: input.accountId, vendorId: input.vendorId ?? null, piQuotationNo: input.piQuotationNo ?? null, diff --git a/App/app/(portal)/po/import/import-form.tsx b/App/app/(portal)/po/import/import-form.tsx index 64e151f..68ad3a3 100644 --- a/App/app/(portal)/po/import/import-form.tsx +++ b/App/app/(portal)/po/import/import-form.tsx @@ -3,7 +3,7 @@ import { useState, useRef } from "react"; import { useRouter } from "next/navigation"; import type { Vendor } from "@prisma/client"; -import type { CostCentreGroup, AccountGroup } from "@/app/(portal)/po/new/new-po-form"; +import type { VesselOption, AccountGroup } from "@/app/(portal)/po/new/new-po-form"; import { SearchableSelect } from "@/components/ui/searchable-select"; import { importPo } from "./actions"; import type { ParsedImport } from "@/app/api/po/import/route"; @@ -13,7 +13,7 @@ const INPUT_CLS = "w-full rounded-lg border border-neutral-300 px-3 py-2.5 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20"; interface Props { - costCentres: CostCentreGroup[]; + vessels: VesselOption[]; accounts: AccountGroup[]; vendors: Vendor[]; } @@ -21,12 +21,12 @@ interface Props { type PreviewState = { parsed: ParsedImport; title: string; - costCentreRef: string; + vesselId: string; accountId: string; vendorId: string; }; -export function ImportForm({ costCentres, accounts, vendors }: Props) { +export function ImportForm({ vessels, accounts, vendors }: Props) { const router = useRouter(); const fileRef = useRef(null); const [parsing, setParsing] = useState(false); @@ -66,7 +66,7 @@ export function ImportForm({ costCentres, accounts, vendors }: Props) { title: parsed.vendorName ? `${parsed.vendorName} — Import` : "Imported Purchase Order", - costCentreRef: costCentres[0]?.siteRef ?? costCentres[0]?.vessels[0]?.ref ?? "", + vesselId: vessels[0]?.id ?? "", accountId: accounts[0]?.items[0]?.id ?? "", vendorId: matchedVendor?.id ?? "", }); @@ -85,7 +85,7 @@ export function ImportForm({ costCentres, accounts, vendors }: Props) { const result = await importPo({ title: preview.title, - costCentreRef: preview.costCentreRef, + vesselId: preview.vesselId, accountId: preview.accountId, vendorId: preview.vendorId || undefined, piQuotationNo: preview.parsed.piQuotationNo || undefined, @@ -184,21 +184,14 @@ export function ImportForm({ costCentres, accounts, vendors }: Props) { Cost Centre *
@@ -295,7 +288,7 @@ export function ImportForm({ costCentres, accounts, vendors }: Props) {
- + ); } diff --git a/App/app/(portal)/po/new/actions.ts b/App/app/(portal)/po/new/actions.ts index cfef005..3caa6e2 100644 --- a/App/app/(portal)/po/new/actions.ts +++ b/App/app/(portal)/po/new/actions.ts @@ -51,7 +51,7 @@ export async function createPo( const parsed = createPoSchema.safeParse({ title: formData.get("title"), - costCentreRef: formData.get("costCentreRef"), + vesselId: formData.get("vesselId"), accountId: formData.get("accountId"), projectCode: formData.get("projectCode") || undefined, dateRequired: formData.get("dateRequired") || undefined, @@ -75,9 +75,6 @@ export async function createPo( } const data = parsed.data; - const vesselId = data.costCentreRef.startsWith("v:") ? data.costCentreRef.slice(2) : null; - const costCentreSiteId = data.costCentreRef.startsWith("s:") ? data.costCentreRef.slice(2) : null; - // totalAmount = grand total including GST const total = data.lineItems.reduce( (sum, item) => sum + item.quantity * item.unitPrice * (1 + item.gstRate), @@ -91,8 +88,7 @@ export async function createPo( status: intent === "submit" ? "SUBMITTED" : "DRAFT", totalAmount: total, currency: data.currency, - vesselId, - siteId: costCentreSiteId, + vesselId: data.vesselId, accountId: data.accountId, vendorId: data.vendorId ?? null, projectCode: data.projectCode ?? null, diff --git a/App/app/(portal)/po/new/new-po-form.tsx b/App/app/(portal)/po/new/new-po-form.tsx index dd6720b..b91274a 100644 --- a/App/app/(portal)/po/new/new-po-form.tsx +++ b/App/app/(portal)/po/new/new-po-form.tsx @@ -11,15 +11,7 @@ import { uploadAndLinkFiles } from "@/lib/upload-files"; import type { LineItemInput } from "@/lib/validations/po"; import { TC_DEFAULTS, TC_FIXED_LINE, TC_FIXED_LINE_2 } from "@/lib/validations/po"; -// Cost centres grouped by site: the site itself is selectable, vessels are listed under it -export type CostCentreGroup = { - siteId: string | null; // null = "Unassigned Vessels" fallback group - siteName: string; - siteRef: string | null; // "s:siteId" — selectable; null = no site option - vessels: { ref: string; label: string }[]; -}; - -// Accounting codes grouped by sub-category +export type VesselOption = { id: string; code: string; name: string }; export type AccountGroup = { group: string; items: { id: string; code: string; name: string }[] }; const INPUT_CLS = @@ -28,15 +20,15 @@ const INPUT_CLS = const EMPTY_LINE: LineItemInput = { name: "", description: "", quantity: 1, unit: "pc", size: "", unitPrice: 0, gstRate: 0.18 }; interface Props { - costCentres: CostCentreGroup[]; + vessels: VesselOption[]; accounts: AccountGroup[]; vendors: Vendor[]; initialLineItems?: LineItemInput[]; initialVendorId?: string; - initialCostCentreRef?: string; + initialVesselId?: string; } -export function NewPoForm({ costCentres, accounts, vendors, initialLineItems, initialVendorId, initialCostCentreRef }: Props) { +export function NewPoForm({ vessels, accounts, vendors, initialLineItems, initialVendorId, initialVesselId }: Props) { const router = useRouter(); const [lineItems, setLineItems] = useState( initialLineItems && initialLineItems.length > 0 ? initialLineItems : [EMPTY_LINE] @@ -96,22 +88,15 @@ export function NewPoForm({ costCentres, accounts, vendors, initialLineItems, in - {/* Cost Centre — grouped by site */} + {/* Cost Centre — vessels only */}
- - {costCentres.map((group) => ( - - {group.siteRef && ( - - )} - {group.vessels.map((v) => ( - - ))} - + {vessels.map((v) => ( + ))}
diff --git a/App/app/(portal)/po/new/page.tsx b/App/app/(portal)/po/new/page.tsx index 32cedff..466c67d 100644 --- a/App/app/(portal)/po/new/page.tsx +++ b/App/app/(portal)/po/new/page.tsx @@ -3,7 +3,7 @@ import { db } from "@/lib/db"; import { hasPermission } from "@/lib/permissions"; import { redirect } from "next/navigation"; import { NewPoForm } from "./new-po-form"; -import { buildCostCentreGroups, buildAccountGroups } from "@/lib/cost-centre-groups"; +import { buildAccountGroups } from "@/lib/cost-centre-groups"; import type { Metadata } from "next"; import type { LineItemInput } from "@/lib/validations/po"; import type { CartItem } from "@/lib/cart"; @@ -11,18 +11,16 @@ import type { CartItem } from "@/lib/cart"; export const metadata: Metadata = { title: "New Purchase Order" }; interface Props { - searchParams: Promise<{ cart?: string; costCentreRef?: string }>; + searchParams: Promise<{ cart?: string; vesselId?: string }>; } export default async function NewPoPage({ searchParams }: Props) { const session = await auth(); if (!session?.user) redirect("/login"); - if (!hasPermission(session.user.role, "create_po")) { - redirect("/dashboard"); - } + if (!hasPermission(session.user.role, "create_po")) redirect("/dashboard"); - const { cart, costCentreRef: initialCostCentreRef } = await searchParams; + const { cart, vesselId: initialVesselId } = await searchParams; let initialLineItems: LineItemInput[] | undefined; let initialVendorId: string | undefined; @@ -44,13 +42,12 @@ export default async function NewPoPage({ searchParams }: Props) { if (vendorIds.length === 1) initialVendorId = vendorIds[0]; } } catch { - // malformed cart param — ignore and start empty + // malformed cart param — ignore } } - const [vessels, sites, leafAccounts, vendors] = await Promise.all([ - db.vessel.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true, code: true, siteId: true } }), - db.site.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true } }), + const [vessels, leafAccounts, vendors] = await Promise.all([ + db.vessel.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true, code: true } }), db.account.findMany({ where: { isActive: true, children: { none: {} } }, orderBy: { code: "asc" }, @@ -59,7 +56,6 @@ export default async function NewPoPage({ searchParams }: Props) { db.vendor.findMany({ where: { isActive: true }, orderBy: { name: "asc" } }), ]); - const costCentres = buildCostCentreGroups(vessels, sites); const accounts = buildAccountGroups(leafAccounts); return ( @@ -71,12 +67,12 @@ export default async function NewPoPage({ searchParams }: Props) {

); diff --git a/App/app/api/reports/export/route.ts b/App/app/api/reports/export/route.ts index ada080c..a5aa672 100644 --- a/App/app/api/reports/export/route.ts +++ b/App/app/api/reports/export/route.ts @@ -24,7 +24,7 @@ export async function GET(request: NextRequest) { const format = sp.get("format") ?? "csv"; const dateFrom = sp.get("dateFrom"); const dateTo = sp.get("dateTo"); - const costCentreRef = sp.get("costCentreRef") ?? sp.get("vesselId"); + const vesselId = sp.get("vesselId"); const status = sp.get("status"); const where: NonNullable[0]>["where"] = {}; @@ -38,16 +38,12 @@ export async function GET(request: NextRequest) { } where.createdAt = createdAt; } - if (costCentreRef) { - if (costCentreRef.startsWith("v:")) where.vesselId = costCentreRef.slice(2); - else if (costCentreRef.startsWith("s:")) where.siteId = costCentreRef.slice(2); - else where.vesselId = costCentreRef; // legacy plain vesselId - } + if (vesselId) where.vesselId = vesselId; if (status) where.status = status as POStatus; const orders = await db.purchaseOrder.findMany({ where, - include: { submitter: true, vessel: true, site: { select: { name: true } }, account: true, vendor: true }, + include: { submitter: true, vessel: true, account: true, vendor: true }, orderBy: { createdAt: "desc" }, }); @@ -57,7 +53,7 @@ export async function GET(request: NextRequest) { ${po.poNumber} ${po.title} ${PO_STATUS_LABELS[po.status] ?? po.status} - ${po.vessel?.name ?? po.site?.name ?? "—"} + ${po.vessel.name} ${po.submitter.name} ${po.vendor?.name ?? "—"} ${Number(po.totalAmount).toLocaleString("en-IN", { style: "currency", currency: "INR" })} @@ -111,7 +107,7 @@ export async function GET(request: NextRequest) { po.poNumber, `"${po.title.replace(/"/g, '""')}"`, po.status, - po.vessel?.name ?? po.site?.name ?? "", + po.vessel.name, po.account.name, po.vendor?.name ?? "", po.submitter.name, diff --git a/App/components/po/po-detail.tsx b/App/components/po/po-detail.tsx index 60786c7..6dca391 100644 --- a/App/components/po/po-detail.tsx +++ b/App/components/po/po-detail.tsx @@ -38,8 +38,7 @@ type PoWithRelations = { paidAt: Date | null; closedAt: Date | null; submitter: { id: string; name: string; email: string }; - vessel: { id: string; name: string } | null; - site?: { id: string; name: string } | null; + vessel: { id: string; name: string }; account: { id: string; name: string; code: string }; vendor: { id: string; @@ -229,7 +228,7 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals po.status === "MGR_REVIEW" && (currentRole === "MANAGER" || currentRole === "SUPERUSER") && (() => { const snap = resubmitSnapshot.fields; - const currentVessel = po.vessel?.name ?? po.site?.name ?? null; + const currentVessel = po.vessel?.name ?? null; const currentAccount = `${po.account.name} (${po.account.code})`; const currentVendor = po.vendor?.name ?? null; const currentDateRequired = po.dateRequired?.toISOString() ?? null; @@ -237,7 +236,7 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals const fieldChanges: { label: string; before: string | null; after: string | null }[] = []; if (snap.title !== po.title) fieldChanges.push({ label: "Title", before: snap.title, after: po.title }); - if (snap.vesselId !== (po.vessel?.id ?? po.site?.id ?? "")) + if (snap.vesselId !== po.vessel.id) fieldChanges.push({ label: "Cost Centre", before: snap.vessel, after: currentVessel }); if (snap.accountId !== po.account.id) fieldChanges.push({ label: "Account", before: snap.account, after: currentAccount }); @@ -287,7 +286,7 @@ export async function PoDetail({ po, currentUserId, currentRole, readOnly = fals

Order Details

-
Cost Centre
{po.vessel?.name ?? po.site?.name ?? "—"}
+
Cost Centre
{po.vessel?.name ?? "—"}
Accounting Code
{po.account.name} ({po.account.code})
Requested By
{po.submitter.name}
{approvalAction && ( diff --git a/App/lib/cost-centre-groups.ts b/App/lib/cost-centre-groups.ts index 36387e2..242f4a3 100644 --- a/App/lib/cost-centre-groups.ts +++ b/App/lib/cost-centre-groups.ts @@ -1,40 +1,3 @@ -import type { CostCentreGroup } from "@/app/(portal)/po/new/new-po-form"; - -type VesselLike = { id: string; name: string; code: string; siteId: string | null }; -type SiteLike = { id: string; name: string }; - -/** - * Builds the grouped cost-centre list used by PO form dropdowns. - * Each group = one site (as an optgroup), with the site itself as a selectable - * option followed by its vessels. - * Vessels with no site appear under an "Unassigned Vessels" group at the end. - */ -export function buildCostCentreGroups( - vessels: VesselLike[], - sites: SiteLike[] -): CostCentreGroup[] { - const groups: CostCentreGroup[] = sites.map((s) => ({ - siteId: s.id, - siteName: s.name, - siteRef: `s:${s.id}`, - vessels: vessels - .filter((v) => v.siteId === s.id) - .map((v) => ({ ref: `v:${v.id}`, label: `${v.code} — ${v.name}` })), - })); - - const unassigned = vessels.filter((v) => !v.siteId); - if (unassigned.length > 0) { - groups.push({ - siteId: null, - siteName: "Unassigned Vessels", - siteRef: null, - vessels: unassigned.map((v) => ({ ref: `v:${v.id}`, label: `${v.code} — ${v.name}` })), - }); - } - - return groups; -} - /** * Builds grouped accounting codes for the SearchableSelect component. * Only returns leaf items (no children), grouped by sub-category. diff --git a/App/lib/validations/po.ts b/App/lib/validations/po.ts index b6957e4..ca674e5 100644 --- a/App/lib/validations/po.ts +++ b/App/lib/validations/po.ts @@ -29,10 +29,7 @@ export const TC_DEFAULTS = { export const createPoSchema = z.object({ title: z.string().min(1, "Title is required").max(200), - costCentreRef: z.string().min(1, "Cost Centre is required").refine( - (v) => v.startsWith("v:") || v.startsWith("s:"), - "Invalid cost centre selection" - ), + vesselId: z.string().min(1, "Cost Centre is required"), accountId: z.string().min(1, "Accounting Code is required"), projectCode: z.string().optional(), dateRequired: z.string().optional(), diff --git a/App/prisma/migrations/20260530000002_vessel_no_site_po_vessel_required/migration.sql b/App/prisma/migrations/20260530000002_vessel_no_site_po_vessel_required/migration.sql new file mode 100644 index 0000000..9eb9f5e --- /dev/null +++ b/App/prisma/migrations/20260530000002_vessel_no_site_po_vessel_required/migration.sql @@ -0,0 +1,7 @@ +-- Remove vessel → site relationship (vessels are standalone cost centres) +ALTER TABLE "Vessel" DROP COLUMN IF EXISTS "siteId"; + +-- Restore vesselId as required on PurchaseOrder +-- First null out any rows that have no vesselId (set to first vessel as fallback, should be none in practice) +UPDATE "PurchaseOrder" SET "vesselId" = (SELECT id FROM "Vessel" LIMIT 1) WHERE "vesselId" IS NULL; +ALTER TABLE "PurchaseOrder" ALTER COLUMN "vesselId" SET NOT NULL; diff --git a/App/prisma/schema.prisma b/App/prisma/schema.prisma index 268c5a9..5627e3b 100644 --- a/App/prisma/schema.prisma +++ b/App/prisma/schema.prisma @@ -100,7 +100,6 @@ model Site { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - vessels Vessel[] purchaseOrders PurchaseOrder[] inventory ItemInventory[] consumption ItemConsumption[] @@ -112,9 +111,6 @@ model Vessel { code String @unique isActive Boolean @default(true) - siteId String? - site Site? @relation(fields: [siteId], references: [id]) - purchaseOrders PurchaseOrder[] } @@ -257,8 +253,8 @@ model PurchaseOrder { submitterId String submitter User @relation("Submitter", fields: [submitterId], references: [id]) - vesselId String? - vessel Vessel? @relation(fields: [vesselId], references: [id]) + vesselId String + vessel Vessel @relation(fields: [vesselId], references: [id]) accountId String account Account @relation(fields: [accountId], references: [id]) vendorId String? diff --git a/App/prisma/seed-prod.ts b/App/prisma/seed-prod.ts index 72751c0..2837eba 100644 --- a/App/prisma/seed-prod.ts +++ b/App/prisma/seed-prod.ts @@ -19,20 +19,20 @@ 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 }, + { employeeId: "ACC-001", name: "Akshata Teli", email: "akshata@pelagiamarine.com", role: Role.ACCOUNTS }, + { employeeId: "ACC-002", name: "Dipali K", email: "dipali.k@pelagiamarine.com", role: Role.ACCOUNTS }, + { employeeId: "ACC-003", name: "Nikita Accounts", email: "nikita.m@pelagiamarine.com", role: Role.ACCOUNTS }, + { employeeId: "ACC-004", name: "Shailesh B", email: "shailesh.b@pelagiamarine.com", role: Role.ACCOUNTS }, + { employeeId: "MGR-001", name: "Chhagan Sarang", email: "chhagan.sarang@pelagiamarine.com", role: Role.MANAGER }, + { employeeId: "MGR-002", name: "Kaushal Pal Singh", email: "kps@pelagiamarine.com", role: Role.MANAGER }, + { employeeId: "MGR-003", name: "Rakesh Kumar Pandey", email: "rkp@pelagiamarine.com", role: Role.MANAGER }, + { employeeId: "MGR-004", name: "Tajinder Kaur", email: "tajinder.kaur@pelagiamarine.com", role: Role.MANAGER }, + { employeeId: "MAN-001", name: "Mayur Deore", email: "mayur@pelagiamarine.com", role: Role.MANNING }, + { employeeId: "MAN-002", name: "Sunil Gupta", email: "sunil.gupta@pelagiamarine.com", role: Role.MANNING }, + { employeeId: "TCH-001", name: "Eeshan Singh", email: "eeshan.singh@pelagiamarine.com", role: Role.TECHNICAL }, + { employeeId: "TCH-002", name: "Manjuprasad B", email: "manjuprasad.b@pelagiamarine.com", role: Role.TECHNICAL }, + { employeeId: "TCH-003", name: "Shrikant T", email: "shrikant.t@pelagiamarine.com", role: Role.TECHNICAL }, + { employeeId: "TCH-004", name: "Supriya Sutar", email: "supriya.s@pelagiamarine.com", role: Role.TECHNICAL }, ]; // ─── Sites ──────────────────────────────────────────────────────────────────── @@ -49,17 +49,17 @@ const SITES: { code: string; name: string }[] = [ // ─── 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" }, +const VESSELS: { code: string; name: string }[] = [ + { code: "HNR1", name: "HNR 1" }, + { code: "HNR2", name: "HNR 2" }, + { code: "HNR3", name: "HNR 3" }, + { code: "HNR4", name: "HNR 4" }, + { code: "CHAMPION", name: "Champion" }, + { code: "HANUNAM", name: "Hanunam" }, + { code: "SEJAL", name: "Sejal" }, + { code: "SEJAL2", name: "Sejal 2" }, + { code: "GD3000", name: "GD 3000" }, + { code: "THILAKKAM", name: "Thilakkam" }, ]; // ─── Main ───────────────────────────────────────────────────────────────────── @@ -86,31 +86,24 @@ async function main() { // ── Sites ────────────────────────────────────────────────────────────────── console.log("\n📍 Seeding sites…"); - const siteIdMap = new Map(); for (const s of SITES) { - const site = await db.site.upsert({ + 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 }, + update: { name: v.name }, + create: { code: v.code, name: v.name }, }); - console.log(` ✓ ${v.name} (${v.code}) → ${v.siteCode}`); + console.log(` ✓ ${v.name} (${v.code})`); } // ── Accounting Codes ─────────────────────────────────────────────────────── diff --git a/App/prisma/seed.ts b/App/prisma/seed.ts index edc4222..26e3446 100644 --- a/App/prisma/seed.ts +++ b/App/prisma/seed.ts @@ -158,25 +158,23 @@ async function main() { }); // ─── Vessels (Cost Centres) ────────────────────────────────────────────────── - const findOrCreateVessel = async (name: string, siteId: string, code: string) => { + const findOrCreateVessel = async (name: string, code: string) => { const vessel = await db.vessel.findFirst({ where: { name } }); - if (vessel) { - return db.vessel.update({ where: { id: vessel.id }, data: { siteId } }); - } - return db.vessel.create({ data: { name, code, siteId } }); + if (vessel) return vessel; + return db.vessel.create({ data: { name, code } }); }; - const mvStar = await findOrCreateVessel("MV Pelagia Star", siteBOM.id, "SITE-001"); - const mvWind = await findOrCreateVessel("MV Aegean Wind", siteJNP.id, "SITE-002"); - const mvPoseidon = await findOrCreateVessel("MV Poseidon", siteKDL.id, "SITE-003"); - const mvNereid = await findOrCreateVessel("MV Nereid", siteCHE.id, "SITE-004"); - const mvThetis = await findOrCreateVessel("MV Thetis", siteKOC.id, "SITE-005"); - const mvTriton = await findOrCreateVessel("MV Triton", siteVIZ.id, "SITE-006"); - const mvAmphitrite = await findOrCreateVessel("MV Amphitrite", siteHAL.id, "SITE-007"); - const mvProteus = await findOrCreateVessel("MV Proteus", sitePAR.id, "SITE-008"); - const mvGalatea = await findOrCreateVessel("MV Galatea", siteMNG.id, "SITE-009"); - const mvCallisto = await findOrCreateVessel("MV Callisto", siteGOA.id, "SITE-010"); - await findOrCreateVessel("MV Doris", siteCHE.id, "SITE-011"); + 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