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:
AI Assistant
2026-03-07 17:32:49 +02:00
parent 40b9522e12
commit b01ea9fc37
4 changed files with 49 additions and 28 deletions
@@ -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),
};