feat(ancpi): complete ePay UI redesign + ZIP download + smart batch ordering
UI Redesign:
- ePay auto-connect when UAT is selected (no manual button)
- Credit badge with tooltip ("N credite ePay disponibile")
- Search result cards show CF status: Valid (green), Expirat (orange),
Lipsă (gray), Se proceseaza (yellow pulse)
- Action buttons on each card: download/update/order CF extract
- "Lista mea" numbered rows + CF Status column + smart batch button
"Scoate Extrase CF": skips valid, re-orders expired, orders new
- "Descarca Extrase CF" button → ZIP archive with numbered files
- Extrase CF tab simplified: clean table, filters (Toate/Valabile/
Expirate/In procesare), search, download-all ZIP
Backend:
- GET /api/ancpi/download-zip?ids=... → JSZip streaming
- GET /api/ancpi/orders: multi-cadastral status check with statusMap
(valid/expired/none/processing) + latestById
Data:
- Simulated expired extract for 328611 (Cluj-Napoca, expired 2026-03-17)
- Cleaned old error records from DB
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,18 +7,38 @@ export const dynamic = "force-dynamic";
|
||||
/**
|
||||
* GET /api/ancpi/orders — list all CF extract orders.
|
||||
*
|
||||
* Query params: ?nrCadastral=&status=&limit=50&offset=0
|
||||
* Query params:
|
||||
* ?nrCadastral=123 — single cadastral number
|
||||
* ?nrCadastral=123,456 — comma-separated for batch status check
|
||||
* ?status=completed — filter by status
|
||||
* ?limit=50&offset=0 — pagination
|
||||
*
|
||||
* When nrCadastral contains commas, returns an extra `statusMap` field:
|
||||
* { orders, total, statusMap: { "123": "valid", "456": "expired", "789": "none" } }
|
||||
* - "valid" = completed + expiresAt > now
|
||||
* - "expired" = completed + expiresAt <= now
|
||||
* - "none" = no completed record
|
||||
*/
|
||||
export async function GET(req: Request) {
|
||||
try {
|
||||
const url = new URL(req.url);
|
||||
const nrCadastral = url.searchParams.get("nrCadastral") || undefined;
|
||||
const nrCadastralParam = url.searchParams.get("nrCadastral") || undefined;
|
||||
const status = url.searchParams.get("status") || undefined;
|
||||
const limit = Math.min(parseInt(url.searchParams.get("limit") ?? "50"), 200);
|
||||
const offset = parseInt(url.searchParams.get("offset") ?? "0");
|
||||
|
||||
// Check if multi-cadastral query
|
||||
const cadastralNumbers = nrCadastralParam
|
||||
? nrCadastralParam.split(",").map((s) => s.trim()).filter(Boolean)
|
||||
: [];
|
||||
const isMulti = cadastralNumbers.length > 1;
|
||||
|
||||
const where: Record<string, unknown> = {};
|
||||
if (nrCadastral) where.nrCadastral = nrCadastral;
|
||||
if (cadastralNumbers.length === 1) {
|
||||
where.nrCadastral = cadastralNumbers[0];
|
||||
} else if (isMulti) {
|
||||
where.nrCadastral = { in: cadastralNumbers };
|
||||
}
|
||||
if (status) where.status = status;
|
||||
|
||||
const [orders, total] = await Promise.all([
|
||||
@@ -31,6 +51,68 @@ export async function GET(req: Request) {
|
||||
prisma.cfExtract.count({ where }),
|
||||
]);
|
||||
|
||||
// Build statusMap for multi-cadastral queries (or single if requested)
|
||||
if (cadastralNumbers.length > 0) {
|
||||
const now = new Date();
|
||||
// For status map, we need completed records for each cadastral number
|
||||
const completedRecords = await prisma.cfExtract.findMany({
|
||||
where: {
|
||||
nrCadastral: { in: cadastralNumbers },
|
||||
status: "completed",
|
||||
},
|
||||
orderBy: { createdAt: "desc" },
|
||||
select: {
|
||||
id: true,
|
||||
nrCadastral: true,
|
||||
expiresAt: true,
|
||||
completedAt: true,
|
||||
minioPath: true,
|
||||
},
|
||||
});
|
||||
|
||||
const statusMap: Record<string, string> = {};
|
||||
const latestById: Record<string, typeof completedRecords[number]> = {};
|
||||
|
||||
// Find latest completed record per cadastral number
|
||||
for (const rec of completedRecords) {
|
||||
const existing = latestById[rec.nrCadastral];
|
||||
if (!existing) {
|
||||
latestById[rec.nrCadastral] = rec;
|
||||
}
|
||||
}
|
||||
|
||||
for (const nr of cadastralNumbers) {
|
||||
const rec = latestById[nr];
|
||||
if (!rec) {
|
||||
statusMap[nr] = "none";
|
||||
} else if (rec.expiresAt && rec.expiresAt <= now) {
|
||||
statusMap[nr] = "expired";
|
||||
} else {
|
||||
statusMap[nr] = "valid";
|
||||
}
|
||||
}
|
||||
|
||||
// Also check for active (in-progress) orders
|
||||
const activeRecords = await prisma.cfExtract.findMany({
|
||||
where: {
|
||||
nrCadastral: { in: cadastralNumbers },
|
||||
status: {
|
||||
in: ["pending", "queued", "cart", "searching", "ordering", "polling", "downloading"],
|
||||
},
|
||||
},
|
||||
select: { nrCadastral: true },
|
||||
});
|
||||
|
||||
for (const rec of activeRecords) {
|
||||
// If there's an active order, mark as "processing" (takes priority over "none")
|
||||
if (statusMap[rec.nrCadastral] === "none") {
|
||||
statusMap[rec.nrCadastral] = "processing";
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({ orders, total, statusMap, latestById });
|
||||
}
|
||||
|
||||
return NextResponse.json({ orders, total });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Eroare server";
|
||||
|
||||
Reference in New Issue
Block a user