fix(ancpi): complete rewrite based on Angular source code analysis

All endpoints and payloads verified against epaymentAngularApp.js:
- EpayJsonInterceptor: form-urlencoded (not JSON), uses reqType param
- County IDs: internal ANCPI IDs from judeteNom (NOT 0-41 indices)
- UAT lookup: reqType=nomenclatorUAT&countyId=<internal_ID>
- Save metadata: reqType=saveProductMetadataForBasketItem (multipart)
  with productMetadataJSON using stringValues[] arrays
- SearchEstate: field names are identificator/judet/uat (not identifier/countyId/uatId)
- Download PDF: Content-Type: application/pdf in request header
- Queue resolves county+UAT IDs dynamically via getCountyList+getUatList

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-23 02:01:39 +02:00
parent eb8cd18210
commit e13a9351be
3 changed files with 490 additions and 629 deletions
+49 -108
View File
@@ -1,6 +1,5 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { EpayClient } from "@/modules/parcel-sync/services/epay-client"; import { EpayClient } from "@/modules/parcel-sync/services/epay-client";
import { resolveEpayCountyIndex } from "@/modules/parcel-sync/services/epay-counties";
import { import {
createEpaySession, createEpaySession,
getEpayCredentials, getEpayCredentials,
@@ -11,15 +10,10 @@ export const runtime = "nodejs";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
/** /**
* GET /api/ancpi/test?step=login|credits|uats|search|order * GET /api/ancpi/test?step=login|uats|search|order
* *
* Temporary diagnostic endpoint to test ePay integration step-by-step. * Temporary diagnostic endpoint. Uses the CORRECT ePay flow:
* * shoppingCartCompleteInformation → judeteNom → nomenclatorUAT → saveMetadata
* Steps:
* login — test login + show credits
* uats — resolve ePay UAT IDs for Cluj-Napoca, Feleacu, Florești
* search — search estates for the 3 test parcels
* order — enqueue orders for the 3 test parcels (USES 3 CREDITS!)
*/ */
export async function GET(req: Request) { export async function GET(req: Request) {
const url = new URL(req.url); const url = new URL(req.url);
@@ -29,97 +23,90 @@ export async function GET(req: Request) {
const password = process.env.ANCPI_PASSWORD ?? ""; const password = process.env.ANCPI_PASSWORD ?? "";
if (!username || !password) { if (!username || !password) {
return NextResponse.json({ return NextResponse.json({ error: "ANCPI credentials not configured" });
error: "ANCPI_USERNAME / ANCPI_PASSWORD not configured",
});
} }
const normalize = (s: string) =>
s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase().trim();
try { try {
// ── Step: login ── // ── login ──
if (step === "login") { if (step === "login") {
const client = await EpayClient.create(username, password); const client = await EpayClient.create(username, password);
const credits = await client.getCredits(); const credits = await client.getCredits();
createEpaySession(username, password, credits); createEpaySession(username, password, credits);
return NextResponse.json({ return NextResponse.json({ step: "login", success: true, credits });
step: "login",
success: true,
credits,
countyIdxCluj: resolveEpayCountyIndex("Cluj"),
});
} }
// ── Step: uats ── // ── uats ── Get internal county IDs + UAT list for Cluj
if (step === "uats") { if (step === "uats") {
const client = await EpayClient.create(username, password); const client = await EpayClient.create(username, password);
const countyIdx = resolveEpayCountyIndex("Cluj"); await client.addToCart(14200); // need cart for ShowCartItems context
if (countyIdx === null) {
return NextResponse.json({ error: "Could not resolve Cluj county index" }); const counties = await client.getCountyList();
const clujCounty = counties.find((c) => normalize(c.value) === "CLUJ");
let uatList: { id: number; value: string }[] = [];
if (clujCounty) {
uatList = await client.getUatList(clujCounty.id);
} }
const uatList = await client.getUatList(countyIdx); const clujNapoca = uatList.find((u) => normalize(u.value).includes("CLUJ-NAPOCA"));
// Find the 3 UATs
const normalize = (s: string) =>
s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase();
const clujNapoca = uatList.find((u) => normalize(u.value).includes("CLUJ-NAPOCA") || normalize(u.value).includes("CLUJNAPOCA"));
const feleacu = uatList.find((u) => normalize(u.value).includes("FELEACU")); const feleacu = uatList.find((u) => normalize(u.value).includes("FELEACU"));
const floresti = uatList.find((u) => normalize(u.value).includes("FLORESTI")); const floresti = uatList.find((u) => normalize(u.value).includes("FLORESTI"));
return NextResponse.json({ return NextResponse.json({
step: "uats", step: "uats",
countyIdx, totalCounties: counties.length,
countiesFirst5: counties.slice(0, 5),
clujCounty,
totalUats: uatList.length, totalUats: uatList.length,
first5: uatList.slice(0, 5), matches: { clujNapoca, feleacu, floresti },
clujNapoca,
feleacu,
floresti,
}); });
} }
// ── Step: search ── // ── search ── SearchEstate with correct params
// Test UAT lookup (JSON) + SearchEstate (with basketId)
if (step === "search") { if (step === "search") {
const client = await EpayClient.create(username, password); const client = await EpayClient.create(username, password);
const countyIdx = resolveEpayCountyIndex("Cluj")!; await client.addToCart(14200);
const results: Record<string, unknown> = { countyIdx };
// 1. Get UAT list (JSON body now) const counties = await client.getCountyList();
const uatList = await client.getUatList(countyIdx); const clujCounty = counties.find((c) => normalize(c.value) === "CLUJ");
results["uatCount"] = uatList.length; if (!clujCounty) {
results["uatFirst5"] = uatList.slice(0, 5); return NextResponse.json({ error: "CLUJ not found", counties: counties.slice(0, 5) });
}
// Find our test UATs const uatList = await client.getUatList(clujCounty.id);
const normalize = (s: string) => const clujNapoca = uatList.find((u) => normalize(u.value).includes("CLUJ-NAPOCA"));
s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase();
const clujNapoca = uatList.find((u) => normalize(u.value).includes("CLUJ-NAPOCA") || normalize(u.value).includes("CLUJNAPOCA"));
const feleacu = uatList.find((u) => normalize(u.value).includes("FELEACU")); const feleacu = uatList.find((u) => normalize(u.value).includes("FELEACU"));
const floresti = uatList.find((u) => normalize(u.value).includes("FLORESTI")); const floresti = uatList.find((u) => normalize(u.value).includes("FLORESTI"));
results["uatMatches"] = { clujNapoca, feleacu, floresti };
// 2. Add to cart + SearchEstate with basketId const results: Record<string, unknown> = {
const basketRowId = await client.addToCart(14200); clujCountyId: clujCounty.id,
results["basketRowId"] = basketRowId; uats: { clujNapoca, feleacu, floresti },
};
// SearchEstate with correct field names: identificator, judet (internal ID), uat (real ID)
if (clujNapoca) { if (clujNapoca) {
results["search_345295"] = await client results["search_345295"] = await client
.searchEstate("345295", countyIdx, clujNapoca.id, basketRowId) .searchEstate("345295", clujCounty.id, clujNapoca.id)
.catch((e: Error) => ({ error: e.message })); .catch((e: Error) => ({ error: e.message }));
} }
if (feleacu) { if (feleacu) {
results["search_63565"] = await client results["search_63565"] = await client
.searchEstate("63565", countyIdx, feleacu.id, basketRowId) .searchEstate("63565", clujCounty.id, feleacu.id)
.catch((e: Error) => ({ error: e.message })); .catch((e: Error) => ({ error: e.message }));
} }
if (floresti) { if (floresti) {
results["search_88089"] = await client results["search_88089"] = await client
.searchEstate("88089", countyIdx, floresti.id, basketRowId) .searchEstate("88089", clujCounty.id, floresti.id)
.catch((e: Error) => ({ error: e.message })); .catch((e: Error) => ({ error: e.message }));
} }
return NextResponse.json({ step: "search", results }); return NextResponse.json({ step: "search", results });
} }
// ── Step: order ── (USES 3 CREDITS!) // ── order ── (USES 3 CREDITS!)
if (step === "order") { if (step === "order") {
if (!getEpayCredentials()) { if (!getEpayCredentials()) {
createEpaySession(username, password, 0); createEpaySession(username, password, 0);
@@ -130,53 +117,15 @@ export async function GET(req: Request) {
createEpaySession(username, password, credits); createEpaySession(username, password, credits);
if (credits < 3) { if (credits < 3) {
return NextResponse.json({ return NextResponse.json({ error: `Doar ${credits} credite, trebuie 3.` });
error: `Doar ${credits} credite disponibile, trebuie 3.`,
});
}
const countyIdx = resolveEpayCountyIndex("Cluj")!;
const uatList = await client.getUatList(countyIdx);
const normalize = (s: string) =>
s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase();
const findUat = (name: string) =>
uatList.find((u) => normalize(u.value).includes(name));
const clujNapoca = findUat("CLUJ-NAPOCA") ?? findUat("CLUJNAPOCA");
const feleacu = findUat("FELEACU");
const floresti = findUat("FLORESTI");
if (!clujNapoca || !feleacu || !floresti) {
return NextResponse.json({
error: "Nu s-au găsit UAT-urile.",
uatCount: uatList.length,
clujNapoca, feleacu, floresti,
});
} }
// Resolve county + UAT IDs through the queue
// Queue handles getCountyList + getUatList internally
const parcels = [ const parcels = [
{ { nrCadastral: "345295", judetName: "CLUJ", uatName: "Cluj-Napoca", judetIndex: 0, uatId: 0 },
nrCadastral: "345295", { nrCadastral: "63565", judetName: "CLUJ", uatName: "Feleacu", judetIndex: 0, uatId: 0 },
judetIndex: countyIdx, { nrCadastral: "88089", judetName: "CLUJ", uatName: "Floresti", judetIndex: 0, uatId: 0 },
judetName: "CLUJ",
uatId: clujNapoca.id,
uatName: clujNapoca.value,
},
{
nrCadastral: "63565",
judetIndex: countyIdx,
judetName: "CLUJ",
uatId: feleacu.id,
uatName: feleacu.value,
},
{
nrCadastral: "88089",
judetIndex: countyIdx,
judetName: "CLUJ",
uatId: floresti.id,
uatName: floresti.value,
},
]; ];
const ids: string[] = []; const ids: string[] = [];
@@ -188,20 +137,12 @@ export async function GET(req: Request) {
return NextResponse.json({ return NextResponse.json({
step: "order", step: "order",
credits, credits,
message: `Enqueued ${ids.length} orders. Processing sequentially...`, message: `Enqueued ${ids.length} orders. Queue resolves county/UAT IDs automatically.`,
orderIds: ids, orderIds: ids,
parcels: parcels.map((p, i) => ({
nrCadastral: p.nrCadastral,
uatName: p.uatName,
uatId: p.uatId,
extractId: ids[i],
})),
}); });
} }
return NextResponse.json({ return NextResponse.json({ error: `Unknown step: ${step}` });
error: `Unknown step: ${step}. Use: login, uats, search, order`,
});
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error); const message = error instanceof Error ? error.message : String(error);
console.error(`[ancpi-test] Step ${step} failed:`, message); console.error(`[ancpi-test] Step ${step} failed:`, message);
File diff suppressed because it is too large Load Diff
+44 -10
View File
@@ -175,20 +175,54 @@ async function processItem(item: QueueItem): Promise<void> {
const basketRowId = await client.addToCart(input.prodId ?? 14200); const basketRowId = await client.addToCart(input.prodId ?? 14200);
await updateStatus(extractId, "cart", { basketRowId }); await updateStatus(extractId, "cart", { basketRowId });
// Step 3: Configure + submit order // Step 3: Resolve internal county + UAT IDs
// Skip SearchEstate — we already have the data from eTerra. await updateStatus(extractId, "searching");
// configureCartItem (via EditCartItemJson) + submitOrder (via EditCartSubmit) const counties = await client.getCountyList();
const normalize = (s: string) =>
s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toUpperCase().trim();
const countyMatch = counties.find(
(c) => normalize(c.value) === normalize(input.judetName),
);
if (!countyMatch) {
await updateStatus(extractId, "failed", {
errorMessage: `Județul "${input.judetName}" nu a fost găsit în ePay. Disponibile: ${counties.slice(0, 5).map((c) => c.value).join(", ")}...`,
});
return;
}
const uats = await client.getUatList(countyMatch.id);
const uatMatch = uats.find(
(u) => normalize(u.value) === normalize(input.uatName),
);
if (!uatMatch) {
await updateStatus(extractId, "failed", {
errorMessage: `UAT "${input.uatName}" nu a fost găsit în ePay pentru ${input.judetName}.`,
});
return;
}
// Step 4: Save metadata + submit order
await updateStatus(extractId, "ordering"); await updateStatus(extractId, "ordering");
const nrCF = input.nrCF ?? input.nrCadastral; const nrCF = input.nrCF ?? input.nrCadastral;
const orderId = await client.submitOrder({ const saved = await client.saveMetadata(
basketRowId, basketRowId,
judetIndex: input.judetIndex, countyMatch.id,
uatId: input.uatId, countyMatch.value,
uatMatch.id,
uatMatch.value,
nrCF, nrCF,
nrCadastral: input.nrCadastral, input.nrCadastral,
solicitantId: process.env.ANCPI_DEFAULT_SOLICITANT_ID || "14452",
process.env.ANCPI_DEFAULT_SOLICITANT_ID || "14452", );
}); if (!saved) {
await updateStatus(extractId, "failed", {
errorMessage: "Salvarea metadatelor în ePay a eșuat.",
});
return;
}
const orderId = await client.submitOrder();
await updateStatus(extractId, "polling", { orderId }); await updateStatus(extractId, "polling", { orderId });