fix: dynamic workspaceId for no-geometry scan (was hardcoded 65)
- resolveWorkspacePk chain: explicit param -> GisUat DB -> ArcGIS layer query - UI passes workspacePk from UAT selection to scan API - Fixes: FELEACU (Cluj, workspace!=65) returning 0 immovables - Better messaging: shows X total, Y with geometry, Z without - Shows warning when 0 immovables found (workspace resolution failed)
This commit is contained in:
@@ -272,6 +272,7 @@ export async function POST(req: Request) {
|
|||||||
phase = ph;
|
phase = ph;
|
||||||
updatePhaseProgress(done, tot);
|
updatePhaseProgress(done, tot);
|
||||||
},
|
},
|
||||||
|
// workspacePk will be auto-resolved from DB/ArcGIS inside the service
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ export const dynamic = "force-dynamic";
|
|||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
try {
|
try {
|
||||||
const body = (await req.json()) as { siruta?: string };
|
const body = (await req.json()) as {
|
||||||
|
siruta?: string;
|
||||||
|
workspacePk?: number;
|
||||||
|
};
|
||||||
const siruta = String(body.siruta ?? "").trim();
|
const siruta = String(body.siruta ?? "").trim();
|
||||||
if (!/^\d+$/.test(siruta)) {
|
if (!/^\d+$/.test(siruta)) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
@@ -45,7 +48,9 @@ export async function POST(req: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const client = await EterraClient.create(username, password);
|
const client = await EterraClient.create(username, password);
|
||||||
const result = await scanNoGeometryParcels(client, siruta);
|
const result = await scanNoGeometryParcels(client, siruta, {
|
||||||
|
workspacePk: body.workspacePk ?? null,
|
||||||
|
});
|
||||||
|
|
||||||
return NextResponse.json(result);
|
return NextResponse.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -690,7 +690,10 @@ export function ParcelSyncModule() {
|
|||||||
const res = await fetch("/api/eterra/no-geom-scan", {
|
const res = await fetch("/api/eterra/no-geom-scan", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ siruta: s }),
|
body: JSON.stringify({
|
||||||
|
siruta: s,
|
||||||
|
workspacePk: workspacePk ?? undefined,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const data = (await res.json()) as {
|
const data = (await res.json()) as {
|
||||||
totalImmovables?: number;
|
totalImmovables?: number;
|
||||||
@@ -699,7 +702,7 @@ export function ParcelSyncModule() {
|
|||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
// Show zero result instead of hiding the card entirely
|
console.warn("[no-geom-scan]", data.error);
|
||||||
setNoGeomScan({ totalImmovables: 0, totalInDb: 0, noGeomCount: 0 });
|
setNoGeomScan({ totalImmovables: 0, totalInDb: 0, noGeomCount: 0 });
|
||||||
} else {
|
} else {
|
||||||
setNoGeomScan({
|
setNoGeomScan({
|
||||||
@@ -714,7 +717,7 @@ export function ParcelSyncModule() {
|
|||||||
}
|
}
|
||||||
setNoGeomScanning(false);
|
setNoGeomScanning(false);
|
||||||
},
|
},
|
||||||
[siruta],
|
[siruta, workspacePk],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Auto-scan for no-geometry parcels when UAT is selected + connected
|
// Auto-scan for no-geometry parcels when UAT is selected + connected
|
||||||
@@ -2376,7 +2379,7 @@ export function ParcelSyncModule() {
|
|||||||
<CardContent className="py-3 px-4">
|
<CardContent className="py-3 px-4">
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
<Loader2 className="h-4 w-4 animate-spin text-amber-500" />
|
<Loader2 className="h-4 w-4 animate-spin text-amber-500" />
|
||||||
Se verifică parcele fără geometrie în eTerra…
|
Se scanează lista de imobile din eTerra…
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -2398,19 +2401,23 @@ export function ParcelSyncModule() {
|
|||||||
<AlertTriangle className="h-4 w-4 text-amber-500 shrink-0" />
|
<AlertTriangle className="h-4 w-4 text-amber-500 shrink-0" />
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
|
Din{" "}
|
||||||
|
<span className="font-semibold">
|
||||||
|
{noGeomScan.totalImmovables.toLocaleString("ro-RO")}
|
||||||
|
</span>{" "}
|
||||||
|
imobile în eTerra:{" "}
|
||||||
|
<span className="text-emerald-600 dark:text-emerald-400 font-medium">
|
||||||
|
{noGeomScan.totalInDb.toLocaleString("ro-RO")}
|
||||||
|
</span>{" "}
|
||||||
|
cu geometrie,{" "}
|
||||||
<span className="font-semibold text-amber-600 dark:text-amber-400">
|
<span className="font-semibold text-amber-600 dark:text-amber-400">
|
||||||
{noGeomScan.noGeomCount.toLocaleString("ro-RO")}
|
{noGeomScan.noGeomCount.toLocaleString("ro-RO")}
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
parcele există în eTerra dar{" "}
|
<span className="font-medium">fără geometrie</span>
|
||||||
<span className="font-medium">nu au geometrie</span>{" "}
|
|
||||||
în layerul GIS
|
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[11px] text-muted-foreground mt-0.5">
|
<p className="text-[11px] text-muted-foreground mt-0.5">
|
||||||
Din{" "}
|
Cele fără geometrie există în baza de date eTerra dar
|
||||||
{noGeomScan.totalImmovables.toLocaleString("ro-RO")}{" "}
|
nu au contur desenat în layerul GIS.
|
||||||
imobile total în eTerra,{" "}
|
|
||||||
{noGeomScan.totalInDb.toLocaleString("ro-RO")} sunt
|
|
||||||
deja în baza de date cu geometrie.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -2446,17 +2453,27 @@ export function ParcelSyncModule() {
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Scan done, all parcels have geometry
|
// Scan done, all parcels have geometry (or totalImmovables=0 ⇒ workspace issue)
|
||||||
if (scanDone && !hasNoGeomParcels)
|
if (scanDone && !hasNoGeomParcels)
|
||||||
return (
|
return (
|
||||||
<Card className="border-dashed">
|
<Card className="border-dashed">
|
||||||
<CardContent className="py-2.5 px-4">
|
<CardContent className="py-2.5 px-4">
|
||||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||||
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />
|
{noGeomScan.totalImmovables > 0 ? (
|
||||||
Toate cele{" "}
|
<>
|
||||||
{noGeomScan.totalImmovables.toLocaleString("ro-RO")}{" "}
|
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />
|
||||||
parcele din eTerra au geometrie — nimic de importat
|
Toate cele{" "}
|
||||||
suplimentar.
|
{noGeomScan.totalImmovables.toLocaleString("ro-RO")}{" "}
|
||||||
|
imobile din eTerra au geometrie — nimic de importat
|
||||||
|
suplimentar.
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<AlertTriangle className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
|
Nu s-au găsit imobile în lista eTerra pentru acest
|
||||||
|
UAT. Verifică sesiunea eTerra.
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -22,6 +22,65 @@ import { EterraClient } from "./eterra-client";
|
|||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* Workspace resolution (county → eTerra workspace PK) */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the eTerra workspace PK for a SIRUTA.
|
||||||
|
* Chain: explicit param → GisUat DB row → ArcGIS layer query → null.
|
||||||
|
*/
|
||||||
|
async function resolveWorkspacePk(
|
||||||
|
client: EterraClient,
|
||||||
|
siruta: string,
|
||||||
|
explicitPk?: number | null,
|
||||||
|
): Promise<number | null> {
|
||||||
|
// 1. Explicit param
|
||||||
|
if (explicitPk && Number.isFinite(explicitPk) && explicitPk > 0) {
|
||||||
|
return explicitPk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. DB lookup
|
||||||
|
try {
|
||||||
|
const row = await prisma.gisUat.findUnique({
|
||||||
|
where: { siruta },
|
||||||
|
select: { workspacePk: true },
|
||||||
|
});
|
||||||
|
if (row?.workspacePk && row.workspacePk > 0) return row.workspacePk;
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. ArcGIS layer query — fetch 1 feature from TERENURI_ACTIVE for this siruta
|
||||||
|
try {
|
||||||
|
const features = await client.listLayer(
|
||||||
|
{
|
||||||
|
id: "TERENURI_ACTIVE",
|
||||||
|
name: "TERENURI_ACTIVE",
|
||||||
|
endpoint: "aut",
|
||||||
|
whereTemplate: "{{adminField}}={{siruta}} AND IS_ACTIVE=1",
|
||||||
|
},
|
||||||
|
siruta,
|
||||||
|
{ limit: 1, outFields: "WORKSPACE_ID" },
|
||||||
|
);
|
||||||
|
const wsId = features?.[0]?.attributes?.WORKSPACE_ID;
|
||||||
|
if (wsId != null) {
|
||||||
|
const num = Number(wsId);
|
||||||
|
if (Number.isFinite(num) && num > 0) {
|
||||||
|
// Persist for future lookups
|
||||||
|
prisma.gisUat
|
||||||
|
.update({ where: { siruta }, data: { workspacePk: num } })
|
||||||
|
.catch(() => {});
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const normalizeId = (value: unknown) => {
|
const normalizeId = (value: unknown) => {
|
||||||
if (value === null || value === undefined) return "";
|
if (value === null || value === undefined) return "";
|
||||||
const text = String(value).trim();
|
const text = String(value).trim();
|
||||||
@@ -43,6 +102,8 @@ export type NoGeomScanResult = {
|
|||||||
paperCadNo?: string;
|
paperCadNo?: string;
|
||||||
paperCfNo?: string;
|
paperCfNo?: string;
|
||||||
}>;
|
}>;
|
||||||
|
/** Error message if workspace couldn't be resolved */
|
||||||
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NoGeomSyncResult = {
|
export type NoGeomSyncResult = {
|
||||||
@@ -64,12 +125,26 @@ export async function scanNoGeometryParcels(
|
|||||||
siruta: string,
|
siruta: string,
|
||||||
options?: {
|
options?: {
|
||||||
onProgress?: (page: number, totalPages: number) => void;
|
onProgress?: (page: number, totalPages: number) => void;
|
||||||
|
workspacePk?: number | null;
|
||||||
},
|
},
|
||||||
): Promise<NoGeomScanResult> {
|
): Promise<NoGeomScanResult> {
|
||||||
|
// 0. Resolve workspace
|
||||||
|
const wsPk = await resolveWorkspacePk(client, siruta, options?.workspacePk);
|
||||||
|
if (!wsPk) {
|
||||||
|
return {
|
||||||
|
totalImmovables: 0,
|
||||||
|
totalInDb: 0,
|
||||||
|
noGeomCount: 0,
|
||||||
|
samples: [],
|
||||||
|
error: `Nu s-a putut determina workspace-ul (județul) pentru SIRUTA ${siruta}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Fetch all immovables from eTerra
|
// 1. Fetch all immovables from eTerra
|
||||||
const allImmovables = await fetchAllImmovables(
|
const allImmovables = await fetchAllImmovables(
|
||||||
client,
|
client,
|
||||||
siruta,
|
siruta,
|
||||||
|
wsPk,
|
||||||
options?.onProgress,
|
options?.onProgress,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -131,12 +206,25 @@ export async function syncNoGeometryParcels(
|
|||||||
siruta: string,
|
siruta: string,
|
||||||
options?: {
|
options?: {
|
||||||
onProgress?: (done: number, total: number, phase: string) => void;
|
onProgress?: (done: number, total: number, phase: string) => void;
|
||||||
|
workspacePk?: number | null;
|
||||||
},
|
},
|
||||||
): Promise<NoGeomSyncResult> {
|
): Promise<NoGeomSyncResult> {
|
||||||
try {
|
try {
|
||||||
|
// 0. Resolve workspace
|
||||||
|
const wsPk = await resolveWorkspacePk(client, siruta, options?.workspacePk);
|
||||||
|
if (!wsPk) {
|
||||||
|
return {
|
||||||
|
imported: 0,
|
||||||
|
skipped: 0,
|
||||||
|
errors: 0,
|
||||||
|
status: "error",
|
||||||
|
error: `Nu s-a putut determina workspace-ul pentru SIRUTA ${siruta}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Fetch all immovables
|
// 1. Fetch all immovables
|
||||||
options?.onProgress?.(0, 1, "Descărcare listă imobile (fără geometrie)");
|
options?.onProgress?.(0, 1, "Descărcare listă imobile (fără geometrie)");
|
||||||
const allImmovables = await fetchAllImmovables(client, siruta);
|
const allImmovables = await fetchAllImmovables(client, siruta, wsPk);
|
||||||
|
|
||||||
// 2. Get existing features from DB
|
// 2. Get existing features from DB
|
||||||
const existingFeatures = await prisma.gisFeature.findMany({
|
const existingFeatures = await prisma.gisFeature.findMany({
|
||||||
@@ -251,6 +339,7 @@ export async function syncNoGeometryParcels(
|
|||||||
async function fetchAllImmovables(
|
async function fetchAllImmovables(
|
||||||
client: EterraClient,
|
client: EterraClient,
|
||||||
siruta: string,
|
siruta: string,
|
||||||
|
workspaceId: number,
|
||||||
onProgress?: (page: number, totalPages: number) => void,
|
onProgress?: (page: number, totalPages: number) => void,
|
||||||
): Promise<any[]> {
|
): Promise<any[]> {
|
||||||
const all: any[] = [];
|
const all: any[] = [];
|
||||||
@@ -258,10 +347,6 @@ async function fetchAllImmovables(
|
|||||||
let totalPages = 1;
|
let totalPages = 1;
|
||||||
let includeInscrisCF = true;
|
let includeInscrisCF = true;
|
||||||
|
|
||||||
// The workspace ID for eTerra admin unit queries.
|
|
||||||
// Default to 65 (standard workspace); the eTerra API resolves by adminUnit.
|
|
||||||
const workspaceId = 65;
|
|
||||||
|
|
||||||
while (page < totalPages) {
|
while (page < totalPages) {
|
||||||
const response = await client.fetchImmovableListByAdminUnit(
|
const response = await client.fetchImmovableListByAdminUnit(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
Reference in New Issue
Block a user