diff --git a/src/app/api/geoportal/enrich/route.ts b/src/app/api/geoportal/enrich/route.ts index e3a0780..a14cc9f 100644 --- a/src/app/api/geoportal/enrich/route.ts +++ b/src/app/api/geoportal/enrich/route.ts @@ -1,71 +1,143 @@ /** * POST /api/geoportal/enrich * - * Enriches parcels for a given SIRUTA. Skips already-enriched features. - * Tries: 1) Active eTerra session, 2) Env credentials, 3) Returns clear error. - * Enrichment data is PERSISTED in GisFeature.enrichment column. + * Quick single-parcel enrichment via eTerra immovable API. + * Persists result in GisFeature.enrichment column. * - * Body: { siruta: string } + * Body: { featureId: string } (GisFeature UUID) */ import { NextResponse } from "next/server"; +import { prisma } from "@/core/storage/prisma"; import { EterraClient } from "@/modules/parcel-sync/services/eterra-client"; -import { enrichFeatures } from "@/modules/parcel-sync/services/enrich-service"; import { getSessionCredentials } from "@/modules/parcel-sync/services/session-store"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; +async function getClient(): Promise { + const session = getSessionCredentials(); + const username = session?.username || process.env.ETERRA_USERNAME || ""; + const password = session?.password || process.env.ETERRA_PASSWORD || ""; + if (!username || !password) { + throw new Error("Credentiale eTerra indisponibile. Deschide eTerra Parcele si logheaza-te."); + } + return EterraClient.create(username, password); +} + export async function POST(req: Request) { try { - const body = (await req.json()) as { siruta?: string }; - const siruta = String(body.siruta ?? "").trim(); + const body = (await req.json()) as { featureId?: string; siruta?: string; objectId?: number }; - if (!siruta) { - return NextResponse.json({ error: "SIRUTA obligatoriu" }, { status: 400 }); + // Find the feature + let feature; + if (body.featureId) { + feature = await prisma.gisFeature.findUnique({ + where: { id: body.featureId }, + select: { id: true, objectId: true, siruta: true, cadastralRef: true, attributes: true, areaValue: true, enrichment: true, enrichedAt: true }, + }); + } else if (body.siruta && body.objectId) { + feature = await prisma.gisFeature.findFirst({ + where: { siruta: body.siruta, objectId: body.objectId }, + select: { id: true, objectId: true, siruta: true, cadastralRef: true, attributes: true, areaValue: true, enrichment: true, enrichedAt: true }, + }); } - // Try multiple credential sources - const session = getSessionCredentials(); - const username = session?.username || process.env.ETERRA_USERNAME || ""; - const password = session?.password || process.env.ETERRA_PASSWORD || ""; - - if (!username || !password) { - return NextResponse.json( - { error: "Credentiale eTerra indisponibile. Deschide eTerra Parcele si logheaza-te mai intai, apoi revino aici." }, - { status: 401 } - ); + if (!feature) { + return NextResponse.json({ error: "Parcela negasita in baza de date" }, { status: 404 }); } - let client: EterraClient; - try { - client = await EterraClient.create(username, password); - } catch (loginErr) { - const loginMsg = loginErr instanceof Error ? loginErr.message : "Login esuat"; - return NextResponse.json( - { error: `Login eTerra esuat: ${loginMsg}. Verifica credentialele in eTerra Parcele.` }, - { status: 401 } - ); + // Already enriched recently (< 7 days)? + if (feature.enrichedAt) { + const age = Date.now() - new Date(feature.enrichedAt).getTime(); + if (age < 7 * 24 * 3600 * 1000 && feature.enrichment) { + return NextResponse.json({ + status: "ok", + message: "Datele sunt deja actualizate", + enrichment: feature.enrichment, + }); + } } - const result = await enrichFeatures(client, siruta); + const client = await getClient(); + + // Get workspace PK from GisUat + const uat = await prisma.gisUat.findUnique({ + where: { siruta: feature.siruta }, + select: { workspacePk: true }, + }); + + if (!uat?.workspacePk) { + return NextResponse.json({ error: "UAT fara workspace. Sincronizeaza UAT-ul din eTerra Parcele." }, { status: 400 }); + } + + // Fetch immovable data from eTerra for this specific parcel + const attrs = (feature.attributes ?? {}) as Record; + const cadRef = feature.cadastralRef ?? String(attrs.NATIONAL_CADASTRAL_REFERENCE ?? ""); + + // Search by cadastral reference + const searchResult = await client.fetchImmovableListByAdminUnit( + uat.workspacePk, + Number(attrs.ADMIN_UNIT_ID ?? 0), + 0, + 100, + ); + + // Find matching parcel in results + const items = (searchResult as { content?: Array> })?.content ?? []; + const match = items.find((item: Record) => { + const itemCad = String(item.nationalCadastralReference ?? item.cadastralNo ?? ""); + return itemCad === cadRef || String(item.objectId) === String(feature.objectId); + }); + + // Build enrichment from available data + const enrichment: Record = { + NR_CAD: cadRef, + NR_CF: match ? String(match.cfNumber ?? match.cfNo ?? "") : "", + NR_CF_VECHI: "", + NR_TOPO: "", + ADRESA: "", + PROPRIETARI: match ? formatOwners(match) : "", + PROPRIETARI_VECHI: "", + SUPRAFATA_2D: feature.areaValue ?? "", + SUPRAFATA_R: feature.areaValue ? Math.round(feature.areaValue) : "", + SOLICITANT: "", + INTRAVILAN: match ? (match.isIntravillan ? "DA" : "Nu") : "", + CATEGORIE_FOLOSINTA: match ? String(match.landUseCategory ?? "") : "", + HAS_BUILDING: 0, + BUILD_LEGAL: 0, + }; + + // Persist + await prisma.gisFeature.update({ + where: { id: feature.id }, + data: { + enrichment: enrichment as object, + enrichedAt: new Date(), + }, + }); return NextResponse.json({ - status: result.status, - enrichedCount: result.enrichedCount, - buildingCrossRefs: result.buildingCrossRefs, - message: result.enrichedCount > 0 - ? `${result.enrichedCount} parcele imbogatite cu succes` - : "Toate parcelele au deja date de enrichment", + status: "ok", + message: "Parcela imbogatita cu succes", + enrichment, }); } catch (error) { - const msg = error instanceof Error ? error.message : "Eroare la enrichment"; - // Provide actionable error messages + const msg = error instanceof Error ? error.message : "Eroare"; if (msg.includes("timeout") || msg.includes("ETIMEDOUT")) { - return NextResponse.json({ error: "eTerra nu raspunde (timeout). Incearca mai tarziu." }, { status: 504 }); + return NextResponse.json({ error: "eTerra nu raspunde. Incearca mai tarziu." }, { status: 504 }); } if (msg.includes("maintenance") || msg.includes("Mentenan")) { - return NextResponse.json({ error: "eTerra este in mentenanta. Incearca mai tarziu." }, { status: 503 }); + return NextResponse.json({ error: "eTerra in mentenanta." }, { status: 503 }); } return NextResponse.json({ error: msg }, { status: 500 }); } } + +function formatOwners(item: Record): string { + const owners = item.owners ?? item.proprietari; + if (Array.isArray(owners)) { + return owners.map((o: Record) => String(o.name ?? o.fullName ?? "")).filter(Boolean).join("; "); + } + if (typeof owners === "string") return owners; + return ""; +} diff --git a/src/modules/geoportal/components/feature-info-panel.tsx b/src/modules/geoportal/components/feature-info-panel.tsx index d2c9143..67c3edb 100644 --- a/src/modules/geoportal/components/feature-info-panel.tsx +++ b/src/modules/geoportal/components/feature-info-panel.tsx @@ -65,14 +65,15 @@ export function FeatureInfoPanel({ feature, onClose }: FeatureInfoPanelProps) { const siruta = String(feature.properties.siruta ?? detail?.siruta ?? ""); const handleEnrich = async () => { - if (!siruta) return; + if (!detail?.id && !siruta) return; setEnriching(true); setEnrichMsg(""); try { + const objectId = feature.properties.object_id ?? feature.properties.objectId; const resp = await fetch("/api/geoportal/enrich", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ siruta }), + body: JSON.stringify(detail?.id ? { featureId: detail.id } : { siruta, objectId: Number(objectId) }), }); const d = await resp.json(); if (resp.ok) {