React's useDeferredValue lets the input update immediately while
deferring the expensive filter (3186 items) to a lower priority.
Removes the setTimeout debounce in favor of React's built-in
concurrent rendering scheduler. Input stays responsive.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Checkbox on each row (ordered selection → numbered files in ZIP)
- "Descarcă selecție (N)" button appears when items selected
- Tooltip shows position in ZIP: "#1 in ZIP", "#2 in ZIP"
- Select-all checkbox in header
- Tooltips on Descarcă tot + Descarcă selecție buttons
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The nested JSON in ePay HTML breaks [^}]* regex. New approach:
find all CF.stringValues independently, find all solutii independently,
then zip them by position (they appear in same order in HTML).
This correctly maps CF number → document for batch orders.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Re-download: all 7 orders re-downloaded using documentsByCadastral
for correct CF→document matching. No more hardcoded order→parcel map.
Tooltips on all CF extract UI elements:
- No extract: "Comandă extras CF (1 credit)"
- Valid: "Valid până la DD.MM.YYYY" + "Descarcă extras CF"
- Expired: "Expirat pe DD.MM.YYYY" + "Comandă extras CF nou (1 credit)"
- Processing: "Comanda în curs de procesare"
Animations: Loader2 spinner while ordering, transition to green check.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fix: batch order documents are now matched by CF number
from parsed metadateCereri (documentsByCadastral), not by index.
Prevents PDF content mismatch when ePay returns docs in different order.
UAT search: name matches shown first, county-only matches after.
Typing "cluj" now shows CLUJ-NAPOCA before county "Cluj" matches.
Cleaned MinIO + DB of incorrectly mapped old test data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major rewrite:
- Queue now processes batches: addToCart×N → saveMetadata×N → ONE
submitOrder → poll → download ALL documents → store in MinIO
- Removed unique constraint on orderId (shared across batch items)
- Added step=download to test endpoint: downloads PDFs from 5
existing orders (9685480-9685484) and stores in MinIO
- step=order now uses enqueueBatch for 2 test parcels (61904, 309952)
as ONE ePay order instead of separate orders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
EditCartSubmit returns 200 (not redirect) — Angular does client-side
redirect to CheckoutConfirmationSubmit.action. Added this step to
actually confirm the order before looking for the orderId.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
submitOrder now captures the previous orderId BEFORE submitting, then
searches for a NEW orderId that isn't in the knownOrderIds set. Queue
passes knownOrderIds between sequential items to prevent duplicate
orderId assignment (unique constraint violation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Angular uses doPostAsFormMultipart — the save endpoint requires
multipart/form-data, not application/x-www-form-urlencoded.
Install form-data package and restore multipart upload.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
form-data package not installed — crashes at runtime. Use
URLSearchParams instead (the Angular source uses doPostAsForm
which is form-urlencoded, so this should work too).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Zero discovery calls needed! ePay internal county IDs are identical
to eTerra WORKSPACE_IDs (CLUJ=127, ALBA=10, etc.) and ePay UAT IDs
are SIRUTA codes (Cluj-Napoca=54975, Florești=57706). Queue now
uses workspacePk + siruta directly from GisUat DB.
Flow: AddToCart → saveMetadata → EditCartSubmit → Poll+Download.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SearchEstate might need active cart and/or different headers.
Add X-Requested-With: XMLHttpRequest, make uatId optional, log raw
response (type, length, sample), and add-to-cart before searching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OpenAM requires an initial GET to set session cookies before the
credentials POST. Without it, POST returns 500 and only sets
AMAuthCookie (intermediate) instead of iPlanetDirectoryPro (final SSO).
Then navigate to ePay goto URL to establish JSESSIONID.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OpenAM goto URL is http://epay.ancpi.ro:80 (HTTP, not HTTPS).
Try multiple URL variants to establish JSESSIONID after OpenAM auth.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ANCPI's OpenAM uses AMAuthCookie instead of iPlanetDirectoryPro.
Accept AMAuthCookie, iPlanetDirectoryPro, or JSESSIONID as valid
session indicators. Navigate to ePay after auth to establish JSESSIONID.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Let axios follow all redirects (maxRedirects=10) so cookie jar captures
iPlanetDirectoryPro from the chain. Explicitly navigate to ePay after
login to ensure JSESSIONID. Log all cookies for debugging. Last resort:
verify login by checking if credit info is visible on ePay page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OpenAM requires module=SelfRegistration and goto= redirect URL.
Also handle 302 manually to capture iPlanetDirectoryPro cookie,
then follow redirect to ePay for JSESSIONID.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Foundation (Phase 1):
- CfExtract Prisma model with version tracking, expiry, MinIO path
- epay-types.ts: all ePay API response types
- epay-counties.ts: WORKSPACE_ID → ePay county index mapping (42 counties)
- epay-storage.ts: MinIO helpers (bucket, naming, upload, download)
- docker-compose.yml: ANCPI env vars
ePay Client (Phase 2):
- epay-client.ts: full HTTP client (login, credits, cart, search estate,
submit order, poll status, download PDF) with cookie jar + auto-relogin
- epay-session-store.ts: separate session from eTerra
Queue + API (Phase 3):
- epay-queue.ts: sequential FIFO queue (global cart constraint),
10-step workflow per order with DB status updates at each step
- POST /api/ancpi/session: connect/disconnect
- POST /api/ancpi/order: create single or bulk orders
- GET /api/ancpi/orders: list all extracts
- GET /api/ancpi/credits: live credit balance
- GET /api/ancpi/download: stream PDF from MinIO
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: PATCH endpoint created a new EterraClient which tried
to re-login with expired session → 401. Now county refresh runs
immediately after successful login in the session route, using the
same authenticated client (fire-and-forget). Component reloads UAT
data 5s after connection to pick up fresh county info.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Restore SIRUTA code display in parentheses next to UAT name
- PATCH response now includes debug samples (sampleUat keys, county
raw data) visible in browser console for diagnosing matching issues
- POST endpoint now supports resync (upsert mode, safe to call again)
- Client logs full PATCH result to browser console for debugging
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PATCH /api/eterra/uats: handle nested responses (unwrapArray), try
multiple field names (extractName/extractCode), log sample UAT for
debugging, match by code first then by name
- GET /api/eterra/uats: include localFeatures count per SIRUTA via
GisFeature groupBy query
- Dropdown: show green badge with local feature count, county with dash
- Add SKILLS.md for ParcelSync/eTerra/GIS module context
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PATCH /api/eterra/uats fetches counties from eTerra nomenclature and
LIMITE_UAT layer, then batch-updates GisUat records with county name
and workspacePk. Auto-triggers on first eTerra connection when county
data is missing. Helps distinguish same-name UATs in different counties.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New eterra-health.ts service: pings eTerra periodically (3min),
detects maintenance (503, keywords), tracks consecutive failures
- New /api/eterra/health endpoint for explicit health queries
- Session route blocks login when eTerra is in maintenance (503 response)
- GET /api/eterra/session now includes eterraAvailable/eterraMaintenance
- ConnectionPill shows amber 'Mentenanță' state with AlertTriangle icon
instead of confusing red error when eTerra is down
- Auto-connect skips when maintenance detected, retries when back online
- 30s session poll auto-detects recovery and re-enables auto-connect
- New API route /api/eterra/uat-dashboard with SQL aggregates
(area stats, intravilan/extravilan split, land use, top owners, fun facts)
- CSS-only dashboard component: KPI cards, donut ring, bar charts
- Dashboard button on each UAT card in DB tab, expands panel below
- New search mode toggle: Nr. Cadastral / Proprietar
- Owner search queries:
1. Local DB first (enrichment PROPRIETARI/PROPRIETARI_VECHI ILIKE)
2. eTerra API fallback (tries personName/titularName/ownerName filter keys)
- DB search works offline (no eTerra connection needed) — uses enriched data
- New API route: POST /api/eterra/search-owner
- New eterra-client method: searchImmovableByOwnerName()
- Owner results show source badge (DB local / eTerra online)
- Results can be added to saved list and exported as CSV
- Relaxed search tab guard: only requires UAT selection (not eTerra connection)
- Cadastral search still requires eTerra connection (shows hint when offline)
- DEFAULT_TIMEOUT_MS: 40_000 -> 120_000 (1000 features with full geometry
from Feleacu regularly exceed 40s on the eTerra server)
- Add timeoutMs option to syncLayer() for caller override
- syncLayer now passes timeoutMs through to EterraClient.create()
Fixes 'timeout of 40000ms exceeded' on TERENURI_ACTIVE sync.
- DEFAULT_PAGE_SIZE: 2000 -> 1000 (matches eTerra maxRecordCount, avoids
requesting more than the server supports on first try)
- PAGE_SIZE_FALLBACKS: [500, 200] (removed 1000 since it's now the default)
- Add retry-once logic for 'Error performing query operation':
Wait 2s and retry same page before falling to smaller sizes.
These errors are often transient server-side timeouts.
- Longer delay (1s vs 0.5s) between page size fallback attempts
Fixes Feleacu (7951 features) background sync failure.
- 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.