From 041bfd41387ab5bc2c2ae26a6a922b0a8b048803 Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Sun, 8 Mar 2026 02:37:39 +0200 Subject: [PATCH] fix(parcel-sync): fix ArcGIS 1000 server cap pagination + scan improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - eterra-client: detect server maxRecordCount cap in fetchAllLayerByWhere When server returns exactly 1000 (or other round cap) but we asked for 2000, recognize this as a server limit, adjust pageSize, and CONTINUE paginating. Previously: 1000 < 2000 -> break (lost all data beyond page 1). - no-geom-sync: count layers first, pass total to fetchAllLayer Belt-and-suspenders: even if cap detection misses, known total prevents early termination. Also use pageSize 1000 to match typical server cap. Clădiri count uses countLayer instead of fetching all OBJECTIDs. - UI: add include-no-geom checkbox in background sync section Users can toggle it independently of scan status. Shows '(scanare in curs)' hint when scan is still running. --- .../components/parcel-sync-module.tsx | 24 +++++++++++ .../parcel-sync/services/eterra-client.ts | 24 ++++++++++- .../parcel-sync/services/no-geom-sync.ts | 43 +++++++++++++------ 3 files changed, 77 insertions(+), 14 deletions(-) diff --git a/src/modules/parcel-sync/components/parcel-sync-module.tsx b/src/modules/parcel-sync/components/parcel-sync-module.tsx index f94c2be..152ef82 100644 --- a/src/modules/parcel-sync/components/parcel-sync-module.tsx +++ b/src/modules/parcel-sync/components/parcel-sync-module.tsx @@ -3068,6 +3068,30 @@ export function ParcelSyncModule() { + {/* Include no-geom toggle (works independently of scan) */} + {session.connected && ( + + )} + {/* Row 2: Background sync buttons */} {session.connected && (
diff --git a/src/modules/parcel-sync/services/eterra-client.ts b/src/modules/parcel-sync/services/eterra-client.ts index 70d7d01..96387f4 100644 --- a/src/modules/parcel-sync/services/eterra-client.ts +++ b/src/modules/parcel-sync/services/eterra-client.ts @@ -339,6 +339,11 @@ export class EterraClient { let offset = 0; const all: EsriFeature[] = []; + // ArcGIS servers have a maxRecordCount (typically 1000). + // If we request 2000 but get exactly 1000, we hit the server cap. + // Track this so we continue paginating instead of stopping. + let serverMaxRecordCount: number | null = null; + while (true) { const params = new URLSearchParams(); params.set("f", "json"); @@ -375,11 +380,28 @@ export class EterraClient { break; } + // Detect server maxRecordCount cap: + // If we asked for more than we got AND the result is a round number + // (1000, 2000), the server likely capped us. Adjust pageSize to match. + if ( + serverMaxRecordCount === null && + features.length < pageSize && + features.length > 0 && + features.length % 500 === 0 // round cap: 500, 1000, 1500, 2000... + ) { + serverMaxRecordCount = features.length; + pageSize = serverMaxRecordCount; + // Don't break — this is a full page at server's cap, continue + } + all.push(...features); offset += features.length; if (onProgress) onProgress(all.length, total); if (total && all.length >= total) break; - if (features.length < pageSize) { + + // End of data: fewer features than the effective page size + const effectivePageSize = serverMaxRecordCount ?? pageSize; + if (features.length < effectivePageSize) { const nextSize = PAGE_SIZE_FALLBACKS.find((s) => s < pageSize); if (total && all.length < total && nextSize) { pageSize = nextSize; diff --git a/src/modules/parcel-sync/services/no-geom-sync.ts b/src/modules/parcel-sync/services/no-geom-sync.ts index d895e66..37020af 100644 --- a/src/modules/parcel-sync/services/no-geom-sync.ts +++ b/src/modules/parcel-sync/services/no-geom-sync.ts @@ -223,36 +223,53 @@ export async function scanNoGeometryParcels( // 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. + // Count first so pagination knows the total and doesn't stop early. const terenuriLayer = { id: "TERENURI_ACTIVE", name: "TERENURI_ACTIVE", endpoint: "aut" as const, whereTemplate: "{{adminField}}={{siruta}} AND IS_ACTIVE=1", }; + const [terenuriCount, cladiriCount] = await Promise.all([ + client.countLayer(terenuriLayer, siruta).catch(() => 0), + client + .countLayer( + { + id: "CLADIRI_ACTIVE", + name: "CLADIRI_ACTIVE", + endpoint: "aut" as const, + whereTemplate: "{{adminField}}={{siruta}} AND IS_ACTIVE=1", + }, + siruta, + ) + .catch(() => 0), + ]); const remoteFeatures = await client.fetchAllLayer(terenuriLayer, siruta, { returnGeometry: false, outFields: "OBJECTID,NATIONAL_CADASTRAL_REFERENCE,IMMOVABLE_ID", - pageSize: 2000, + pageSize: 1000, + total: terenuriCount > 0 ? terenuriCount : undefined, }); - // 2b. Also fetch CLADIRI_ACTIVE count (lightweight, just OBJECTID) + // 2b. Also fetch CLADIRI_ACTIVE features (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 + let remoteCladiriCount = cladiriCount; + if (remoteCladiriCount === 0) { + try { + const cladiriFeatures = await client.fetchAllLayer(cladiriLayer, siruta, { + returnGeometry: false, + outFields: "OBJECTID", + pageSize: 1000, + }); + remoteCladiriCount = cladiriFeatures.length; + } catch { + // Non-fatal — just won't show clădiri count + } } const remoteCadRefs = new Set();