diff --git a/App/pelagia-portal/app/(portal)/admin/vendors/[id]/page.tsx b/App/pelagia-portal/app/(portal)/admin/vendors/[id]/page.tsx index 804807a..7974969 100644 --- a/App/pelagia-portal/app/(portal)/admin/vendors/[id]/page.tsx +++ b/App/pelagia-portal/app/(portal)/admin/vendors/[id]/page.tsx @@ -81,10 +81,11 @@ export default async function VendorDetailPage({ params }: Props) { id: vendor.id, name: vendor.name, vendorId: vendor.vendorId, - address: (vendor as typeof vendor & { address?: string | null }).address ?? null, - gstin: (vendor as typeof vendor & { gstin?: string | null }).gstin ?? null, + address: vendor.address ?? null, + pincode: vendor.pincode ?? null, + gstin: vendor.gstin ?? null, contactName: vendor.contactName, - contactMobile: (vendor as typeof vendor & { contactMobile?: string | null }).contactMobile ?? null, + contactMobile: vendor.contactMobile ?? null, contactEmail: vendor.contactEmail, isActive: vendor.isActive, }} /> diff --git a/App/pelagia-portal/app/(portal)/admin/vendors/actions.ts b/App/pelagia-portal/app/(portal)/admin/vendors/actions.ts index 724132e..4693f49 100644 --- a/App/pelagia-portal/app/(portal)/admin/vendors/actions.ts +++ b/App/pelagia-portal/app/(portal)/admin/vendors/actions.ts @@ -3,6 +3,7 @@ import { auth } from "@/auth"; import { db } from "@/lib/db"; import { hasPermission } from "@/lib/permissions"; +import { geocodePincode } from "@/lib/geo"; import { z } from "zod"; import { revalidatePath } from "next/cache"; @@ -12,14 +13,19 @@ const vendorSchema = z.object({ name: z.string().min(1, "Vendor name is required"), vendorId: z.string().optional(), address: z.string().optional(), + pincode: z.string().optional(), gstin: z.string().optional(), - latitude: z.coerce.number().optional(), - longitude: z.coerce.number().optional(), contactName: z.string().optional(), contactMobile: z.string().optional(), contactEmail: z.string().email("Invalid contact email").optional().or(z.literal("")), }); +async function resolveLatLng(pincode?: string) { + if (!pincode) return { latitude: null, longitude: null }; + const coords = await geocodePincode(pincode); + return { latitude: coords?.lat ?? null, longitude: coords?.lng ?? null }; +} + export async function createVendor(formData: FormData): Promise { const session = await auth(); if (!session?.user || !hasPermission(session.user.role, "manage_vendors")) { @@ -30,9 +36,8 @@ export async function createVendor(formData: FormData): Promise { name: formData.get("name"), vendorId: formData.get("vendorId") || undefined, address: formData.get("address") || undefined, + pincode: formData.get("pincode") || undefined, gstin: formData.get("gstin") || undefined, - latitude: formData.get("latitude") || undefined, - longitude: formData.get("longitude") || undefined, contactName: formData.get("contactName") || undefined, contactMobile: formData.get("contactMobile") || undefined, contactEmail: formData.get("contactEmail") || undefined, @@ -45,14 +50,17 @@ export async function createVendor(formData: FormData): Promise { if (exists) return { error: "A vendor with that Vendor ID already exists" }; } + const { latitude, longitude } = await resolveLatLng(data.pincode); + await db.vendor.create({ data: { name: data.name, vendorId: data.vendorId ?? null, address: data.address ?? null, + pincode: data.pincode ?? null, gstin: data.gstin ?? null, - latitude: data.latitude ?? null, - longitude: data.longitude ?? null, + latitude, + longitude, contactName: data.contactName ?? null, contactMobile: data.contactMobile ?? null, contactEmail: data.contactEmail || null, @@ -77,9 +85,8 @@ export async function updateVendor(formData: FormData): Promise { name: formData.get("name"), vendorId: formData.get("vendorId") || undefined, address: formData.get("address") || undefined, + pincode: formData.get("pincode") || undefined, gstin: formData.get("gstin") || undefined, - latitude: formData.get("latitude") || undefined, - longitude: formData.get("longitude") || undefined, contactName: formData.get("contactName") || undefined, contactMobile: formData.get("contactMobile") || undefined, contactEmail: formData.get("contactEmail") || undefined, @@ -92,15 +99,22 @@ export async function updateVendor(formData: FormData): Promise { if (conflict) return { error: "Another vendor already has that Vendor ID" }; } + const existing = await db.vendor.findUnique({ where: { id }, select: { pincode: true, latitude: true, longitude: true } }); + const pincodeChanged = data.pincode !== (existing?.pincode ?? undefined); + const { latitude, longitude } = pincodeChanged + ? await resolveLatLng(data.pincode) + : { latitude: existing?.latitude ?? null, longitude: existing?.longitude ?? null }; + await db.vendor.update({ where: { id }, data: { name: data.name, vendorId: data.vendorId ?? null, address: data.address ?? null, + pincode: data.pincode ?? null, gstin: data.gstin ?? null, - latitude: data.latitude ?? null, - longitude: data.longitude ?? null, + latitude, + longitude, contactName: data.contactName ?? null, contactMobile: data.contactMobile ?? null, contactEmail: data.contactEmail || null, @@ -112,6 +126,26 @@ export async function updateVendor(formData: FormData): Promise { return { ok: true }; } +export async function deleteVendor(id: string): Promise { + const session = await auth(); + if (!session?.user || !hasPermission(session.user.role, "manage_vendors")) return { error: "Unauthorized" }; + + const blocked = await db.purchaseOrder.findFirst({ + where: { vendorId: id, status: { not: "DRAFT" } }, + }); + if (blocked) return { error: "Cannot delete: vendor is referenced in submitted or active purchase orders." }; + + await db.$transaction(async (tx) => { + await tx.purchaseOrder.updateMany({ where: { vendorId: id, status: "DRAFT" }, data: { vendorId: null } }); + await tx.product.updateMany({ where: { lastVendorId: id }, data: { lastVendorId: null } }); + await tx.productVendorPrice.deleteMany({ where: { vendorId: id } }); + await tx.vendor.delete({ where: { id } }); + }); + + revalidatePath("/admin/vendors"); + return { ok: true }; +} + export async function toggleVendorActive(vendorId: string): Promise { const session = await auth(); if (!session?.user || !hasPermission(session.user.role, "manage_vendors")) { diff --git a/App/pelagia-portal/app/(portal)/admin/vendors/page.tsx b/App/pelagia-portal/app/(portal)/admin/vendors/page.tsx index 7f4bf4f..4c1a1ae 100644 --- a/App/pelagia-portal/app/(portal)/admin/vendors/page.tsx +++ b/App/pelagia-portal/app/(portal)/admin/vendors/page.tsx @@ -4,6 +4,8 @@ import { hasPermission } from "@/lib/permissions"; import { redirect } from "next/navigation"; import Link from "next/link"; import { AddVendorButton, EditVendorButton } from "./vendor-form"; +import { ConfirmDeleteButton } from "@/components/ui/confirm-delete-button"; +import { deleteVendor } from "./actions"; import type { Metadata } from "next"; export const metadata: Metadata = { title: "Vendor Registry" }; @@ -76,17 +78,21 @@ export default async function AdminVendorsPage() { - + + + + ))} diff --git a/App/pelagia-portal/app/(portal)/admin/vendors/vendor-form.tsx b/App/pelagia-portal/app/(portal)/admin/vendors/vendor-form.tsx index d3d40cd..5bb93ac 100644 --- a/App/pelagia-portal/app/(portal)/admin/vendors/vendor-form.tsx +++ b/App/pelagia-portal/app/(portal)/admin/vendors/vendor-form.tsx @@ -7,8 +7,8 @@ import { createVendor, updateVendor, toggleVendorActive } from "./actions"; type VendorRow = { id: string; name: string; vendorId: string | null; address: string | null; - gstin: string | null; contactName: string | null; contactMobile: string | null; - contactEmail: string | null; isActive: boolean; + pincode: string | null; gstin: string | null; contactName: string | null; + contactMobile: string | null; contactEmail: 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"; @@ -23,8 +23,7 @@ function VendorFormFields({ vendor }: { vendor?: VendorRow }) { const [gstin, setGstin] = useState(vendor?.gstin ?? ""); const [name, setName] = useState(vendor?.name ?? ""); const [address, setAddress] = useState(vendor?.address ?? ""); - const [lat, setLat] = useState(""); - const [lng, setLng] = useState(""); + const [pincode, setPincode] = useState(vendor?.pincode ?? ""); // CAPTCHA flow state const [captchaStep, setCaptchaStep] = useState<"idle" | "loading" | "ready" | "verifying">("idle"); @@ -41,7 +40,7 @@ function VendorFormFields({ vendor }: { vendor?: VendorRow }) { const res = await fetch("/api/gst/captcha"); const data = await res.json(); if (data.error) { setGstError(data.error); setCaptchaStep("idle"); return; } - setCaptchaB64(data.captchaB64); + setCaptchaB64(data.captchaBase64); setSessionId(data.sessionId); setCaptchaStep("ready"); } catch { setGstError("Failed to load CAPTCHA"); setCaptchaStep("idle"); } @@ -70,8 +69,7 @@ function VendorFormFields({ vendor }: { vendor?: VendorRow }) { // Fill form fields setName(data.tradeName || data.legalName); setAddress(data.address); - if (data.lat) setLat(String(data.lat)); - if (data.lng) setLng(String(data.lng)); + if (data.pincode) setPincode(data.pincode); setGstSuccess(`✓ ${data.legalName} — ${data.status} since ${data.registrationDate}`); setCaptchaStep("idle"); } catch { setGstError("Lookup failed"); setCaptchaStep("idle"); } @@ -168,15 +166,11 @@ function VendorFormFields({ vendor }: { vendor?: VendorRow }) {