fix(parcel-sync): scan uses remote GIS layer instead of empty local DB
- scanNoGeometryParcels now fetches TERENURI_ACTIVE features from remote ArcGIS (lightweight, no geometry) to cross-reference with eTerra immovable list - Cross-references by both NATIONAL_CADASTRAL_REFERENCE and IMMOVABLE_ID - Works correctly regardless of whether user has synced to local DB - Renamed totalInDb -> withGeometry in NoGeomScanResult, UI, and API - Extended fetchAllLayer() to forward outFields/returnGeometry options
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
* POST /api/eterra/no-geom-scan
|
||||
*
|
||||
* Scans eTerra immovable list for a UAT and counts how many parcels
|
||||
* exist in the eTerra database but have no geometry in the GIS layer
|
||||
* (i.e., they are NOT in the local TERENURI_ACTIVE DB).
|
||||
* exist in the eTerra database but have no geometry in the remote
|
||||
* ArcGIS GIS layer (TERENURI_ACTIVE). Cross-references remotely.
|
||||
*
|
||||
* Body: { siruta: string }
|
||||
* Returns: { totalImmovables, totalInDb, noGeomCount, samples }
|
||||
* Returns: { totalImmovables, withGeometry, noGeomCount, samples }
|
||||
*
|
||||
* Requires active eTerra session.
|
||||
*/
|
||||
|
||||
@@ -388,7 +388,7 @@ export function ParcelSyncModule() {
|
||||
const [noGeomScanning, setNoGeomScanning] = useState(false);
|
||||
const [noGeomScan, setNoGeomScan] = useState<{
|
||||
totalImmovables: number;
|
||||
totalInDb: number;
|
||||
withGeometry: number;
|
||||
noGeomCount: number;
|
||||
} | null>(null);
|
||||
const [noGeomScanSiruta, setNoGeomScanSiruta] = useState(""); // siruta for which scan was done
|
||||
@@ -697,23 +697,23 @@ export function ParcelSyncModule() {
|
||||
});
|
||||
const data = (await res.json()) as {
|
||||
totalImmovables?: number;
|
||||
totalInDb?: number;
|
||||
withGeometry?: number;
|
||||
noGeomCount?: number;
|
||||
error?: string;
|
||||
};
|
||||
if (data.error) {
|
||||
console.warn("[no-geom-scan]", data.error);
|
||||
setNoGeomScan({ totalImmovables: 0, totalInDb: 0, noGeomCount: 0 });
|
||||
setNoGeomScan({ totalImmovables: 0, withGeometry: 0, noGeomCount: 0 });
|
||||
} else {
|
||||
setNoGeomScan({
|
||||
totalImmovables: data.totalImmovables ?? 0,
|
||||
totalInDb: data.totalInDb ?? 0,
|
||||
withGeometry: data.withGeometry ?? 0,
|
||||
noGeomCount: data.noGeomCount ?? 0,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Show zero result on network error
|
||||
setNoGeomScan({ totalImmovables: 0, totalInDb: 0, noGeomCount: 0 });
|
||||
setNoGeomScan({ totalImmovables: 0, withGeometry: 0, noGeomCount: 0 });
|
||||
}
|
||||
setNoGeomScanning(false);
|
||||
},
|
||||
@@ -2407,7 +2407,7 @@ export function ParcelSyncModule() {
|
||||
</span>{" "}
|
||||
imobile în eTerra:{" "}
|
||||
<span className="text-emerald-600 dark:text-emerald-400 font-medium">
|
||||
{noGeomScan.totalInDb.toLocaleString("ro-RO")}
|
||||
{noGeomScan.withGeometry.toLocaleString("ro-RO")}
|
||||
</span>{" "}
|
||||
cu geometrie,{" "}
|
||||
<span className="font-semibold text-amber-600 dark:text-amber-400">
|
||||
|
||||
@@ -309,6 +309,8 @@ export class EterraClient {
|
||||
total?: number;
|
||||
onProgress?: ProgressCallback;
|
||||
delayMs?: number;
|
||||
outFields?: string;
|
||||
returnGeometry?: boolean;
|
||||
},
|
||||
) {
|
||||
const where = await this.buildWhere(layer, siruta);
|
||||
|
||||
@@ -93,7 +93,8 @@ const normalizeCadRef = (value: unknown) =>
|
||||
|
||||
export type NoGeomScanResult = {
|
||||
totalImmovables: number;
|
||||
totalInDb: number;
|
||||
/** Features present in the remote ArcGIS TERENURI_ACTIVE layer (have geometry) */
|
||||
withGeometry: number;
|
||||
noGeomCount: number;
|
||||
/** Sample of immovable identifiers without geometry */
|
||||
samples: Array<{
|
||||
@@ -116,7 +117,11 @@ export type NoGeomSyncResult = {
|
||||
|
||||
/**
|
||||
* Scan: count how many eTerra immovables for this UAT have no geometry
|
||||
* in the local DB.
|
||||
* in the remote GIS layer (TERENURI_ACTIVE).
|
||||
*
|
||||
* Cross-references the eTerra immovable list against the REMOTE ArcGIS
|
||||
* layer (lightweight fetch, no geometry download). This works correctly
|
||||
* regardless of whether the user has synced to local DB yet.
|
||||
*
|
||||
* This does NOT write anything — it's a read-only operation.
|
||||
*/
|
||||
@@ -133,14 +138,14 @@ export async function scanNoGeometryParcels(
|
||||
if (!wsPk) {
|
||||
return {
|
||||
totalImmovables: 0,
|
||||
totalInDb: 0,
|
||||
withGeometry: 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 immovable list API
|
||||
const allImmovables = await fetchAllImmovables(
|
||||
client,
|
||||
siruta,
|
||||
@@ -148,20 +153,33 @@ export async function scanNoGeometryParcels(
|
||||
options?.onProgress,
|
||||
);
|
||||
|
||||
// 2. Get all existing cadastralRefs in DB for TERENURI_ACTIVE
|
||||
const existingFeatures = await prisma.gisFeature.findMany({
|
||||
where: { layerId: "TERENURI_ACTIVE", siruta },
|
||||
select: { cadastralRef: true, objectId: true },
|
||||
// 2. Fetch remote GIS cadastral refs (lightweight — no geometry)
|
||||
// This is the source of truth for "has geometry" regardless of local DB state.
|
||||
// ~4 pages for 8k features with outFields only = very fast.
|
||||
const terenuriLayer = {
|
||||
id: "TERENURI_ACTIVE",
|
||||
name: "TERENURI_ACTIVE",
|
||||
endpoint: "aut" as const,
|
||||
whereTemplate: "{{adminField}}={{siruta}} AND IS_ACTIVE=1",
|
||||
};
|
||||
const remoteFeatures = await client.fetchAllLayer(terenuriLayer, siruta, {
|
||||
returnGeometry: false,
|
||||
outFields: "OBJECTID,NATIONAL_CADASTRAL_REFERENCE,IMMOVABLE_ID",
|
||||
pageSize: 2000,
|
||||
});
|
||||
|
||||
const existingCadRefs = new Set<string>();
|
||||
const existingObjIds = new Set<number>();
|
||||
for (const f of existingFeatures) {
|
||||
if (f.cadastralRef) existingCadRefs.add(normalizeCadRef(f.cadastralRef));
|
||||
existingObjIds.add(f.objectId);
|
||||
const remoteCadRefs = new Set<string>();
|
||||
const remoteImmIds = new Set<string>();
|
||||
for (const f of remoteFeatures) {
|
||||
const cadRef = normalizeCadRef(
|
||||
f.attributes?.NATIONAL_CADASTRAL_REFERENCE ?? "",
|
||||
);
|
||||
if (cadRef) remoteCadRefs.add(cadRef);
|
||||
const immId = normalizeId(f.attributes?.IMMOVABLE_ID);
|
||||
if (immId) remoteImmIds.add(immId);
|
||||
}
|
||||
|
||||
// 3. Find immovables not in DB
|
||||
// 3. Cross-reference: immovables NOT in remote GIS = no geometry
|
||||
const noGeomItems: Array<{
|
||||
immovablePk: number;
|
||||
identifierDetails: string;
|
||||
@@ -172,12 +190,13 @@ export async function scanNoGeometryParcels(
|
||||
for (const item of allImmovables) {
|
||||
const cadRef = normalizeCadRef(item.identifierDetails ?? "");
|
||||
const immPk = Number(item.immovablePk ?? 0);
|
||||
const immId = normalizeId(item.immovablePk);
|
||||
|
||||
// Already in DB by cadastral ref?
|
||||
if (cadRef && existingCadRefs.has(cadRef)) continue;
|
||||
// Present in remote GIS layer by cadastral ref? → has geometry
|
||||
if (cadRef && remoteCadRefs.has(cadRef)) continue;
|
||||
|
||||
// Already in DB by negative objectId?
|
||||
if (immPk > 0 && existingObjIds.has(-immPk)) continue;
|
||||
// Present in remote GIS layer by IMMOVABLE_ID? → has geometry
|
||||
if (immId && remoteImmIds.has(immId)) continue;
|
||||
|
||||
noGeomItems.push({
|
||||
immovablePk: immPk,
|
||||
@@ -189,7 +208,7 @@ export async function scanNoGeometryParcels(
|
||||
|
||||
return {
|
||||
totalImmovables: allImmovables.length,
|
||||
totalInDb: existingFeatures.length,
|
||||
withGeometry: remoteFeatures.length,
|
||||
noGeomCount: noGeomItems.length,
|
||||
samples: noGeomItems.slice(0, 20),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user