From a826f45b24df8db3a6e9c56564500f8d95bfed2e Mon Sep 17 00:00:00 2001 From: AI Assistant Date: Mon, 23 Mar 2026 09:38:23 +0200 Subject: [PATCH] feat(ancpi): re-download with CF matching + tooltips + animations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/app/api/ancpi/test/route.ts | 315 ++++++++++-------- .../components/epay-order-button.tsx | 136 ++++++-- .../components/parcel-sync-module.tsx | 269 ++++++++++----- 3 files changed, 457 insertions(+), 263 deletions(-) diff --git a/src/app/api/ancpi/test/route.ts b/src/app/api/ancpi/test/route.ts index 4703165..87476f3 100644 --- a/src/app/api/ancpi/test/route.ts +++ b/src/app/api/ancpi/test/route.ts @@ -86,46 +86,25 @@ export async function GET(req: Request) { }); } - // ── download ── Download PDFs from 5 existing orders + // ── download ── Re-download PDFs from all known ePay orders if (step === "download") { const client = await EpayClient.create(username, password); createEpaySession(username, password, await client.getCredits()); - // Known orders from previous test - const orderIds = ["9685480", "9685481", "9685482", "9685483", "9685484"]; + // All known order IDs (MinIO + DB were cleaned, need re-download) + // Single orders: mapping unknown — use documentsByCadastral to discover CF + const singleOrderIds = ["9685480", "9685481", "9685482", "9685483", "9685484"]; + // Batch orders: documentsByCadastral maps CF -> doc correctly + const batchOrderIds = ["9685487", "9685488"]; + const allOrderIds = [...singleOrderIds, ...batchOrderIds]; - // Mapping: orderId → nrCadastral (5 orders for 3 parcels) - // Orders were for: 345295 (Cluj-Napoca), 63565 (Feleacu), 88089 (Florești) - // 5 orders for 3 parcels = some duplicates - const orderParcelMap: Record< - string, - { nrCadastral: string; judetName: string; uatName: string } - > = { - "9685480": { - nrCadastral: "345295", - judetName: "CLUJ", - uatName: "Cluj-Napoca", - }, - "9685481": { - nrCadastral: "63565", - judetName: "CLUJ", - uatName: "Feleacu", - }, - "9685482": { - nrCadastral: "88089", - judetName: "CLUJ", - uatName: "Florești", - }, - "9685483": { - nrCadastral: "345295", - judetName: "CLUJ", - uatName: "Cluj-Napoca", - }, - "9685484": { - nrCadastral: "63565", - judetName: "CLUJ", - uatName: "Feleacu", - }, + // UAT name lookup for DB records + const uatLookup: Record = { + "345295": { uatId: 54975, uatName: "Cluj-Napoca" }, + "63565": { uatId: 57582, uatName: "Feleacu" }, + "88089": { uatId: 57706, uatName: "Floresti" }, + "61904": { uatId: 57582, uatName: "Feleacu" }, + "309952": { uatId: 54975, uatName: "Cluj-Napoca" }, }; const results: Array<{ @@ -138,31 +117,18 @@ export async function GET(req: Request) { error?: string; }> = []; - for (const orderId of orderIds) { - const parcelInfo = orderParcelMap[orderId]; - if (!parcelInfo) { - results.push({ - orderId, - nrCadastral: "unknown", - status: "error", - documents: 0, - downloaded: false, - error: "No parcel mapping for orderId", - }); - continue; - } - + for (const orderId of allOrderIds) { try { - // Get order status and document info + // Get order status — documentsByCadastral maps CF → doc const orderStatus = await client.getOrderStatus(orderId); console.log( - `[ancpi-test] Order ${orderId}: status=${orderStatus.status}, docs=${orderStatus.documents.length}`, + `[ancpi-test] Order ${orderId}: status=${orderStatus.status}, docs=${orderStatus.documents.length}, byCF=${orderStatus.documentsByCadastral.size}`, ); if (orderStatus.documents.length === 0) { results.push({ orderId, - nrCadastral: parcelInfo.nrCadastral, + nrCadastral: "unknown", status: orderStatus.status, documents: 0, downloaded: false, @@ -171,94 +137,171 @@ export async function GET(req: Request) { continue; } - // Find downloadable PDF document - const doc = orderStatus.documents.find( - (d) => d.downloadValabil && d.contentType === "application/pdf", - ); + // Process each document by cadastral number from the map + if (orderStatus.documentsByCadastral.size > 0) { + for (const [cfNumber, doc] of orderStatus.documentsByCadastral) { + if (!doc.downloadValabil || doc.contentType !== "application/pdf") { + results.push({ + orderId, + nrCadastral: cfNumber, + status: orderStatus.status, + documents: orderStatus.documents.length, + downloaded: false, + error: "Document not downloadable or not PDF", + }); + continue; + } - if (!doc) { - results.push({ - orderId, - nrCadastral: parcelInfo.nrCadastral, - status: orderStatus.status, - documents: orderStatus.documents.length, - downloaded: false, - error: "No downloadable PDF found", - }); - continue; - } + try { + // Download the PDF + const pdfBuffer = await client.downloadDocument(doc.idDocument, 4); + console.log( + `[ancpi-test] Downloaded doc ${doc.idDocument} (CF ${cfNumber}): ${pdfBuffer.length} bytes`, + ); - // Download the PDF - const pdfBuffer = await client.downloadDocument(doc.idDocument, 4); - console.log( - `[ancpi-test] Downloaded doc ${doc.idDocument}: ${pdfBuffer.length} bytes`, - ); + // Resolve UAT info for this cadastral number + const uat = uatLookup[cfNumber]; + const uatId = uat?.uatId ?? 0; + const uatName = uat?.uatName ?? "Necunoscut"; - // Store in MinIO - const { path, index } = await storeCfExtract( - pdfBuffer, - parcelInfo.nrCadastral, - { - "ancpi-order-id": orderId, - "nr-cadastral": parcelInfo.nrCadastral, - judet: parcelInfo.judetName, - uat: parcelInfo.uatName, - "data-document": doc.dataDocument ?? "", - stare: orderStatus.status, - produs: "EXI_ONLINE", - }, - ); + // Store in MinIO + const { path, index } = await storeCfExtract( + pdfBuffer, + cfNumber, + { + "ancpi-order-id": orderId, + "nr-cadastral": cfNumber, + judet: "CLUJ", + uat: uatName, + "data-document": doc.dataDocument ?? "", + stare: orderStatus.status, + produs: "EXI_ONLINE", + }, + ); - // Upsert CfExtract record — find by orderId or create - const documentDate = doc.dataDocument - ? new Date(doc.dataDocument) - : new Date(); - const expiresAt = new Date(documentDate); - expiresAt.setDate(expiresAt.getDate() + 30); + // Calculate dates + const documentDate = doc.dataDocument + ? new Date(doc.dataDocument) + : new Date(); + const expiresAt = new Date(documentDate); + expiresAt.setDate(expiresAt.getDate() + 30); - // Try to find existing record by orderId - const existing = await prisma.cfExtract.findFirst({ - where: { orderId }, - }); + // Always create new records (DB was cleaned) + // Increment version for duplicate parcels + const maxVersion = await prisma.cfExtract.aggregate({ + where: { nrCadastral: cfNumber }, + _max: { version: true }, + }); - if (existing) { - // Update existing record - await prisma.cfExtract.update({ - where: { id: existing.id }, - data: { - status: "completed", - epayStatus: orderStatus.status, - idDocument: doc.idDocument, - documentName: doc.nume, - documentDate, - minioPath: path, - minioIndex: index, - completedAt: new Date(), - expiresAt, - errorMessage: null, - }, - }); + await prisma.cfExtract.create({ + data: { + orderId, + nrCadastral: cfNumber, + nrCF: cfNumber, + judetIndex: 127, + judetName: "CLUJ", + uatId, + uatName, + status: "completed", + epayStatus: orderStatus.status, + idDocument: doc.idDocument, + documentName: doc.nume, + documentDate, + minioPath: path, + minioIndex: index, + completedAt: new Date(), + expiresAt, + version: (maxVersion._max.version ?? 0) + 1, + }, + }); + + results.push({ + orderId, + nrCadastral: cfNumber, + status: orderStatus.status, + documents: orderStatus.documents.length, + downloaded: true, + minioPath: path, + }); + } catch (dlErr) { + const msg = dlErr instanceof Error ? dlErr.message : String(dlErr); + console.error( + `[ancpi-test] Failed to download doc for CF ${cfNumber} in order ${orderId}:`, + msg, + ); + results.push({ + orderId, + nrCadastral: cfNumber, + status: "error", + documents: orderStatus.documents.length, + downloaded: false, + error: msg, + }); + } + } } else { - // Create new record + // Fallback: no CF mapping, process first downloadable document + const doc = orderStatus.documents.find( + (d) => d.downloadValabil && d.contentType === "application/pdf", + ); + if (!doc) { + results.push({ + orderId, + nrCadastral: "unknown", + status: orderStatus.status, + documents: orderStatus.documents.length, + downloaded: false, + error: "No downloadable PDF and no CF mapping found", + }); + continue; + } + + // Try to extract CF from document name (e.g. "Extras_Informare_345295.pdf") + const cfFromName = doc.nume.match(/(\d{4,})/)?.[1] ?? "unknown"; + + const pdfBuffer = await client.downloadDocument(doc.idDocument, 4); + console.log( + `[ancpi-test] Downloaded doc ${doc.idDocument} (CF from name: ${cfFromName}): ${pdfBuffer.length} bytes`, + ); + + const uat = uatLookup[cfFromName]; + const uatId = uat?.uatId ?? 0; + const uatName = uat?.uatName ?? "Necunoscut"; + + const { path, index } = await storeCfExtract( + pdfBuffer, + cfFromName, + { + "ancpi-order-id": orderId, + "nr-cadastral": cfFromName, + judet: "CLUJ", + uat: uatName, + "data-document": doc.dataDocument ?? "", + stare: orderStatus.status, + produs: "EXI_ONLINE", + }, + ); + + const documentDate = doc.dataDocument + ? new Date(doc.dataDocument) + : new Date(); + const expiresAt = new Date(documentDate); + expiresAt.setDate(expiresAt.getDate() + 30); + const maxVersion = await prisma.cfExtract.aggregate({ - where: { nrCadastral: parcelInfo.nrCadastral }, + where: { nrCadastral: cfFromName }, _max: { version: true }, }); await prisma.cfExtract.create({ data: { orderId, - nrCadastral: parcelInfo.nrCadastral, - nrCF: parcelInfo.nrCadastral, + nrCadastral: cfFromName, + nrCF: cfFromName, judetIndex: 127, - judetName: parcelInfo.judetName, - uatId: - parcelInfo.uatName === "Cluj-Napoca" - ? 54975 - : parcelInfo.uatName === "Feleacu" - ? 57582 - : 57706, - uatName: parcelInfo.uatName, + judetName: "CLUJ", + uatId, + uatName, status: "completed", epayStatus: orderStatus.status, idDocument: doc.idDocument, @@ -271,16 +314,16 @@ export async function GET(req: Request) { version: (maxVersion._max.version ?? 0) + 1, }, }); - } - results.push({ - orderId, - nrCadastral: parcelInfo.nrCadastral, - status: orderStatus.status, - documents: orderStatus.documents.length, - downloaded: true, - minioPath: path, - }); + results.push({ + orderId, + nrCadastral: cfFromName, + status: orderStatus.status, + documents: orderStatus.documents.length, + downloaded: true, + minioPath: path, + }); + } } catch (error) { const message = error instanceof Error ? error.message : String(error); @@ -290,7 +333,7 @@ export async function GET(req: Request) { ); results.push({ orderId, - nrCadastral: parcelInfo.nrCadastral, + nrCadastral: "unknown", status: "error", documents: 0, downloaded: false, @@ -301,7 +344,7 @@ export async function GET(req: Request) { return NextResponse.json({ step: "download", - totalOrders: orderIds.length, + totalOrders: allOrderIds.length, results, summary: { downloaded: results.filter((r) => r.downloaded).length, diff --git a/src/modules/parcel-sync/components/epay-order-button.tsx b/src/modules/parcel-sync/components/epay-order-button.tsx index 5ff2a06..4089ed2 100644 --- a/src/modules/parcel-sync/components/epay-order-button.tsx +++ b/src/modules/parcel-sync/components/epay-order-button.tsx @@ -1,8 +1,14 @@ "use client"; import { useState, useEffect, useCallback, useRef } from "react"; -import { FileText, Loader2, Check } from "lucide-react"; +import { FileText, Loader2, Check, RefreshCw } from "lucide-react"; import { Button } from "@/shared/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/shared/components/ui/tooltip"; import { cn } from "@/shared/lib/utils"; import type { EpaySessionStatus } from "./epay-connect"; @@ -15,6 +21,10 @@ type Props = { siruta: string; judetName: string; uatName: string; + /** Custom label for the button (e.g. "Actualizeaza" for expired extracts) */ + label?: string; + /** Custom tooltip text */ + tooltipText?: string; }; /* ------------------------------------------------------------------ */ @@ -26,6 +36,8 @@ export function EpayOrderButton({ siruta, judetName, uatName, + label, + tooltipText, }: Props) { const [ordering, setOrdering] = useState(false); const [ordered, setOrdered] = useState(false); @@ -93,12 +105,12 @@ export function EpayOrderButton({ }); const data = (await res.json()) as { orders?: unknown[]; error?: string }; if (!res.ok || data.error) { - if (mountedRef.current) setError(data.error ?? "Eroare comandă"); + if (mountedRef.current) setError(data.error ?? "Eroare comanda"); } else { if (mountedRef.current) setOrdered(true); } } catch { - if (mountedRef.current) setError("Eroare rețea"); + if (mountedRef.current) setError("Eroare retea"); } finally { if (mountedRef.current) setOrdering(false); } @@ -109,45 +121,95 @@ export function EpayOrderButton({ (epayStatus.credits != null && epayStatus.credits < 1) || ordering; + const isRenew = !!label; // "Actualizeaza" mode for expired extracts + const Icon = isRenew ? RefreshCw : FileText; + + const resolveTooltip = (): string => { + if (error) return error; + if (ordering) return "Se comanda..."; + if (ordered) return "Extras CF valid"; + if (!epayStatus.connected) return "ePay neconectat"; + if (epayStatus.credits != null && epayStatus.credits < 1) return "Credite insuficiente"; + return tooltipText ?? "Comanda extras CF (1 credit)"; + }; + if (ordered) { return ( - + + + + + + Extras CF comandat cu succes + + ); } + if (label) { + // Render as a compact text button with label (for "Actualizeaza" etc.) + return ( + + + + + + {resolveTooltip()} + + + ); + } + + // Default: icon-only button return ( - + + + + + + {resolveTooltip()} + + ); } diff --git a/src/modules/parcel-sync/components/parcel-sync-module.tsx b/src/modules/parcel-sync/components/parcel-sync-module.tsx index e487a97..4eb07ad 100644 --- a/src/modules/parcel-sync/components/parcel-sync-module.tsx +++ b/src/modules/parcel-sync/components/parcel-sync-module.tsx @@ -46,6 +46,12 @@ import { DropdownMenuTrigger, } from "@/shared/components/ui/dropdown-menu"; import { cn } from "@/shared/lib/utils"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/shared/components/ui/tooltip"; import { LAYER_CATALOG, LAYER_CATEGORY_LABELS, @@ -126,6 +132,15 @@ function formatArea(val?: number | null) { return val.toLocaleString("ro-RO", { maximumFractionDigits: 2 }) + " mp"; } +/** Format ISO date as DD.MM.YYYY (no time) */ +function formatShortDate(iso?: string | null) { + if (!iso) return "—"; + const d = new Date(iso); + const dd = String(d.getDate()).padStart(2, "0"); + const mm = String(d.getMonth() + 1).padStart(2, "0"); + return `${dd}.${mm}.${d.getFullYear()}`; +} + /* ------------------------------------------------------------------ */ /* Connection Status Pill */ /* ------------------------------------------------------------------ */ @@ -447,6 +462,8 @@ export function ParcelSyncModule() { const [cfStatusMap, setCfStatusMap] = useState>({}); /** Latest completed extract IDs per nrCadastral */ const [cfLatestIds, setCfLatestIds] = useState>({}); + /** Expiry dates per nrCadastral (ISO string) */ + const [cfExpiryDates, setCfExpiryDates] = useState>({}); /** Whether we're currently loading CF statuses */ const [cfStatusLoading, setCfStatusLoading] = useState(false); /** List CF batch order state */ @@ -1534,12 +1551,18 @@ export function ParcelSyncModule() { } if (data.latestById) { const idMap: Record = {}; + const expiryMap: Record = {}; for (const [nr, rec] of Object.entries(data.latestById)) { if (rec && typeof rec === "object" && "id" in rec) { idMap[nr] = (rec as { id: string }).id; + const expires = (rec as { expiresAt: string | null }).expiresAt; + if (expires) { + expiryMap[nr] = expires; + } } } setCfLatestIds((prev) => ({ ...prev, ...idMap })); + setCfExpiryDates((prev) => ({ ...prev, ...expiryMap })); } } catch { /* silent */ @@ -2318,61 +2341,93 @@ export function ParcelSyncModule() { {p.immovablePk && sirutaValid && (() => { const cfStatus = cfStatusMap[p.nrCad]; const extractId = cfLatestIds[p.nrCad]; + const cfExpiry = cfExpiryDates[p.nrCad]; if (cfStatus === "valid") { return ( -
- - Extras CF valid - - {extractId && ( - - )} -
+ +
+ + + + Extras CF + + + + {cfExpiry ? `Valid pana la ${formatShortDate(cfExpiry)}` : "Extras CF valid"} + + + {extractId && ( + + + + + Descarca extras CF + + )} +
+
); } if (cfStatus === "expired") { return ( -
- - Extras CF expirat - - -
+ +
+ + + + Expirat + + + + {cfExpiry ? `Expirat pe ${formatShortDate(cfExpiry)}` : "Extras CF expirat"} + + + +
+
); } if (cfStatus === "processing") { return ( - - Se proceseaza... - + + + + + Se proceseaza... + + + Comanda in curs de procesare + + ); } // "none" or unknown @@ -2382,6 +2437,7 @@ export function ParcelSyncModule() { siruta={siruta} judetName={selectedUat?.county ?? ""} uatName={selectedUat?.name ?? ""} + tooltipText="Comanda extras CF (1 credit)" /> ); })()} @@ -2639,61 +2695,93 @@ export function ParcelSyncModule() { {r.immovablePk && sirutaValid && (() => { const cfStatus = cfStatusMap[r.nrCad]; const extractId = cfLatestIds[r.nrCad]; + const cfExpiry = cfExpiryDates[r.nrCad]; if (cfStatus === "valid") { return ( -
- - Extras CF valid - - {extractId && ( - - )} -
+ +
+ + + + Extras CF + + + + {cfExpiry ? `Valid pana la ${formatShortDate(cfExpiry)}` : "Extras CF valid"} + + + {extractId && ( + + + + + Descarca extras CF + + )} +
+
); } if (cfStatus === "expired") { return ( -
- - Extras CF expirat - - -
+ +
+ + + + Expirat + + + + {cfExpiry ? `Expirat pe ${formatShortDate(cfExpiry)}` : "Extras CF expirat"} + + + +
+
); } if (cfStatus === "processing") { return ( - - Se proceseaza... - + + + + + Se proceseaza... + + + Comanda in curs de procesare + + ); } return ( @@ -2702,6 +2790,7 @@ export function ParcelSyncModule() { siruta={siruta} judetName={selectedUat?.county ?? ""} uatName={selectedUat?.name ?? ""} + tooltipText="Comanda extras CF (1 credit)" /> ); })()}