fix(ancpi): find NEW orderId after submit, track known IDs in queue

submitOrder now captures the previous orderId BEFORE submitting, then
searches for a NEW orderId that isn't in the knownOrderIds set. Queue
passes knownOrderIds between sequential items to prevent duplicate
orderId assignment (unique constraint violation).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-23 02:43:21 +02:00
parent c452bd9fb7
commit 6c60572a3e
2 changed files with 67 additions and 11 deletions
@@ -434,11 +434,22 @@ export class EpayClient {
/* ── Order Submission ──────────────────────────────────────── */
async submitOrder(): Promise<string> {
/**
* Submit order. Pass knownOrderIds to skip them when finding the new orderId.
*/
async submitOrder(knownOrderIds?: Set<string>): Promise<string> {
// Get current latest orderId BEFORE submitting (to detect the new one)
let previousOrderId: string | null = null;
try {
previousOrderId = await this.getLatestOrderId();
} catch {
// No previous orders
}
const body = new URLSearchParams();
body.set("goToCheckout", "true");
await this.client.post(
const response = await this.client.post(
`${BASE_URL}/EditCartSubmit.action`,
body.toString(),
{
@@ -449,7 +460,12 @@ export class EpayClient {
},
);
return this.getLatestOrderId();
const finalUrl =
response.request?.res?.responseUrl ?? response.config?.url ?? "";
console.log(`[epay] EditCartSubmit: finalUrl=${finalUrl.slice(0, 100)}`);
// Find the NEW orderId (different from previous + not in knownOrderIds)
return this.findNewOrderId(previousOrderId, knownOrderIds);
}
private async getLatestOrderId(): Promise<string> {
@@ -459,6 +475,36 @@ export class EpayClient {
const html = String(response.data ?? "");
const match = html.match(/ShowOrderDetails\.action\?orderId=(\d+)/);
if (match) return match[1] ?? "";
throw new Error("Could not determine orderId");
}
private async findNewOrderId(
previousOrderId: string | null,
knownOrderIds?: Set<string>,
): Promise<string> {
const response = await this.client.get(`${BASE_URL}/LogIn.action`, {
timeout: DEFAULT_TIMEOUT_MS,
});
const html = String(response.data ?? "");
// Find ALL orderIds on the page
const allMatches = html.matchAll(/ShowOrderDetails\.action\?orderId=(\d+)/g);
for (const m of allMatches) {
const oid = m[1] ?? "";
if (!oid) continue;
if (oid === previousOrderId) continue;
if (knownOrderIds?.has(oid)) continue;
console.log(`[epay] New orderId: ${oid}`);
return oid;
}
// If no new orderId found, the latest one might be it (first order)
const latest = html.match(/ShowOrderDetails\.action\?orderId=(\d+)/);
if (latest?.[1]) {
console.log(`[epay] Using latest orderId: ${latest[1]}`);
return latest[1];
}
throw new Error("Could not determine orderId after checkout");
}
+18 -8
View File
@@ -124,10 +124,14 @@ async function processQueue(): Promise<void> {
if (g.__epayQueueProcessing) return; // already running
g.__epayQueueProcessing = true;
// Track all orderIds from this batch to avoid duplicates
const knownOrderIds = new Set<string>();
try {
while (g.__epayQueue && g.__epayQueue.length > 0) {
const item = g.__epayQueue.shift()!;
await processItem(item);
const orderId = await processItem(item, knownOrderIds);
if (orderId) knownOrderIds.add(orderId);
}
} finally {
g.__epayQueueProcessing = false;
@@ -145,7 +149,10 @@ async function updateStatus(
});
}
async function processItem(item: QueueItem): Promise<void> {
async function processItem(
item: QueueItem,
knownOrderIds: Set<string>,
): Promise<string | null> {
const { extractId, input } = item;
try {
@@ -155,7 +162,7 @@ async function processItem(item: QueueItem): Promise<void> {
await updateStatus(extractId, "failed", {
errorMessage: "Nu ești conectat la ePay.",
});
return;
return null;
}
const client = await EpayClient.create(creds.username, creds.password);
@@ -167,7 +174,7 @@ async function processItem(item: QueueItem): Promise<void> {
await updateStatus(extractId, "failed", {
errorMessage: `Credite insuficiente: ${credits}. Reîncărcați contul pe epay.ancpi.ro.`,
});
return;
return null;
}
// Step 2: Add to cart
@@ -216,10 +223,10 @@ async function processItem(item: QueueItem): Promise<void> {
await updateStatus(extractId, "failed", {
errorMessage: "Salvarea metadatelor în ePay a eșuat.",
});
return;
return null;
}
const orderId = await client.submitOrder();
const orderId = await client.submitOrder(knownOrderIds);
await updateStatus(extractId, "polling", { orderId });
@@ -242,7 +249,7 @@ async function processItem(item: QueueItem): Promise<void> {
epayStatus: finalStatus.status,
errorMessage: `Comanda ${finalStatus.status.toLowerCase()}.`,
});
return;
return null;
}
// Step 6: Download PDF
@@ -254,7 +261,7 @@ async function processItem(item: QueueItem): Promise<void> {
epayStatus: finalStatus.status,
errorMessage: "Nu s-a găsit documentul PDF în comanda finalizată.",
});
return;
return null;
}
await updateStatus(extractId, "downloading", {
@@ -303,9 +310,12 @@ async function processItem(item: QueueItem): Promise<void> {
console.log(
`[epay-queue] Completed: ${input.nrCadastral}${path} (credits: ${newCredits})`,
);
return orderId;
} catch (error) {
const message = error instanceof Error ? error.message : "Eroare necunoscută";
console.error(`[epay-queue] Failed: ${input.nrCadastral}:`, message);
await updateStatus(extractId, "failed", { errorMessage: message });
return null;
}
}