pelagia-portal/App/app/(portal)/po/import/page.tsx
Hardik 0d17672ea9 feat(accounts): hierarchical accounting codes with 6-digit format and category tree
- Account model gains parentId (self-referential, 3 levels: TopCategory → SubCategory → Item)
- DB migration: adds parentId FK column to Account table
- Code format changed from PREFIX-NNN to 6-digit numeric (e.g. 100101)
- Seeded all 300+ accounting codes from the official chart (Rev. 01/251227) across
  7 top categories: Capital Expenses, Business Development, Office Admin, Project
  Expenses, Manning, Technical, Bunker/Lubes
- Admin Accounting Code page: collapsible tree view (top category > sub-category > items),
  inline search, Add/Edit dialogs with parent selector and 6-digit code field
- All PO forms (new, edit, import, manager-edit): accounting code dropdown now shows
  only leaf items grouped in <optgroup> by sub-category, labelled "TopCat › SubCat"
- Seed data updated: old flat account codes replaced by mapped leaf codes from new hierarchy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 03:27:31 +05:30

55 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

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

import { auth } from "@/auth";
import { db } from "@/lib/db";
import { redirect } from "next/navigation";
import { ImportForm } from "./import-form";
import type { CostCentreOption, AccountGroup } from "@/app/(portal)/po/new/new-po-form";
import type { Metadata } from "next";
export const metadata: Metadata = { title: "Import Purchase Order" };
export default async function ImportPoPage() {
const session = await auth();
if (!session?.user) redirect("/login");
const { role } = session.user;
if (!["MANAGER", "SUPERUSER", "ADMIN"].includes(role)) redirect("/dashboard");
const [vessels, sites, accounts, vendors] = await Promise.all([
db.vessel.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, select: { id: true, name: true, code: true } }),
db.site.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" },
select: { id: true, code: true, name: true, parent: { select: { name: true, code: true, parent: { select: { name: true, code: true } } } } },
}),
db.vendor.findMany({ orderBy: { name: "asc" } }),
]);
const accountGroupMap = new Map<string, typeof accounts>();
for (const a of accounts) {
const subLabel = a.parent ? `${a.parent.code}${a.parent.name}` : "Uncategorised";
const topLabel = a.parent?.parent ? `${a.parent.parent.name} ` : "";
const groupKey = `${topLabel}${subLabel}`;
if (!accountGroupMap.has(groupKey)) accountGroupMap.set(groupKey, []);
accountGroupMap.get(groupKey)!.push(a);
}
const accountGroups: AccountGroup[] = Array.from(accountGroupMap.entries()).map(([group, items]) => ({ group, items }));
const costCentres: CostCentreOption[] = [
...vessels.map((v) => ({ ref: `v:${v.id}`, label: `${v.code}${v.name}`, group: "Vessels" as const })),
...sites.map((s) => ({ ref: `s:${s.id}`, label: `${s.code}${s.name}`, group: "Sites" as const })),
];
return (
<div className="max-w-6xl">
<div className="mb-6">
<h1 className="text-2xl font-semibold text-neutral-900">Import Purchase Order</h1>
<p className="mt-1 text-sm text-neutral-500">
Upload a Pelagia-format Excel PO file. Line items and vendor details are extracted automatically.
You then select the cost centre, accounting code, and confirm before saving as a draft.
</p>
</div>
<ImportForm costCentres={costCentres} accounts={accountGroups} vendors={vendors} />
</div>
);
}