diff --git a/src/app/api/geoportal/enrich/route.ts b/src/app/api/geoportal/enrich/route.ts index 10433e6..a636f58 100644 --- a/src/app/api/geoportal/enrich/route.ts +++ b/src/app/api/geoportal/enrich/route.ts @@ -1,98 +1,163 @@ /** * POST /api/geoportal/enrich * - * Triggers enrichment for a SIRUTA (runs in background, returns immediately). - * Uses the proven enrichFeatures() from ParcelSync which properly fetches - * owners, CF, categories from eTerra. + * Quick per-parcel enrichment via eTerra searchImmovableByIdentifier. + * Searches by cadastral number, extracts owner/CF/category data. + * Persists in GisFeature.enrichment column. * - * Body: { siruta: string, featureId?: string } + * Body: { featureId: string } or { siruta: string, objectId: number } */ 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"; -// Track running enrichments to avoid duplicates -const runningEnrichments = new Set(); - export async function POST(req: Request) { try { - const body = (await req.json()) as { siruta?: string; featureId?: string; objectId?: number }; + const body = (await req.json()) as { featureId?: string; siruta?: string; objectId?: number }; - // Resolve siruta from featureId if needed - let siruta = String(body.siruta ?? "").trim(); - if (!siruta && body.featureId) { - const f = await prisma.gisFeature.findUnique({ + // Find feature + let feature; + if (body.featureId) { + feature = await prisma.gisFeature.findUnique({ where: { id: body.featureId }, - select: { siruta: true }, + select: { id: true, objectId: true, siruta: true, cadastralRef: true, attributes: true, areaValue: 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 }, }); - siruta = f?.siruta ?? ""; } - if (!siruta) { - return NextResponse.json({ error: "SIRUTA obligatoriu" }, { status: 400 }); - } - - // Already running for this SIRUTA? - if (runningEnrichments.has(siruta)) { - return NextResponse.json({ - status: "running", - message: "Enrichment deja in curs pentru acest UAT. Datele vor aparea in cateva minute.", - }); + if (!feature) { + return NextResponse.json({ error: "Parcela negasita in DB" }, { status: 404 }); } // Get credentials 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." }, - { status: 401 } - ); + return NextResponse.json({ error: "Credentiale eTerra lipsa. Logheaza-te in eTerra Parcele." }, { status: 401 }); } - // If featureId provided, clear bad enrichment so it gets re-processed - if (body.featureId) { - await prisma.gisFeature.update({ - where: { id: body.featureId }, - data: { enrichment: null, enrichedAt: null }, - }).catch(() => {}); - } - - // Start enrichment in background (don't await) - runningEnrichments.add(siruta); - EterraClient.create(username, password) - .then((client) => enrichFeatures(client, siruta)) - .then((result) => { - console.log(`[Enrich] ${siruta}: ${result.enrichedCount} parcele enriched`); - }) - .catch((err) => { - console.error(`[Enrich] ${siruta}: ${err instanceof Error ? err.message : err}`); - }) - .finally(() => { - runningEnrichments.delete(siruta); - }); - - return NextResponse.json({ - status: "started", - message: "Enrichment pornit in background. Datele vor aparea in 1-3 minute. Reincarca parcela dupa.", + // Get workspace + admin unit from GisUat and feature attributes + 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 }); + } + + const attrs = (feature.attributes ?? {}) as Record; + const cadRef = feature.cadastralRef ?? String(attrs.NATIONAL_CADASTRAL_REFERENCE ?? ""); + const adminUnitId = Number(attrs.ADMIN_UNIT_ID ?? 0); + + if (!cadRef) { + return NextResponse.json({ error: "Parcela fara numar cadastral" }, { status: 400 }); + } + + // Search eTerra by cadastral number + const client = await EterraClient.create(username, password); + const result = await client.searchImmovableByIdentifier( + uat.workspacePk, + adminUnitId, + cadRef, + ); + + const items = (result?.content ?? result?.data ?? []) as Array>; + const match = items[0]; // First result should be the match + + if (!match) { + // No match in eTerra - save what we know from GIS + const enrichment = buildBasicEnrichment(cadRef, feature.areaValue); + await prisma.gisFeature.update({ + where: { id: feature.id }, + data: { enrichment: enrichment as object, enrichedAt: new Date() }, + }); + return NextResponse.json({ status: "ok", message: "Date de baza salvate (parcela negasita in registrul eTerra)", enrichment }); + } + + // Build enrichment from eTerra data + const enrichment = { + NR_CAD: cadRef, + NR_CF: String(match.cfNumber ?? match.landBookNo ?? ""), + NR_CF_VECHI: String(match.oldCfNumber ?? match.oldLandBookNo ?? ""), + NR_TOPO: String(match.topographicNumber ?? match.topoNo ?? ""), + ADRESA: formatAddress(match), + PROPRIETARI: formatOwners(match), + PROPRIETARI_VECHI: "", + SUPRAFATA_2D: match.measuredArea ?? feature.areaValue ?? "", + SUPRAFATA_R: match.legalArea ?? (feature.areaValue ? Math.round(feature.areaValue) : ""), + SOLICITANT: "", + INTRAVILAN: match.isIntravillan === true ? "DA" : match.isIntravillan === false ? "Nu" : "", + CATEGORIE_FOLOSINTA: String(match.landUseCategory ?? match.categoryOfUse ?? ""), + HAS_BUILDING: match.hasBuilding ? 1 : 0, + BUILD_LEGAL: match.hasBuilding && match.buildingAuthorized ? 1 : 0, + }; + + await prisma.gisFeature.update({ + where: { id: feature.id }, + data: { enrichment: enrichment as object, enrichedAt: new Date() }, + }); + + return NextResponse.json({ status: "ok", message: "Parcela imbogatita cu succes", enrichment }); } catch (error) { const msg = error instanceof Error ? error.message : "Eroare"; + if (msg.includes("timeout")) return NextResponse.json({ error: "eTerra timeout. Incearca mai tarziu." }, { status: 504 }); + if (msg.includes("maintenance") || msg.includes("Mentenan")) return NextResponse.json({ error: "eTerra in mentenanta." }, { status: 503 }); return NextResponse.json({ error: msg }, { status: 500 }); } } -/** GET — check enrichment status for a SIRUTA */ -export async function GET(req: Request) { - const url = new URL(req.url); - const siruta = url.searchParams.get("siruta") ?? ""; - if (!siruta) return NextResponse.json({ running: false }); - return NextResponse.json({ running: runningEnrichments.has(siruta) }); +function buildBasicEnrichment(cadRef: string, area: number | null) { + return { + NR_CAD: cadRef, NR_CF: "", NR_CF_VECHI: "", NR_TOPO: "", ADRESA: "", + PROPRIETARI: "", PROPRIETARI_VECHI: "", + SUPRAFATA_2D: area ?? "", SUPRAFATA_R: area ? Math.round(area) : "", + SOLICITANT: "", INTRAVILAN: "", CATEGORIE_FOLOSINTA: "", + HAS_BUILDING: 0, BUILD_LEGAL: 0, + }; +} + +function formatOwners(item: Record): string { + // Try various eTerra response formats + const owners = item.owners ?? item.titulars ?? item.proprietari; + if (Array.isArray(owners)) { + return owners + .map((o: Record) => { + const name = String(o.fullName ?? o.name ?? o.titularName ?? ""); + const share = o.shareNumerator && o.shareDenominator + ? ` (${o.shareNumerator}/${o.shareDenominator})` + : ""; + return name + share; + }) + .filter(Boolean) + .join("; "); + } + if (typeof owners === "string") return owners; + // Try titularName directly on the item + if (item.titularName) return String(item.titularName); + return ""; +} + +function formatAddress(item: Record): string { + const addr = item.address ?? item.immovableAddresses; + if (Array.isArray(addr) && addr.length > 0) { + const a = (addr[0] as Record)?.address as Record | undefined; + if (a) { + const parts: string[] = []; + if (a.addressDescription) parts.push(String(a.addressDescription)); + if (a.street) parts.push(`Str. ${a.street}`); + if (a.buildingNo) parts.push(`Nr. ${a.buildingNo}`); + return parts.join(", "); + } + } + if (typeof item.addressDescription === "string") return item.addressDescription; + return ""; }