diff --git a/App/app/(portal)/admin/sites/[id]/page.tsx b/App/app/(portal)/admin/sites/[id]/page.tsx index baf80a3..fa9b01e 100644 --- a/App/app/(portal)/admin/sites/[id]/page.tsx +++ b/App/app/(portal)/admin/sites/[id]/page.tsx @@ -31,7 +31,6 @@ export default async function SiteDetailPage({ params }: Props) { db.site.findUnique({ where: { id }, include: { - vessels: { select: { id: true, name: true, isActive: true } }, inventory: { include: { product: { select: { id: true, name: true, code: true } } }, orderBy: { quantity: "desc" }, @@ -100,7 +99,7 @@ export default async function SiteDetailPage({ params }: Props) { )}
- + + Create PO {canEdit && } @@ -108,11 +107,7 @@ export default async function SiteDetailPage({ params }: Props) {
{/* Summary cards */} -
-
-

Assigned Vessels

-

{site.vessels.length}

-
+

Items Tracked

{site.inventory.length}

@@ -167,20 +162,6 @@ export default async function SiteDetailPage({ params }: Props) {
- {/* Assigned vessels */} - {site.vessels.length > 0 && ( -
-

Assigned Vessels (Cost Centres)

-
- {site.vessels.map((v) => ( - - {v.name} - - ))} -
-
- )} {/* Recent POs */} {site.purchaseOrders.length > 0 && ( diff --git a/App/app/(portal)/admin/sites/actions.ts b/App/app/(portal)/admin/sites/actions.ts index 2869530..9afe04a 100644 --- a/App/app/(portal)/admin/sites/actions.ts +++ b/App/app/(portal)/admin/sites/actions.ts @@ -85,7 +85,6 @@ export async function deleteSite(id: string): Promise { await tx.purchaseOrder.updateMany({ where: { siteId: id, status: "DRAFT" }, data: { siteId: null } }); await tx.itemConsumption.deleteMany({ where: { siteId: id } }); await tx.itemInventory.deleteMany({ where: { siteId: id } }); - await tx.vessel.updateMany({ where: { siteId: id }, data: { siteId: null } }); await tx.site.delete({ where: { id } }); }); diff --git a/App/app/(portal)/admin/sites/page.tsx b/App/app/(portal)/admin/sites/page.tsx index 4d74021..aefc6d5 100644 --- a/App/app/(portal)/admin/sites/page.tsx +++ b/App/app/(portal)/admin/sites/page.tsx @@ -15,7 +15,7 @@ export default async function SitesPage() { const sites = await db.site.findMany({ orderBy: { name: "asc" }, include: { - _count: { select: { vessels: true, inventory: true } }, + _count: { select: { inventory: true } }, }, }); @@ -32,7 +32,6 @@ export default async function SitesPage() { latitude: s.latitude ?? null, longitude: s.longitude ?? null, isActive: s.isActive, - vesselCount: s._count.vessels, inventoryCount: s._count.inventory, }))} /> diff --git a/App/app/(portal)/admin/sites/sites-table.tsx b/App/app/(portal)/admin/sites/sites-table.tsx index 05eb335..69aca8a 100644 --- a/App/app/(portal)/admin/sites/sites-table.tsx +++ b/App/app/(portal)/admin/sites/sites-table.tsx @@ -18,7 +18,6 @@ export type SiteRow = { latitude: number | null; longitude: number | null; isActive: boolean; - vesselCount: number; inventoryCount: number; }; @@ -123,7 +122,6 @@ export function SitesTable({ toggleSort(k as keyof SiteRow)}>Name toggleSort(k as keyof SiteRow)}>Code toggleSort(k as keyof SiteRow)}>Address - Vessels Items tracked Location toggleSort(k as keyof SiteRow)}>Status @@ -133,7 +131,7 @@ export function SitesTable({ {filtered.length === 0 && ( - + No sites match your search. @@ -149,9 +147,6 @@ export function SitesTable({ {site.address ?? } - - {site.vesselCount || } - {site.inventoryCount || } diff --git a/App/app/(portal)/admin/vessels/[id]/page.tsx b/App/app/(portal)/admin/vessels/[id]/page.tsx index 8b87674..ab990f7 100644 --- a/App/app/(portal)/admin/vessels/[id]/page.tsx +++ b/App/app/(portal)/admin/vessels/[id]/page.tsx @@ -24,7 +24,6 @@ export default async function VesselDetailPage({ params }: Props) { const vessel = await db.vessel.findUnique({ where: { id }, include: { - site: true, purchaseOrders: { select: { id: true, poNumber: true, status: true, totalAmount: true, createdAt: true, vendor: { select: { name: true } } }, orderBy: { createdAt: "desc" }, @@ -60,13 +59,8 @@ export default async function VesselDetailPage({ params }: Props) {

{vessel.name}

- {vessel.site && ( -

- Home site: {vessel.site.name} -

- )}
- + + Create PO diff --git a/App/app/(portal)/admin/vessels/actions.ts b/App/app/(portal)/admin/vessels/actions.ts index e6078cc..698b6cc 100644 --- a/App/app/(portal)/admin/vessels/actions.ts +++ b/App/app/(portal)/admin/vessels/actions.ts @@ -12,7 +12,6 @@ type ActionResult = { ok: true } | { error: string }; const vesselSchema = z.object({ name: z.string().min(1, "Vessel name is required"), code: z.string().optional(), - siteId: z.string().optional(), }); export async function createVessel(formData: FormData): Promise { @@ -24,7 +23,6 @@ export async function createVessel(formData: FormData): Promise { const parsed = vesselSchema.safeParse({ name: formData.get("name"), code: (formData.get("code") as string).trim() || undefined, - siteId: (formData.get("siteId") as string) || undefined, }); if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" }; @@ -32,16 +30,14 @@ export async function createVessel(formData: FormData): Promise { let code: string; if (parsed.data.code) { - // User supplied a custom code — validate uniqueness const conflict = await db.vessel.findUnique({ where: { code: parsed.data.code } }); if (conflict) return { error: `Code "${parsed.data.code}" is already in use by another vessel.` }; code = parsed.data.code; } else { - // Auto-generate next available code code = nextId("SITE", existingCodes.map((v) => v.code)); } - await db.vessel.create({ data: { name: parsed.data.name, code, siteId: parsed.data.siteId ?? null } }); + await db.vessel.create({ data: { name: parsed.data.name, code } }); revalidatePath("/admin/vessels"); return { ok: true }; } @@ -57,11 +53,10 @@ export async function updateVessel(formData: FormData): Promise { const parsed = vesselSchema.safeParse({ name: formData.get("name"), - siteId: (formData.get("siteId") as string) || undefined, }); if (!parsed.success) return { error: parsed.error.errors[0]?.message ?? "Validation failed" }; - await db.vessel.update({ where: { id }, data: { name: parsed.data.name, siteId: parsed.data.siteId ?? null } }); + await db.vessel.update({ where: { id }, data: { name: parsed.data.name } }); revalidatePath("/admin/vessels"); return { ok: true }; } diff --git a/App/app/(portal)/admin/vessels/page.tsx b/App/app/(portal)/admin/vessels/page.tsx index a9ab70f..647cc43 100644 --- a/App/app/(portal)/admin/vessels/page.tsx +++ b/App/app/(portal)/admin/vessels/page.tsx @@ -14,13 +14,9 @@ export default async function AdminVesselsPage() { if (!hasPermission(session.user.role, "manage_vessels_accounts")) redirect("/dashboard"); - const [vessels, sites] = await Promise.all([ - db.vessel.findMany({ - orderBy: { name: "asc" }, - include: { site: { select: { name: true } } }, - }), - db.site.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true } }), - ]); + const vessels = await db.vessel.findMany({ + orderBy: { name: "asc" }, + }); const suggestedCode = nextId("SITE", vessels.map((v) => v.code)); @@ -31,11 +27,8 @@ export default async function AdminVesselsPage() { id: v.id, code: v.code, name: v.name, - siteId: v.siteId ?? null, - siteName: v.site?.name ?? null, isActive: v.isActive, }))} - sites={sites} /> ); } diff --git a/App/app/(portal)/admin/vessels/vessel-form.tsx b/App/app/(portal)/admin/vessels/vessel-form.tsx index 2e0d62d..542db3d 100644 --- a/App/app/(portal)/admin/vessels/vessel-form.tsx +++ b/App/app/(portal)/admin/vessels/vessel-form.tsx @@ -5,38 +5,25 @@ import { useRouter } from "next/navigation"; import { AdminDialog } from "@/components/ui/admin-dialog"; import { createVessel, updateVessel, toggleVesselActive } from "./actions"; -type SiteOption = { id: string; name: string }; - type VesselRow = { id: string; name: string; code: string; - siteId: string | null; isActive: boolean; }; const INPUT = "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"; -function VesselFormFields({ - vessel, - sites, - suggestedCode, -}: { - vessel?: VesselRow; - sites: SiteOption[]; - suggestedCode?: string; -}) { +function VesselFormFields({ vessel, suggestedCode }: { vessel?: VesselRow; suggestedCode?: string }) { return (
{vessel ? ( - /* Editing: show code read-only — code changes on existing data would break references */

{vessel.code}

) : ( - /* Creating: editable, pre-filled with next available code */ 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