"use server"; import { auth } from "@/auth"; import { db } from "@/lib/db"; import { hasPermission } from "@/lib/permissions"; import { generatePoNumber } from "@/lib/po-number"; import { revalidatePath } from "next/cache"; import type { ParsedImportLine } from "@/app/api/po/import/route"; export type ImportPoInput = { title: string; vesselId: string; accountId: string; companyId?: string; /** Original PO number from the imported Excel — preserved as-is on the PO record. * If absent, a new structured number is generated. */ originalPoNumber?: string; /** vendorId of an existing vendor, if pre-matched in the UI */ vendorId?: string; /** Raw vendor name from the Excel — used to auto-create if no vendorId matched */ parsedVendorName?: string; parsedVendorAddress?: string; parsedVendorContact?: string; piQuotationNo?: string; placeOfDelivery?: string; tcDelivery?: string; tcDispatch?: string; tcInspection?: string; tcTransitInsurance?: string; tcPaymentTerms?: string; tcOthers?: string; lineItems: ParsedImportLine[]; }; export async function importPo( input: ImportPoInput ): Promise<{ id: string } | { error: string }> { const session = await auth(); if (!session?.user) return { error: "Unauthorized" }; if (!hasPermission(session.user.role, "create_po") && session.user.role !== "ADMIN") { return { error: "You do not have permission to import purchase orders." }; } const now = new Date(); // ── 1. Resolve / auto-create vendor ─────────────────────────────────────── let resolvedVendorId: string | null = input.vendorId ?? null; if (!resolvedVendorId && input.parsedVendorName) { // Try case-insensitive match first const existing = await db.vendor.findFirst({ where: { name: { equals: input.parsedVendorName, mode: "insensitive" } }, select: { id: true }, }); if (existing) { resolvedVendorId = existing.id; } else { // Auto-create vendor from imported data const newVendor = await db.vendor.create({ data: { name: input.parsedVendorName, address: input.parsedVendorAddress || null, contacts: input.parsedVendorContact ? { create: { name: input.parsedVendorContact, isPrimary: true, }, } : undefined, }, }); resolvedVendorId = newVendor.id; } } // ── 2. Resolve / auto-create products ───────────────────────────────────── const resolvedLineItems: Array< ParsedImportLine & { productId?: string } > = []; for (const item of input.lineItems) { const existing = await db.product.findFirst({ where: { name: { equals: item.name, mode: "insensitive" } }, select: { id: true }, }); let productId: string | undefined; if (existing) { productId = existing.id; // Update lastPrice / lastVendor on the product if (item.unitPrice > 0) { await db.product.update({ where: { id: existing.id }, data: { lastPrice: item.unitPrice, ...(resolvedVendorId ? { lastVendorId: resolvedVendorId } : {}), }, }); } } else { // Auto-create product const count = await db.product.count(); const code = `PROD-${String(count + 1).padStart(4, "0")}`; const newProduct = await db.product.create({ data: { code, name: item.name, lastPrice: item.unitPrice > 0 ? item.unitPrice : null, lastVendorId: resolvedVendorId ?? null, }, }); productId = newProduct.id; } // Upsert per-vendor price so the item catalogue reflects actual invoice prices if (productId && resolvedVendorId && item.unitPrice > 0) { await db.productVendorPrice.upsert({ where: { productId_vendorId: { productId, vendorId: resolvedVendorId } }, update: { price: item.unitPrice }, create: { productId, vendorId: resolvedVendorId, price: item.unitPrice }, }); } resolvedLineItems.push({ ...item, productId }); } // ── 3. Calculate total ───────────────────────────────────────────────────── const total = resolvedLineItems.reduce( (sum, item) => sum + item.quantity * item.unitPrice * (1 + (item.gstRate ?? 0.18)), 0 ); // ── 4. Determine PO number ──────────────────────────────────────────────── // Preserve the original PO number from the imported document when available; // otherwise generate a new structured number starting from 9000+. const poNumber = input.originalPoNumber?.trim() || await generatePoNumber(input.vesselId, input.companyId); // ── 5. Create PO in CLOSED state ────────────────────────────────────────── // Imported POs bypass the approval workflow — they are historical records. const po = await db.purchaseOrder.create({ data: { poNumber, title: input.title, status: "CLOSED", totalAmount: total, currency: "INR", vesselId: input.vesselId, accountId: input.accountId, companyId: input.companyId ?? null, vendorId: resolvedVendorId, piQuotationNo: input.piQuotationNo ?? null, placeOfDelivery: input.placeOfDelivery ?? null, tcDelivery: input.tcDelivery ?? null, tcDispatch: input.tcDispatch ?? null, tcInspection: input.tcInspection ?? null, tcTransitInsurance: input.tcTransitInsurance ?? null, tcPaymentTerms: input.tcPaymentTerms ?? null, tcOthers: input.tcOthers ?? null, submitterId: session.user.id, submittedAt: now, approvedAt: now, paidAt: now, closedAt: now, lineItems: { create: resolvedLineItems.map((item, idx) => ({ name: item.name, quantity: item.quantity, unit: item.unit, unitPrice: item.unitPrice, totalPrice: item.quantity * item.unitPrice, gstRate: item.gstRate ?? 0.18, sortOrder: idx, productId: item.productId ?? null, })), }, actions: { create: [ { actionType: "CREATED", actorId: session.user.id, createdAt: now }, { actionType: "SUBMITTED", actorId: session.user.id, createdAt: now }, { actionType: "APPROVED", actorId: session.user.id, createdAt: now }, { actionType: "CLOSED", actorId: session.user.id, createdAt: now }, ], }, }, }); revalidatePath("/history"); revalidatePath("/dashboard"); return { id: po.id }; }