feat(ancpi): batch ordering + download existing orders

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>
This commit is contained in:
AI Assistant
2026-03-23 03:20:36 +02:00
parent 08cd7164cb
commit 8488a53e3b
4 changed files with 502 additions and 204 deletions
+2 -2
View File
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
import { getEpayCredentials } from "@/modules/parcel-sync/services/epay-session-store";
import {
enqueueOrder,
enqueueBulk,
enqueueBatch,
} from "@/modules/parcel-sync/services/epay-queue";
import type { CfExtractCreateInput } from "@/modules/parcel-sync/services/epay-types";
@@ -62,7 +62,7 @@ export async function POST(req: Request) {
});
}
const ids = await enqueueBulk(parcels);
const ids = await enqueueBatch(parcels);
const orders = ids.map((id, i) => ({
id,
nrCadastral: parcels[i]?.nrCadastral ?? "",
+244 -30
View File
@@ -4,13 +4,15 @@ import {
createEpaySession,
getEpayCredentials,
} from "@/modules/parcel-sync/services/epay-session-store";
import { enqueueOrder } from "@/modules/parcel-sync/services/epay-queue";
import { enqueueBatch } from "@/modules/parcel-sync/services/epay-queue";
import { storeCfExtract } from "@/modules/parcel-sync/services/epay-storage";
import { prisma } from "@/core/storage/prisma";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
/**
* GET /api/ancpi/test?step=login|uats|order
* GET /api/ancpi/test?step=login|uats|order|download
*
* ePay internal county IDs = eTerra WORKSPACE_IDs.
* ePay UAT IDs = SIRUTA codes.
@@ -67,8 +69,232 @@ export async function GET(req: Request) {
});
}
// ── order ── (USES 3 CREDITS!)
// Uses WORKSPACE_ID as county ID, SIRUTA as UAT ID — zero discovery
// ── download ── Download PDFs from 5 existing 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"];
// 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",
},
};
const results: Array<{
orderId: string;
nrCadastral: string;
status: string;
documents: number;
downloaded: boolean;
minioPath?: string;
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;
}
try {
// Get order status and document info
const orderStatus = await client.getOrderStatus(orderId);
console.log(
`[ancpi-test] Order ${orderId}: status=${orderStatus.status}, docs=${orderStatus.documents.length}`,
);
if (orderStatus.documents.length === 0) {
results.push({
orderId,
nrCadastral: parcelInfo.nrCadastral,
status: orderStatus.status,
documents: 0,
downloaded: false,
error: "No documents found in order",
});
continue;
}
// Find downloadable PDF document
const doc = orderStatus.documents.find(
(d) => d.downloadValabil && d.contentType === "application/pdf",
);
if (!doc) {
results.push({
orderId,
nrCadastral: parcelInfo.nrCadastral,
status: orderStatus.status,
documents: orderStatus.documents.length,
downloaded: false,
error: "No downloadable PDF found",
});
continue;
}
// Download the PDF
const pdfBuffer = await client.downloadDocument(doc.idDocument, 4);
console.log(
`[ancpi-test] Downloaded doc ${doc.idDocument}: ${pdfBuffer.length} bytes`,
);
// 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",
},
);
// 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);
// Try to find existing record by orderId
const existing = await prisma.cfExtract.findFirst({
where: { orderId },
});
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,
},
});
} else {
// Create new record
const maxVersion = await prisma.cfExtract.aggregate({
where: { nrCadastral: parcelInfo.nrCadastral },
_max: { version: true },
});
await prisma.cfExtract.create({
data: {
orderId,
nrCadastral: parcelInfo.nrCadastral,
nrCF: parcelInfo.nrCadastral,
judetIndex: 127,
judetName: parcelInfo.judetName,
uatId:
parcelInfo.uatName === "Cluj-Napoca"
? 54975
: parcelInfo.uatName === "Feleacu"
? 57582
: 57706,
uatName: parcelInfo.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: parcelInfo.nrCadastral,
status: orderStatus.status,
documents: orderStatus.documents.length,
downloaded: true,
minioPath: path,
});
} catch (error) {
const message =
error instanceof Error ? error.message : String(error);
console.error(
`[ancpi-test] Failed to process order ${orderId}:`,
message,
);
results.push({
orderId,
nrCadastral: parcelInfo.nrCadastral,
status: "error",
documents: 0,
downloaded: false,
error: message,
});
}
}
return NextResponse.json({
step: "download",
totalOrders: orderIds.length,
results,
summary: {
downloaded: results.filter((r) => r.downloaded).length,
failed: results.filter((r) => !r.downloaded).length,
},
});
}
// ── order ── Batch order test (USES 2 CREDITS!)
// Uses enqueueBatch to create ONE ePay order for all parcels
if (step === "order") {
if (!getEpayCredentials()) {
createEpaySession(username, password, 0);
@@ -78,24 +304,9 @@ export async function GET(req: Request) {
const credits = await client.getCredits();
createEpaySession(username, password, credits);
if (credits < 3) {
return NextResponse.json({
error: `Doar ${credits} credite, trebuie 3.`,
});
}
// workspacePk=127 (CLUJ), SIRUTA codes as UAT IDs
const parcels = [
{
nrCadastral: "345295",
siruta: "54975", // SIRUTA = ePay UAT ID
judetIndex: 127, // workspacePk = ePay county ID
judetName: "CLUJ",
uatId: 54975,
uatName: "Cluj-Napoca",
},
{
nrCadastral: "63565",
nrCadastral: "61904",
siruta: "57582",
judetIndex: 127,
judetName: "CLUJ",
@@ -103,26 +314,29 @@ export async function GET(req: Request) {
uatName: "Feleacu",
},
{
nrCadastral: "88089",
siruta: "57706",
nrCadastral: "309952",
siruta: "54975",
judetIndex: 127,
judetName: "CLUJ",
uatId: 57706,
uatName: "Florești",
uatId: 54975,
uatName: "Cluj-Napoca",
},
];
const ids: string[] = [];
for (const p of parcels) {
const id = await enqueueOrder(p);
ids.push(id);
if (credits < parcels.length) {
return NextResponse.json({
error: `Doar ${credits} credite, trebuie ${parcels.length}.`,
});
}
// Use enqueueBatch — ONE order for all parcels
const ids = await enqueueBatch(parcels);
return NextResponse.json({
step: "order",
credits,
message: `Enqueued ${ids.length} orders (4 requests each, zero discovery).`,
orderIds: ids,
message: `Enqueued batch of ${ids.length} parcels as ONE order.`,
extractIds: ids,
parcels: parcels.map((p, i) => ({
nrCadastral: p.nrCadastral,
uatName: p.uatName,