Commit Graph

142 Commits

Author SHA1 Message Date
AI Assistant d367b5f736 fix(ancpi): add SearchEstate debug logging, try without uatId, add cart first
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>
2026-03-23 01:27:30 +02:00
AI Assistant f92fcfd86b fix(ancpi): test SearchEstate with various uatId values 2026-03-23 01:18:58 +02:00
AI Assistant b61cd71044 feat(ancpi): add test endpoint for step-by-step ePay verification
GET /api/ancpi/test?step=login|uats|search|order
Temporary diagnostic route to test ePay integration before building UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 00:20:11 +02:00
AI Assistant 3921852eb5 feat(parcel-sync): add ANCPI ePay CF extract ordering backend
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>
2026-03-23 00:09:52 +02:00
AI Assistant f6781ab851 feat(parcel-sync): store UAT geometries from LIMITE_UAT in local DB
- Add geometry (Json), areaValue (Float), lastUpdatedDtm (String) to
  GisUat model for local caching of UAT boundaries
- County refresh now fetches LIMITE_UAT with returnGeometry=true and
  stores EsriGeometry rings per UAT in EPSG:3844
- Uses LAST_UPDATED_DTM from eTerra for future incremental sync
- Skips geometry fetch if >50% already have geometry stored

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 23:14:52 +02:00
AI Assistant 7b10f1e533 fix(parcel-sync): use verified WORKSPACE_ID → county mapping from eTerra
LIMITE_UAT provides SIRUTA + WORKSPACE_ID for all 3186 UATs across 42
workspaces. eTerra nomenclature APIs all return 404, and immovable list
returns empty for small communes. Use verified workspace→county mapping
derived from eTerra data (cross-referenced sample UATs + DB confirmations).
Logs unknown workspaces if eTerra ever adds new ones.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:52:46 +02:00
AI Assistant 61a44525bf fix(parcel-sync): resolve county names 100% from eTerra, zero hardcoding
LIMITE_UAT gives SIRUTA + WORKSPACE_ID for all 3186 UATs. For each of
the 42 unique workspaces, fetch 1 immovable via fetchImmovableListBy
AdminUnit — the response includes workspace.name = county name.
No static mappings, no nomenclature endpoints (they 404).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:39:02 +02:00
AI Assistant ce49b9e536 fix(parcel-sync): resolve counties via LIMITE_UAT WORKSPACE_ID + known UAT seats
eTerra nomenclature endpoints (fetchCounties, fetchNomenByPk) return
404. New approach: LIMITE_UAT gives ADMIN_UNIT_ID + WORKSPACE_ID for
all 3186 UATs across 42 workspaces. Use a static mapping of county
seat SIRUTAs to identify which workspace belongs to which county.
Logs unresolved workspaces for debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:36:29 +02:00
AI Assistant f9a2f6f82a fix(parcel-sync): use LIMITE_UAT + fetchNomenByPk for county data
fetchCounties() returns 404 — endpoint doesn't exist on eTerra.
New approach: query LIMITE_UAT layer for all features (no geometry)
to discover SIRUTA + WORKSPACE_ID per UAT, then resolve each unique
WORKSPACE_ID to county name via fetchNomenByPk(). Fallback: resolve
county for UATs that already have workspacePk in DB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:29:56 +02:00
AI Assistant 899b5c4cf7 fix(parcel-sync): populate county data during login, not via PATCH
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>
2026-03-22 22:23:46 +02:00
AI Assistant 379e7e4d3f feat(parcel-sync): add diagnostic endpoint for county debugging
GET /api/eterra/uats/test-counties returns raw eTerra nomenclature
response structure — shows exact field names and data format for
fetchCounties() and fetchAdminUnitsByCounty(). Temporary diagnostic
to fix county population issue.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 22:04:37 +02:00
AI Assistant 8fa89a7675 fix(parcel-sync): restore SIRUTA in dropdown, add county debug output
- 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>
2026-03-22 21:34:29 +02:00
AI Assistant 431291c410 fix(parcel-sync): robust county population + local feature count in dropdown
- 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>
2026-03-22 21:18:22 +02:00
AI Assistant 79750b2a4a fix(parcel-sync): use eTerra nomenclature API for county population
LIMITE_UAT layer lacks WORKSPACE_ID field, so the previous approach
failed silently. Now uses fetchCounties() + fetchAdminUnitsByCounty()
nomenclature API: Phase 1 fills county for UATs with existing
workspacePk, Phase 2 enumerates counties and matches by name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 20:56:43 +02:00
AI Assistant 86c39473a5 feat(parcel-sync): show county in UAT search dropdown via eTerra data
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>
2026-03-22 20:46:13 +02:00
AI Assistant a3ab539197 feat: add read-only /api/projects endpoint for external tools
Returns project tags from tag-manager (category=project).
Supports search (?q=), company filter (?company=), single by ID (?id=).
Same Bearer token auth as address-book API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:08:59 +02:00
AI Assistant aab38d909c feat: add dedicated /api/address-book REST endpoint for inter-service access
Bearer token auth (ADDRESSBOOK_API_KEY) for external tools like avizare.
Supports GET (list/search/filter/by-id), POST (create), PUT (update), DELETE.
Middleware exclusion so it bypasses NextAuth session requirement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 22:33:00 +02:00
AI Assistant 87ac81c6c9 fix: encode unicode filenames in Content-Disposition headers
Filenames with Romanian characters (Ș, Ț, etc.) caused ByteString errors.
Also pass original filename through to extreme mode response.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 20:50:37 +02:00
AI Assistant 4b5d3bd498 fix(pdf-compress): bypass middleware body buffering for upload routes
Next.js middleware buffers the entire request body (10MB default limit)
before the route handler runs. middlewareClientMaxBodySize experimental
flag doesn't work reliably with standalone output.

