diff --git a/src/app/api/eterra/debug-fields/route.ts b/src/app/api/eterra/debug-fields/route.ts new file mode 100644 index 0000000..f2ea284 --- /dev/null +++ b/src/app/api/eterra/debug-fields/route.ts @@ -0,0 +1,187 @@ +import { NextResponse } from "next/server"; +import { EterraClient } from "@/modules/parcel-sync/services/eterra-client"; +import { PrismaClient } from "@prisma/client"; + +const prisma = new PrismaClient(); + +/** + * GET /api/eterra/debug-fields?siruta=161829&cadRef=77102 + * + * Diagnostic endpoint — shows all available fields from eTerra + local DB + * for a specific parcel and its buildings. + */ +export async function GET(request: Request) { + const url = new URL(request.url); + const siruta = url.searchParams.get("siruta") ?? "161829"; + const cadRef = url.searchParams.get("cadRef") ?? "77102"; + + const username = process.env.ETERRA_USERNAME; + const password = process.env.ETERRA_PASSWORD; + if (!username || !password) { + return NextResponse.json({ error: "ETERRA creds missing" }, { status: 500 }); + } + + const result: Record = { + query: { siruta, cadRef }, + timestamp: new Date().toISOString(), + }; + + try { + const client = await EterraClient.create(username, password); + + // 1. GIS layer: TERENURI_ACTIVE — raw attributes + const terenuri = await client.listLayerByWhere( + { id: "TERENURI_ACTIVE", name: "TERENURI_ACTIVE", endpoint: "aut" }, + `ADMIN_UNIT_ID=${siruta} AND IS_ACTIVE=1 AND NATIONAL_CADASTRAL_REFERENCE='${cadRef}'`, + { limit: 1, outFields: "*" }, + ); + const parcelAttrs = terenuri[0]?.attributes ?? null; + result.gis_parcela = { + found: !!parcelAttrs, + fields: parcelAttrs + ? Object.entries(parcelAttrs) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([k, v]) => ({ field: k, value: v, type: typeof v })) + : [], + }; + + // 2. GIS layer: CLADIRI_ACTIVE — buildings on this parcel + const cladiri = await client.listLayerByWhere( + { id: "CLADIRI_ACTIVE", name: "CLADIRI_ACTIVE", endpoint: "aut" }, + `ADMIN_UNIT_ID=${siruta} AND IS_ACTIVE=1 AND NATIONAL_CADASTRAL_REFERENCE LIKE '${cadRef}-%'`, + { limit: 20, outFields: "*" }, + ); + result.gis_cladiri = { + count: cladiri.length, + buildings: cladiri.map((c) => { + const a = c.attributes; + return { + cadastralRef: a.NATIONAL_CADASTRAL_REFERENCE, + fields: Object.entries(a) + .sort(([x], [y]) => x.localeCompare(y)) + .filter(([, v]) => v != null && v !== "" && v !== 0) + .map(([k, v]) => ({ field: k, value: v, type: typeof v })), + }; + }), + }; + + // 3. Immovable details (enrichment source) + const immId = parcelAttrs?.IMMOVABLE_ID; + const wsId = parcelAttrs?.WORKSPACE_ID; + if (immId && wsId) { + try { + const details = await client.fetchImmovableParcelDetails( + wsId as string | number, + immId as string | number, + ); + result.immovable_parcel_details = { + count: details.length, + items: details, + }; + } catch (e) { + result.immovable_parcel_details = { + error: e instanceof Error ? e.message : String(e), + }; + } + + // 4. Immovable list entry (address source) + try { + const listResponse = await client.fetchImmovableListByAdminUnit( + wsId as number, + siruta, + 0, + 5, + true, + ); + const items = (listResponse?.content ?? []) as Record[]; + // Find our specific immovable + const match = items.find( + (item) => String(item.immovablePk) === String(immId) || + String(item.identifierDetails ?? "").includes(cadRef), + ); + result.immovable_list_entry = { + totalInUat: listResponse?.totalElements ?? "?", + matchFound: !!match, + entry: match ?? null, + note: "Acest obiect contine campul immovableAddresses cu adresa completa", + }; + } catch (e) { + result.immovable_list_entry = { + error: e instanceof Error ? e.message : String(e), + }; + } + + // 5. Documentation data (owner source) + try { + const docResponse = await client.fetchDocumentationData( + wsId as number, + [String(immId)], + ); + const immovables = docResponse?.immovables ?? []; + const regs = docResponse?.partTwoRegs ?? []; + result.documentation_data = { + immovablesCount: immovables.length, + immovables: immovables.slice(0, 3), + registrationsCount: regs.length, + registrations: regs.slice(0, 10), + note: "partTwoRegs contine proprietarii (nodeType=P, nodeStatus=-1=radiat)", + }; + } catch (e) { + result.documentation_data = { + error: e instanceof Error ? e.message : String(e), + }; + } + } + + // 6. Local DB data (what we have stored) + const dbParcel = await prisma.gisFeature.findFirst({ + where: { layerId: "TERENURI_ACTIVE", siruta, cadastralRef: cadRef }, + select: { + objectId: true, + cadastralRef: true, + areaValue: true, + isActive: true, + enrichment: true, + enrichedAt: true, + geometrySource: true, + }, + }); + const dbBuildings = await prisma.gisFeature.findMany({ + where: { + layerId: "CLADIRI_ACTIVE", + siruta, + cadastralRef: { startsWith: `${cadRef}-` }, + }, + select: { + objectId: true, + cadastralRef: true, + areaValue: true, + attributes: true, + }, + }); + result.local_db = { + parcel: dbParcel + ? { + objectId: dbParcel.objectId, + cadastralRef: dbParcel.cadastralRef, + areaValue: dbParcel.areaValue, + enrichedAt: dbParcel.enrichedAt, + geometrySource: dbParcel.geometrySource, + enrichment: dbParcel.enrichment, + } + : null, + buildings: dbBuildings.map((b) => ({ + objectId: b.objectId, + cadastralRef: b.cadastralRef, + areaValue: b.areaValue, + is_legal: (b.attributes as Record)?.IS_LEGAL, + })), + }; + } catch (e) { + result.error = e instanceof Error ? e.message : String(e); + } + + return NextResponse.json(result, { + headers: { "Content-Type": "application/json; charset=utf-8" }, + }); +}