From b287b4c34b79f05e10a6ee6014c5a5731109c8b6 Mon Sep 17 00:00:00 2001
From: AI Assistant
Date: Sat, 7 Mar 2026 21:40:38 +0200
Subject: [PATCH] fix: stable scan display, accurate workflow preview, cladiri
count
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
ROOT CAUSE: The cross-reference between immovable list and GIS layer
produces wildly different matchedCount on each scan (320, 430, 629, 433)
because the eTerra immovable/list API with inscrisCF=-1 returns
inconsistent results across calls. The GIS layer count (505) is stable.
SCAN DISPLAY — now uses only stable numbers:
- Header shows 'Layer GIS: 505 terenuri + X cladiri' (stable ArcGIS count)
- Shows 'Lista imobile: 2.717 (estimat ~2.212 fara geometrie)' using
simple subtraction totalImmovables - remoteGisCount
- Cross-ref matchedCount kept internally for import logic, but NOT shown
as the primary number — eliminates visual instability
- hasNoGeomParcels now uses estimated count (stable)
WORKFLOW PREVIEW — now accurate:
- Step 1: 'Sync GIS — descarca 505 terenuri + X cladiri' (separate counts)
or 'skip (date proaspete in DB)' when fresh
- Step 2 (enrichment): Fixed 'deja imbogatite' bug when DB is empty.
Now correctly computes what WILL be in DB after sync completes:
geoAfterSync + noGeomAfterImport - localDbEnrichedComplete
- Steps 3-4 unchanged
CLADIRI COUNT:
- Scan now also fetches CLADIRI_ACTIVE layer count (lightweight, OBJECTID only)
- New field remoteCladiriCount in NoGeomScanResult
- Displayed in header and workflow step 1
- Non-fatal: if CLADIRI fetch fails, just shows 0
---
src/app/api/eterra/export-bundle/route.ts | 8 +-
.../components/parcel-sync-module.tsx | 103 ++++++++++--------
.../parcel-sync/services/no-geom-sync.ts | 27 ++++-
3 files changed, 88 insertions(+), 50 deletions(-)
diff --git a/src/app/api/eterra/export-bundle/route.ts b/src/app/api/eterra/export-bundle/route.ts
index f0aea75..539234d 100644
--- a/src/app/api/eterra/export-bundle/route.ts
+++ b/src/app/api/eterra/export-bundle/route.ts
@@ -696,8 +696,12 @@ export async function POST(req: Request) {
...(hasNoGeom
? [
` 3. Import fără geometrie: ${fmt(noGeomImported)} noi importate` +
- (noGeomCleaned > 0 ? `, ${fmt(noGeomCleaned)} vechi șterse` : "") +
- (noGeomSkipped > 0 ? `, ${fmt(noGeomSkipped)} filtrate/skip` : ""),
+ (noGeomCleaned > 0
+ ? `, ${fmt(noGeomCleaned)} vechi șterse`
+ : "") +
+ (noGeomSkipped > 0
+ ? `, ${fmt(noGeomSkipped)} filtrate/skip`
+ : ""),
]
: [` 3. Import fără geometrie: dezactivat`]),
` 4. Îmbogățire (CF, prop.): da`,
diff --git a/src/modules/parcel-sync/components/parcel-sync-module.tsx b/src/modules/parcel-sync/components/parcel-sync-module.tsx
index f33827c..940c8ce 100644
--- a/src/modules/parcel-sync/components/parcel-sync-module.tsx
+++ b/src/modules/parcel-sync/components/parcel-sync-module.tsx
@@ -390,6 +390,7 @@ export function ParcelSyncModule() {
totalImmovables: number;
withGeometry: number;
remoteGisCount: number;
+ remoteCladiriCount: number;
noGeomCount: number;
matchedByRef: number;
matchedById: number;
@@ -720,6 +721,7 @@ export function ParcelSyncModule() {
totalImmovables: 0,
withGeometry: 0,
remoteGisCount: 0,
+ remoteCladiriCount: 0,
noGeomCount: 0,
matchedByRef: 0,
matchedById: 0,
@@ -751,6 +753,7 @@ export function ParcelSyncModule() {
totalImmovables: Number(data.totalImmovables ?? 0),
withGeometry: Number(data.withGeometry ?? 0),
remoteGisCount: Number(data.remoteGisCount ?? 0),
+ remoteCladiriCount: Number(data.remoteCladiriCount ?? 0),
noGeomCount: Number(data.noGeomCount ?? 0),
matchedByRef: Number(data.matchedByRef ?? 0),
matchedById: Number(data.matchedById ?? 0),
@@ -2430,7 +2433,13 @@ export function ParcelSyncModule() {
session.connected &&
(() => {
const scanDone = noGeomScan !== null && noGeomScanSiruta === siruta;
- const hasNoGeomParcels = scanDone && noGeomScan.noGeomCount > 0;
+ const estimatedNoGeom = scanDone
+ ? Math.max(
+ 0,
+ noGeomScan.totalImmovables - noGeomScan.remoteGisCount,
+ )
+ : 0;
+ const hasNoGeomParcels = scanDone && estimatedNoGeom > 0;
const scanning = noGeomScanning;
// Still scanning
@@ -2517,9 +2526,7 @@ export function ParcelSyncModule() {
-
- {noGeomScan.localSyncFresh && noGeomScan.localDbWithGeom > 0
- ? "Sync terenuri + clădiri — "
- : "Sync terenuri + clădiri — "}
+ {"Sync GIS — "}
0
? "skip (date proaspete în DB)"
- : `descarcă ${noGeomScan.remoteGisCount.toLocaleString("ro-RO")} features`}
+ : `descarcă ${noGeomScan.remoteGisCount.toLocaleString("ro-RO")} terenuri` +
+ (noGeomScan.remoteCladiriCount > 0
+ ? ` + ${noGeomScan.remoteCladiriCount.toLocaleString("ro-RO")} clădiri`
+ : "")}
{includeNoGeom && (
@@ -2540,7 +2550,6 @@ export function ParcelSyncModule() {
Import parcele fără geometrie —{" "}
{(() => {
- // Only useful items will be imported (quality filter)
const usefulNoGeom =
noGeomScan.qualityBreakdown.useful;
const newNoGeom = Math.max(
@@ -2562,20 +2571,23 @@ export function ParcelSyncModule() {
Îmbogățire CF, proprietari, adrese —{" "}
{(() => {
- const usefulNoGeom = noGeomScan.qualityBreakdown.useful;
- const totalToEnrich =
- noGeomScan.localDbTotal +
- (includeNoGeom
- ? Math.max(
- 0,
- usefulNoGeom - noGeomScan.localDbNoGeom,
- )
- : 0);
- // Use enrichedComplete (not enriched) — stale
- // enrichment (missing PROPRIETARI_VECHI etc.)
- // will be re-processed
+ // What will be in DB after sync + optional no-geom import:
+ // If DB is empty: sync will add remoteGisCount geo features
+ // If DB is fresh: keep localDbTotal
+ const geoAfterSync =
+ noGeomScan.localSyncFresh &&
+ noGeomScan.localDbWithGeom > 0
+ ? noGeomScan.localDbWithGeom
+ : noGeomScan.remoteGisCount;
+ const noGeomAfterImport = includeNoGeom
+ ? Math.max(
+ noGeomScan.localDbNoGeom,
+ noGeomScan.qualityBreakdown.useful,
+ )
+ : noGeomScan.localDbNoGeom;
+ const totalAfter = geoAfterSync + noGeomAfterImport;
const remaining =
- totalToEnrich - noGeomScan.localDbEnrichedComplete;
+ totalAfter - noGeomScan.localDbEnrichedComplete;
return remaining > 0
? `~${remaining.toLocaleString("ro-RO")} de procesat (~${Math.ceil((remaining * 0.25) / 60)} min)`
: "deja îmbogățite";
@@ -2604,38 +2616,37 @@ export function ParcelSyncModule() {
- Din{" "}
-
- {noGeomScan.totalImmovables.toLocaleString("ro-RO")}
- {" "}
- imobile în eTerra:{" "}
-
- {noGeomScan.withGeometry.toLocaleString("ro-RO")}
- {" "}
- cu geometrie,{" "}
-
- {noGeomScan.noGeomCount.toLocaleString("ro-RO")}
- {" "}
- fără geometrie
-
-
Layer GIS:{" "}
-
+
{noGeomScan.remoteGisCount.toLocaleString("ro-RO")}
-
- {" features (se descarcă toate)"}
- {noGeomScan.remoteGisCount !== noGeomScan.withGeometry && (
+ {" "}
+ terenuri
+ {noGeomScan.remoteCladiriCount > 0 && (
<>
- {" · "}
- {noGeomScan.withGeometry.toLocaleString("ro-RO")} potrivite
- cu lista de imobile
- {noGeomScan.matchedByRef > 0 && noGeomScan.matchedById > 0 && (
-
- {" "}({noGeomScan.matchedByRef} cadRef + {noGeomScan.matchedById} ID)
-
- )}
+ {" + "}
+
+ {noGeomScan.remoteCladiriCount.toLocaleString(
+ "ro-RO",
+ )}
+ {" "}
+ clădiri
>
)}
+ {" · "}
+ Lista imobile:{" "}
+
+ {noGeomScan.totalImmovables.toLocaleString("ro-RO")}
+
+ {" (estimat "}
+
+ ~
+ {Math.max(
+ 0,
+ noGeomScan.totalImmovables -
+ noGeomScan.remoteGisCount,
+ ).toLocaleString("ro-RO")}
+
+ {" fără geometrie)"}
Cele fără geometrie există în baza de date eTerra dar
diff --git a/src/modules/parcel-sync/services/no-geom-sync.ts b/src/modules/parcel-sync/services/no-geom-sync.ts
index ac85ab7..1a9a73d 100644
--- a/src/modules/parcel-sync/services/no-geom-sync.ts
+++ b/src/modules/parcel-sync/services/no-geom-sync.ts
@@ -113,10 +113,12 @@ export type NoGeomQuality = {
export type NoGeomScanResult = {
totalImmovables: number;
- /** Immovables that matched a remote GIS feature (have geometry) */
+ /** Immovables that matched a remote GIS feature (cross-ref, may vary) */
withGeometry: number;
- /** Total features in the remote ArcGIS TERENURI_ACTIVE layer */
+ /** Total features in the remote ArcGIS TERENURI_ACTIVE layer (stable) */
remoteGisCount: number;
+ /** Total features in the remote ArcGIS CLADIRI_ACTIVE layer (stable) */
+ remoteCladiriCount: number;
noGeomCount: number;
/** Match quality: how many matched by cadastral ref vs immovable ID */
matchedByRef: number;
@@ -185,6 +187,7 @@ export async function scanNoGeometryParcels(
totalImmovables: 0,
withGeometry: 0,
remoteGisCount: 0,
+ remoteCladiriCount: 0,
noGeomCount: 0,
matchedByRef: 0,
matchedById: 0,
@@ -233,6 +236,25 @@ export async function scanNoGeometryParcels(
pageSize: 2000,
});
+ // 2b. Also fetch CLADIRI_ACTIVE count (lightweight, just OBJECTID)
+ const cladiriLayer = {
+ id: "CLADIRI_ACTIVE",
+ name: "CLADIRI_ACTIVE",
+ endpoint: "aut" as const,
+ whereTemplate: "{{adminField}}={{siruta}} AND IS_ACTIVE=1",
+ };
+ let remoteCladiriCount = 0;
+ try {
+ const cladiriFeatures = await client.fetchAllLayer(cladiriLayer, siruta, {
+ returnGeometry: false,
+ outFields: "OBJECTID",
+ pageSize: 2000,
+ });
+ remoteCladiriCount = cladiriFeatures.length;
+ } catch {
+ // Non-fatal — just won't show clădiri count
+ }
+
const remoteCadRefs = new Set();
const remoteImmIds = new Set();
for (const f of remoteFeatures) {
@@ -392,6 +414,7 @@ export async function scanNoGeometryParcels(
totalImmovables: allImmovables.length,
withGeometry: matchedCount,
remoteGisCount: remoteFeatures.length,
+ remoteCladiriCount,
noGeomCount: noGeomItems.length,
matchedByRef,
matchedById,