Solution: exclude api/compress-pdf from middleware matcher so the body
streams directly to the route handler. Auth check moved to a shared
helper (auth-check.ts) called at the start of each route.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 19:54:28 +02:00
AI Assistant 003a2821fd fix(pdf-compress): zero-memory multipart parsing + streamed response
Previous approach loaded entire raw body (287MB) into RAM via readFile,
then extracted PDF (another 287MB), then read output (287MB) = ~860MB peak.
Docker container OOM killed silently -> 500.

New approach:
- parse-upload.ts: scan raw file on disk using 64KB buffer reads (findInFile),
  then stream-copy just the PDF portion. Peak memory: ~64KB.
- extreme/route.ts: stream qpdf output directly from disk via Readable.toWeb.
  Never loads result into memory.

Total peak memory: ~64KB + qpdf process memory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 19:44:06 +02:00
AI Assistant f032cf0e4a fix(pdf-compress): replace busboy with manual multipart parsing
Busboy's file event never fires in Next.js Turbopack despite the
stream being read correctly (CJS/ESM interop issue). Replace with
manual boundary parsing: stream body to disk chunk-by-chunk, then
extract the PDF part using simple boundary scanning. Tested working
with 1MB+ payloads — streams to disk so memory usage stays constant
regardless of file size.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 18:47:37 +02:00
AI Assistant 5a7de39f6a fix(pdf-compress): stream large uploads via busboy instead of arrayBuffer
req.arrayBuffer() fails with 502 on files >100MB because it tries to
buffer the entire body in memory before the route handler runs.

New approach: busboy streams the multipart body directly to a temp file
on disk — never buffers the whole request in memory. Works for any size.

Shared helper: parse-upload.ts (busboy streaming, 500MB limit, fields).
Both local (qpdf) and cloud (iLovePDF) routes refactored to use it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 18:07:16 +02:00
AI Assistant f5deccd8ea refactor(pdf-compress): replace Ghostscript with qpdf + iLovePDF API
Ghostscript -sDEVICE=pdfwrite fundamentally re-encodes fonts, causing
garbled text regardless of parameters. This cannot be fixed.

New approach:
- Local: qpdf-only lossless structural optimization (5-30% savings,
  zero corruption risk — fonts and images completely untouched)
- Cloud: iLovePDF API integration (auth → start → upload → process →
  download) with 3 levels (recommended/extreme/low), proper image
  recompression without font corruption

