feat(eterra): RGI API routes + test page for issued documents

New eTerra RGI (Registrul General de Intrare) integration:

API routes (/api/eterra/rgi/):
- POST /applications — list applications with workspace/year filters
- GET /details?applicationId=X — application details
- GET /issued-docs?applicationId=X&workspaceId=Y — issued documents list
- GET /download-doc?wid=X&aid=Y&did=Z — download issued document

EterraClient: added rgiPost, rgiGet, rgiDownload methods for RGI API.

Test page (/rgi-test):
- Filters: workspace, orgUnit, year
- Toggle: "Doar solutionate cu termen viitor"
- Table with application list, expandable issued docs, download links
- Raw JSON debug sections (collapsible)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AI Assistant
2026-03-24 20:59:49 +02:00
parent 1dac5206e4
commit aa11ca389e
6 changed files with 921 additions and 0 deletions
@@ -982,6 +982,54 @@ export class EterraClient {
);
}
/* ── RGI (Registrul General de Intrare) API ───────────────── */
/**
* Generic RGI POST request with JSON body.
* Uses the same JSESSIONID cookie jar as GIS queries.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async rgiPost<T = any>(path: string, body?: unknown): Promise<T> {
const url = `${BASE_URL}/api/${path}`;
return this.requestRaw(() =>
this.client.post(url, body ?? {}, {
headers: { "Content-Type": "application/json;charset=UTF-8" },
timeout: this.timeoutMs,
}),
);
}
/**
* Generic RGI GET request.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async rgiGet<T = any>(path: string): Promise<T> {
const url = `${BASE_URL}/api/${path}`;
return this.requestRaw(() =>
this.client.get(url, { timeout: this.timeoutMs }),
);
}
/**
* Download a file from RGI (returns Buffer).
*/
async rgiDownload(path: string): Promise<{ data: Buffer; contentType: string; filename: string }> {
const url = `${BASE_URL}/api/${path}`;
const response = await this.requestWithRetry(() =>
this.client.get(url, {
timeout: this.timeoutMs,
responseType: "arraybuffer",
}),
);
const cd = (response as { headers?: Record<string, string> }).headers?.["content-disposition"] ?? "";
const match = /filename="?([^"]+)"?/.exec(cd);
return {
data: Buffer.from(response.data as ArrayBuffer),
contentType: (response as { headers?: Record<string, string> }).headers?.["content-type"] ?? "application/octet-stream",
filename: match?.[1] ?? "document.pdf",
};
}
private async requestWithRetry<T>(request: () => Promise<T>) {
let attempt = 0;
while (true) {