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
@@ -0,0 +1,97 @@
import { NextRequest, NextResponse } from "next/server";
import { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
type Body = {
workspaceId: number;
orgUnitId: number;
year: string;
page?: number;
nrElements?: number;
};
/**
* POST /api/eterra/rgi/applications
*
* List RGI applications for a given workspace (county) and org unit (UAT).
* Proxies eTerra rgi/applicationgrid/list endpoint.
*/
export async function POST(req: NextRequest) {
try {
const body = (await req.json()) as Body;
const { workspaceId, orgUnitId, year } = body;
if (!workspaceId || !orgUnitId || !year) {
return NextResponse.json(
{ error: "workspaceId, orgUnitId and year are required" },
{ status: 400 },
);
}
const username = process.env.ETERRA_USERNAME ?? "";
const password = process.env.ETERRA_PASSWORD ?? "";
if (!username || !password) {
return NextResponse.json(
{ error: "Credentials missing" },
{ status: 500 },
);
}
const client = await EterraClient.create(username, password);
const page = body.page ?? 0;
const nrElements = body.nrElements ?? 25;
const payload = {
filters: [
{
value: workspaceId,
type: "NUMBER",
key: "workspace.nomenPk",
op: "=",
},
{
value: orgUnitId,
type: "NUMBER",
key: "partyFunctionByOrgUnitId.nomenPk",
op: "=",
},
],
applicationFilters: {
applicationType: "own",
tabCode: "NUMBER_LIST",
year,
countyId: workspaceId,
adminUnitId: orgUnitId,
showAll: false,
showNewRequests: false,
showSuspended: false,
showSolutionDeadlineExpired: false,
showPendingLimitation: false,
showCadastreNumberAllocated: false,
showImmovableRegistered: false,
showDocumentIssued: false,
showRestitutionClosed: false,
showRejected: false,
showClosed: false,
showWithdrawn: false,
showSolutionDeadlineExceeded: false,
},
sorters: [],
nrElements,
page,
};
const result = await client.rgiPost(
"rgi/applicationgrid/list",
payload,
);
return NextResponse.json(result);
} catch (error) {
const message = error instanceof Error ? error.message : "Eroare server";
return NextResponse.json({ error: message }, { status: 500 });
}
}
+45
View File
@@ -0,0 +1,45 @@
import { NextRequest, NextResponse } from "next/server";
import { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
/**
* GET /api/eterra/rgi/details?applicationId=...
*
* Fetch RGI application details by application ID.
* Proxies eTerra rgi/appdetail/details endpoint.
*/
export async function GET(req: NextRequest) {
try {
const applicationId = req.nextUrl.searchParams.get("applicationId");
if (!applicationId) {
return NextResponse.json(
{ error: "applicationId is required" },
{ status: 400 },
);
}
const username = process.env.ETERRA_USERNAME ?? "";
const password = process.env.ETERRA_PASSWORD ?? "";
if (!username || !password) {
return NextResponse.json(
{ error: "Credentials missing" },
{ status: 500 },
);
}
const client = await EterraClient.create(username, password);
const result = await client.rgiPost(
`rgi/appdetail/details?applicationid=${encodeURIComponent(applicationId)}`,
undefined,
);
return NextResponse.json(result);
} catch (error) {
const message = error instanceof Error ? error.message : "Eroare server";
return NextResponse.json({ error: message }, { status: 500 });
}
}
@@ -0,0 +1,68 @@
import { NextRequest, NextResponse } from "next/server";
import { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
/**
* GET /api/eterra/rgi/download-doc?workspaceId=...&applicationId=...&docId=...
*
* Check file visibility and download an issued document from RGI.
* Step 1: Calls rgi/appdetail/issueddocs/fileVisibility to check access.
* Step 2: Downloads the file via rgi/appdetail/issueddocs/download.
*/
export async function GET(req: NextRequest) {
try {
const workspaceId = req.nextUrl.searchParams.get("workspaceId");
const applicationId = req.nextUrl.searchParams.get("applicationId");
const docId = req.nextUrl.searchParams.get("docId");
if (!workspaceId || !applicationId || !docId) {
return NextResponse.json(
{ error: "workspaceId, applicationId and docId are required" },
{ status: 400 },
);
}
const username = process.env.ETERRA_USERNAME ?? "";
const password = process.env.ETERRA_PASSWORD ?? "";
if (!username || !password) {
return NextResponse.json(
{ error: "Credentials missing" },
{ status: 500 },
);
}
const client = await EterraClient.create(username, password);
// Step 1: Check file visibility
const visibilityPath = `rgi/appdetail/issueddocs/fileVisibility/${encodeURIComponent(workspaceId)}/${encodeURIComponent(applicationId)}/${encodeURIComponent(docId)}`;
const visibility = await client.rgiGet(visibilityPath);
// Step 2: Attempt to download the file
try {
const downloadPath = `rgi/appdetail/issueddocs/download/${encodeURIComponent(workspaceId)}/${encodeURIComponent(applicationId)}/${encodeURIComponent(docId)}`;
const { data, contentType, filename } =
await client.rgiDownload(downloadPath);
return new NextResponse(new Uint8Array(data), {
status: 200,
headers: {
"Content-Type": contentType,
"Content-Disposition": `attachment; filename="${encodeURIComponent(filename)}"`,
"Content-Length": String(data.length),
},
});
} catch {
// Download failed — return the visibility response instead so the caller
// can inspect what eTerra reported (may contain a URL or error details).
return NextResponse.json({
visibility,
downloadFailed: true,
});
}
} catch (error) {
const message = error instanceof Error ? error.message : "Eroare server";
return NextResponse.json({ error: message }, { status: 500 });
}
}
@@ -0,0 +1,51 @@
import { NextRequest, NextResponse } from "next/server";
import { EterraClient } from "@/modules/parcel-sync/services/eterra-client";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
/**
* GET /api/eterra/rgi/issued-docs?applicationId=...&workspaceId=...
*
* List issued documents for an RGI application.
* Proxies eTerra rgi/appdetail/issueddocs/list endpoint.
*/
export async function GET(req: NextRequest) {
try {
const applicationId = req.nextUrl.searchParams.get("applicationId");
const workspaceId = req.nextUrl.searchParams.get("workspaceId");
if (!applicationId || !workspaceId) {
return NextResponse.json(
{ error: "applicationId and workspaceId are required" },
{ status: 400 },
);
}
const username = process.env.ETERRA_USERNAME ?? "";
const password = process.env.ETERRA_PASSWORD ?? "";
if (!username || !password) {
return NextResponse.json(
{ error: "Credentials missing" },
{ status: 500 },
);
}
const client = await EterraClient.create(username, password);
const result = await client.rgiPost(
`rgi/appdetail/issueddocs/list?applicationid=${encodeURIComponent(applicationId)}&reSaveDocsInPendingAndTiomeOut=false&workspaceid=${encodeURIComponent(workspaceId)}`,
{
filters: [],
sorters: [],
nrElements: 50,
page: 0,
},
);
return NextResponse.json(result);
} catch (error) {
const message = error instanceof Error ? error.message : "Eroare server";
return NextResponse.json({ error: message }, { status: 500 });
}
}