Frontend: 3 modes (cloud recommended, cloud extreme, local lossless).
Docker: ILOVEPDF_PUBLIC_KEY env var added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:50:46 +02:00
AI Assistant d75fcb1d1c fix(pdf-compress): remove /screen preset that destroys font encoding
The -dPDFSETTINGS=/screen GS preset overwrites font encoding tables,
producing garbled text in output PDFs. Replace with individual params
that ONLY compress images while preserving fonts intact.

Three quality levels via GS (no Stirling dependency):
- extreme: 100 DPI, QFactor 1.2 (~quality 35)
- high: 150 DPI, QFactor 0.76 (~quality 50)
- balanced: 200 DPI, QFactor 0.4 (~quality 70)

Route all UI modes through the GS endpoint with level parameter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:19:42 +02:00
AI Assistant 9e73dc3cb9 fix(pdf-compress): use arrayBuffer() instead of formData() for large files
formData() fails with "Failed to parse body as FormData" on large PDFs
in Next.js route handlers. Switch to req.arrayBuffer() which reliably
reads the full body, then manually extract the PDF from multipart.

Extreme mode: arrayBuffer + multipart extraction + GS + qpdf pipeline.
Stirling mode: arrayBuffer forwarding to Stirling with proper headers.

Revert serverActions.bodySizeLimit (doesn't apply to route handlers).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 16:32:05 +02:00
AI Assistant 194ddf0849 fix(pdf-compress): fix broken multipart parsing + add body size limit
Extreme mode: replace fragile manual multipart boundary parsing (which
extracted only a fraction of large files, producing empty PDFs) with
standard req.formData(). Add GS output validation + stderr capture.

Stirling mode: parse formData first then build fresh FormData for
Stirling instead of raw body passthrough (which lost data on large
files). Add 5min timeout + original/compressed size headers.

next.config: add 250MB body size limit for server actions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 16:18:34 +02:00
AI Assistant 34024404a5 fix: prevent deleting registry entries that would create sequence gaps
Only the last entry in a company+year sequence can be deleted. Trying
to delete an earlier number (e.g. #2 when #3 exists) returns a 409
error with a Romanian message explaining the restriction.

Also routes UI deletes through the API (like create/update) so they
get proper audit logging and sequence recalculation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:38:51 +02:00
AI Assistant 5cb438ef67 fix: JSONB space-after-colon in all registry LIKE/regex patterns
PostgreSQL JSONB value::text serializes JSON with spaces after colons
("number": "B-2026-00001") but all LIKE patterns searched for the
no-space format ("number":"B-2026-00001"), causing zero matches and
every new entry getting sequence #1.

Fixed in allocateSequenceNumber, recalculateSequence, and debug-sequences.
Added PATCH handler to migrate old-format entries (BTG/SDT/USW/GRP)
to new single-letter format (B/S/U/G).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:22:33 +02:00
AI Assistant 4b61d07ffd debug: add raw value snippet to debug-sequences for regex diagnostics
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 22:04:25 +02:00
AI Assistant 39d64b033e fix: replace \d with [0-9] in all PostgreSQL regex patterns
PostgreSQL POSIX regex on the server does not support \d shorthand,
causing SUBSTRING to return NULL and every entry to get sequence 1.

Replaced all \d with [0-9] in:
- allocateSequenceNumber (new + old format queries)
- recalculateSequence (new + old format queries)
- debug-sequences endpoint (GET + POST queries)

Also added samples field to debug GET for raw number diagnostics,
and POST now handles old-format entries (BTG→B mapping) with
ON CONFLICT GREATEST for proper counter merging.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:59:04 +02:00
AI Assistant 0f555c55ee feat: simplify registry number format to B-2026-00001
New format: single-letter prefix + year + 5-digit sequence.
No direction code (IN/OUT) in the number — shown via arrow icon.
Sequence is shared across directions within the same company+year.

Changes:
- REGISTRY_COMPANY_PREFIX: BTG→B, USW→U, SDT→S, GRP→G
- OLD_COMPANY_PREFIX map for backward compat with existing entries
- allocateSequenceNumber: searches both old and new format entries
  to find the actual max sequence (backward compat)
- recalculateSequence: same dual-format search
- parseRegistryNumber: supports 3 formats (current, v1, legacy)
- isNewFormat: updated regex for B-2026-00001
- CompactNumber: already used single-letter badges, just updated comment
- debug-sequences endpoint: updated for new format
- Notification test data: updated to new format
- RegistrySequence.type: now "SEQ" (shared) instead of "IN"/"OUT"

After deploy: POST /api/registratura/debug-sequences to clean up
old counters, then recreate test entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:49:35 +02:00
AI Assistant eb39024548 fix: debug-sequences endpoint regex escaping with $queryRawUnsafe
Prisma tagged template literals were mangling regex backslashes.
Switch to $queryRawUnsafe for the complex regex queries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:38:40 +02:00
AI Assistant 46de088423 fix: use only actual entries as source of truth for registry numbers
The previous fix still used MAX(actualMax, counterVal) which meant a
stale counter (from entries deleted before the fix was deployed) would
override the actual entry count. Changed to use ONLY actualMax + 1.

The RegistrySequence counter is now just a cache that gets synced —
it never overrides the actual entries count.

Also added /api/registratura/debug-sequences endpoint:
- GET: shows all counters vs actual entry max (for diagnostics)
- POST: resets all counters to match actual entries (one-time fix)

After deploy, call POST /api/registratura/debug-sequences to reset
the stale counters, then delete the BTG-2026-OUT-00004 entry and
recreate it — it will get 00001.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:20:58 +02:00
AI Assistant dbed7105b7 fix: bulletproof registry number allocation using actual DB entries as source of truth
The old allocateSequenceNumber blindly incremented a counter in
RegistrySequence, which drifted out of sync when entries were deleted
or moved between companies — producing wrong numbers (e.g., #6 for
the first entry of a company).

New approach:
- Uses pg_advisory_xact_lock inside a Prisma interactive transaction
  to serialize concurrent allocations
- Always queries the actual MAX sequence from KeyValueStore entries
  (the source of truth) before allocating the next number
- Takes MAX(actual entries, counter) + 1 so the counter can never
  produce a stale/duplicate number
- Upserts the counter to the new value for consistency
- Also adds recalculateSequence to DELETE handler so the counter
  stays in sync after entry deletions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 21:11:52 +02:00
AI Assistant 2739c6af6f fix: extreme PDF compression producing empty output on large files
The multipart body parser was using the first \r\n\r\n as the file
content start, but this could miss the actual file part. Now properly
iterates through parts to find the one with filename= header, and
uses lastIndexOf for the closing boundary to avoid false matches
inside PDF binary data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:48:36 +02:00
AI Assistant d7bd1a7f5d feat: external status monitor for registratura (Primaria Cluj-Napoca)
- Add ExternalStatusTracking types + ExternalDocStatus semantic states
- Authority catalog with Primaria Cluj-Napoca (POST scraper + HTML parser)
- Status check service: batch + single entry, change detection via hash
- API routes: cron-triggered batch (/api/registratura/status-check) +
  user-triggered single (/api/registratura/status-check/single)
- Add "status-change" notification type with instant email on change
- Table badge: Radio icon color-coded by status (amber/blue/green/red)
- Detail panel: full monitoring section with status, history, manual check
- Auto-detection: prompt when recipient matches known authority
- Activation dialog: configure petitioner name + confirm registration data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:42:21 +02:00
AI Assistant 1c51236c31 fix: registry number re-allocation on company change + extreme PDF large file support
- Registratura: re-allocate number when company/direction changes on update,
  recalculate old company's sequence counter from actual entries
- Extreme PDF: stream body to temp file instead of req.formData() to support large files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 14:05:13 +02:00
AI Assistant ed504bd1de fix: stream PDF body to Stirling to avoid FormData parse failure on large files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 13:48:05 +02:00
AI Assistant 9d58f1b705 feat: add test digest mode (?test=true) + group company sees all entries
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 08:47:17 +02:00
AI Assistant 974d06fff8 feat: add email notification system (Brevo SMTP + N8N daily digest)
- Add core notification service: types, email-service (nodemailer/Brevo SMTP), notification-service (digest builder, preference CRUD, HTML renderer)
- Add API routes: POST /api/notifications/digest (N8N cron, Bearer auth), GET/PUT /api/notifications/preferences (session auth)
- Add NotificationPreferences UI component (Bell button + dialog with per-type toggles) in Registratura toolbar
- Add 7 Brevo SMTP env vars to docker-compose.yml
- Update CLAUDE.md, ROADMAP.md, DATA-MODEL.md, SYSTEM-ARCHITECTURE.md, CONFIGURATION.md, DOCKER-DEPLOYMENT.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 01:12:36 +02:00
AI Assistant 6786ac07d1 fix(registratura): remove intern direction — only intrat/iesit are valid
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 08:01:29 +02:00
AI Assistant a0dd35a066 feat(registratura): atomic numbering, reserved slots, audit trail, API endpoints + theme toggle animation
Registratura module:
- Atomic sequence numbering (BTG-2026-IN-00125 format) via PostgreSQL upsert
- Reserved monthly slots (2/company/month) for late registrations
- Append-only audit trail with diff tracking
- REST API: /api/registratura (CRUD), /api/registratura/reserved, /api/registratura/audit
- Auth: NextAuth session + Bearer API key support
- New "intern" direction type with UI support (form, filters, table, detail panel)
- Prisma models: RegistrySequence, RegistryAudit

Theme toggle:
- SVG mask-based sun/moon morph with 360° spin animation
- Inverted logic (sun in dark mode, moon in light mode)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 07:54:32 +02:00
AI Assistant 6cf809d985 Merge branch 'claude/elastic-chaplygin' 2026-03-09 12:26:16 +02:00
AI Assistant ca4d7b5d8d feat(auth): force Authentik login on first visit, fix ManicTime sync
Auth:
- Add middleware.ts that redirects unauthenticated users to Authentik SSO
- Extract authOptions to shared auth-options.ts
- Add getAuthSession() helper for API route protection
- Add loading spinner during session validation
- Dev mode bypasses auth (stub user still works)

ManicTime:
- Fix hardcoded companyId="beletage" — now uses group context from Tags.txt
- Fix extended project format label parsing (extracts name after year)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:26:08 +02:00
AI Assistant 5209fd5dd0 feat(dwg): DWG→DXF via sidecar microservice (libredwg)
Add dedicated dwg2dxf container (Debian slim + libredwg-tools + Flask)
instead of modifying the Alpine base image. The ArchiTools API route
proxies to the sidecar over Docker internal network.

- dwg2dxf-api/: Dockerfile + Flask app (POST /convert, GET /health)
- docker-compose.yml: dwg2dxf service, healthcheck, depends_on
- route.ts: rewritten from local exec to HTTP proxy
- .dockerignore: exclude sidecar from main build context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 10:51:27 +02:00
AI Assistant 12b7bca990 Mini Utilities v0.2.0: extreme PDF compression (GS+qpdf), DWG→DXF, paste support, drag-drop layers
- Extreme PDF compression via direct Ghostscript + qpdf pipeline
  (PassThroughJPEGImages=false, QFactor 1.5, 72 DPI downsample)
- DWG→DXF converter via libredwg (Docker only)
- PDF unlock in-app via Stirling PDF proxy
- Removed PDF/A tab (unused)
- Paste (Ctrl+V) on all file drop zones
- Mouse drag-drop reordering on thermal layers
- Tabs reorganized into 2 visual rows
- Dockerfile: added ghostscript, qpdf, libredwg
2026-03-08 21:44:43 +02:00
AI Assistant b7a236c45a feat(parcel-sync): eTerra health check + maintenance detection
- 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
2026-03-08 10:28:30 +02:00
AI Assistant 6557cd5374 feat(parcel-sync): per-UAT analytics dashboard in Database tab
- 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
2026-03-08 10:18:34 +02:00
AI Assistant 6558c690f5 feat(parcel-sync): owner name search (proprietar) in Search tab
- 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)
2026-03-08 03:48:23 +02:00
AI Assistant d12f01fc02 fix(parcel-sync): add 2min timeout to no-geom scan, non-blocking UI
- Server: Promise.race with 120s timeout on no-geom-scan API route
- Client: AbortController with 120s timeout on scan fetch
- UI: show 'max 2 min' during scanning + hint that buttons work without scan
- UI: timeout state shows retry button + explains no-geom won't be available
- Prevents indefinitely stuck 'Se scanează...' on slow eTerra responses
2026-03-08 02:28:51 +02:00