feat(parcel-sync): county-aware UAT autocomplete with workspace resolution
- New /api/eterra/uats endpoint fetches all counties + UATs from eTerra, caches server-side for 1 hour, returns enriched data with county name and workspacePk for each UAT - When eTerra is connected, auto-fetches enriched UAT list (replaces static uat.json fallback) shows 'FELEACU (57582), CLUJ' format - UAT autocomplete now searches both UAT name and county name - Selected UAT stores workspacePk in state, passes it directly to /api/eterra/search eliminates slow per-search county resolution - Search route accepts optional workspacePk, falls back to resolveWorkspace() - Dropdown shows UAT name, SIRUTA code, and county prominently - Increased autocomplete results from 8 to 12 items
This commit is contained in:
@@ -7,9 +7,10 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
type Body = {
|
||||
siruta?: string;
|
||||
search?: string; // cadastral number(s), comma or newline separated
|
||||
search?: string; // cadastral number(s), comma or newline separated
|
||||
username?: string;
|
||||
password?: string;
|
||||
workspacePk?: number; // county workspace PK — if provided, skips resolution
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -81,7 +82,11 @@ function formatAddress(item?: any) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function normalizeIntravilan(values: string[]) {
|
||||
const normalized = values
|
||||
.map((v) => String(v ?? "").trim().toLowerCase())
|
||||
.map((v) =>
|
||||
String(v ?? "")
|
||||
.trim()
|
||||
.toLowerCase(),
|
||||
)
|
||||
.filter(Boolean);
|
||||
const unique = new Set(normalized);
|
||||
if (!unique.size) return "";
|
||||
@@ -182,8 +187,11 @@ export async function POST(req: Request) {
|
||||
|
||||
const client = await EterraClient.create(username, password);
|
||||
|
||||
// Resolve workspace (county) for this SIRUTA
|
||||
const workspaceId = await resolveWorkspace(client, siruta);
|
||||
// Use provided workspacePk — or fall back to resolution
|
||||
let workspaceId = body.workspacePk ?? null;
|
||||
if (!workspaceId || !Number.isFinite(workspaceId)) {
|
||||
workspaceId = await resolveWorkspace(client, siruta);
|
||||
}
|
||||
if (!workspaceId) {
|
||||
return NextResponse.json(
|
||||
{ error: "Nu s-a putut determina județul pentru UAT-ul selectat." },
|
||||
@@ -228,9 +236,7 @@ export async function POST(req: Request) {
|
||||
// Basic data from immovable list
|
||||
let nrCF = String(item?.paperLbNo ?? item?.paperCadNo ?? "");
|
||||
let nrCFVechi = "";
|
||||
let nrTopo = String(
|
||||
item?.topNo ?? item?.paperCadNo ?? "",
|
||||
);
|
||||
let nrTopo = String(item?.topNo ?? item?.paperCadNo ?? "");
|
||||
let addressText = formatAddress(item);
|
||||
let proprietari = "";
|
||||
let solicitant = "";
|
||||
@@ -298,16 +304,16 @@ export async function POST(req: Request) {
|
||||
// Pick most recent application
|
||||
const chosen =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(apps ?? []).filter((a: any) => a?.dataCerere)
|
||||
(apps ?? [])
|
||||
.filter((a: any) => a?.dataCerere)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.sort((a: any, b: any) =>
|
||||
(b.dataCerere ?? 0) - (a.dataCerere ?? 0),
|
||||
.sort(
|
||||
(a: any, b: any) =>
|
||||
(b.dataCerere ?? 0) - (a.dataCerere ?? 0),
|
||||
)[0] ?? apps?.[0];
|
||||
|
||||
if (chosen) {
|
||||
solicitant = String(
|
||||
chosen.solicitant ?? chosen.deponent ?? "",
|
||||
);
|
||||
solicitant = String(chosen.solicitant ?? chosen.deponent ?? "");
|
||||
const appId = chosen.applicationId;
|
||||
if (appId) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
|
||||
import { getSessionCredentials } from "@/modules/parcel-sync/services/session-store";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Server-side cache */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
type EnrichedUat = {
|
||||
siruta: string;
|
||||
name: string;
|
||||
county: string;
|
||||
workspacePk: number;
|
||||
};
|
||||
|
||||
const globalRef = globalThis as {
|
||||
__eterraEnrichedUats?: { data: EnrichedUat[]; ts: number };
|
||||
};
|
||||
|
||||
/** Cache TTL — 1 hour (county/UAT data rarely changes) */
|
||||
const CACHE_TTL_MS = 60 * 60 * 1000;
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* GET /api/eterra/uats */
|
||||
/* */
|
||||
/* Returns all UATs enriched with county name + workspacePk. */
|
||||
/* Cached server-side for 1 hour. */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Return from cache if fresh
|
||||
const cached = globalRef.__eterraEnrichedUats;
|
||||
if (cached && Date.now() - cached.ts < CACHE_TTL_MS) {
|
||||
return NextResponse.json({ uats: cached.data, cached: true });
|
||||
}
|
||||
|
||||
// Need eTerra credentials
|
||||
const session = getSessionCredentials();
|
||||
const username = String(
|
||||
session?.username || process.env.ETERRA_USERNAME || "",
|
||||
).trim();
|
||||
const password = String(
|
||||
session?.password || process.env.ETERRA_PASSWORD || "",
|
||||
).trim();
|
||||
|
||||
if (!username || !password) {
|
||||
return NextResponse.json(
|
||||
{ error: "Conectează-te la eTerra mai întâi.", uats: [] },
|
||||
{ status: 401 },
|
||||
);
|
||||
}
|
||||
|
||||
const client = await EterraClient.create(username, password);
|
||||
|
||||
// Fetch all counties
|
||||
const counties = await client.fetchCounties();
|
||||
const enriched: EnrichedUat[] = [];
|
||||
|
||||
for (const county of counties) {
|
||||
const countyPk = county?.nomenPk ?? county?.pk ?? county?.id;
|
||||
const countyName = String(county?.name ?? "").trim();
|
||||
if (!countyPk || !countyName) continue;
|
||||
|
||||
try {
|
||||
const uats = await client.fetchAdminUnitsByCounty(countyPk);
|
||||
for (const uat of uats) {
|
||||
const uatPk = String(uat?.nomenPk ?? uat?.pk ?? "");
|
||||
const uatName = String(uat?.name ?? "").trim();
|
||||
if (!uatPk || !uatName) continue;
|
||||
|
||||
enriched.push({
|
||||
siruta: uatPk,
|
||||
name: uatName,
|
||||
county: countyName,
|
||||
workspacePk: Number(countyPk),
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Skip county if UAT fetch fails
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Update cache
|
||||
globalRef.__eterraEnrichedUats = { data: enriched, ts: Date.now() };
|
||||
|
||||
// Also populate the workspace cache used by search route
|
||||
const wsGlobal = globalThis as {
|
||||
__eterraWorkspaceCache?: Map<string, number>;
|
||||
};
|
||||
if (!wsGlobal.__eterraWorkspaceCache) {
|
||||
wsGlobal.__eterraWorkspaceCache = new Map();
|
||||
}
|
||||
for (const u of enriched) {
|
||||
wsGlobal.__eterraWorkspaceCache.set(u.siruta, u.workspacePk);
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
uats: enriched,
|
||||
cached: false,
|
||||
total: enriched.length,
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Eroare server";
|
||||
return NextResponse.json({ error: message, uats: [] }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user