fix(epay-ui): show localitate + judet on intern extracts; hide cancelled rows
The intern (and gis-api-sourced) rows showed an empty "jud." with no UAT name or county, and a few dead cancelled/test rows cluttered the list. - gis-api returns siruta + uatName but judetName is null there, and the CfExtractRow type didn't even declare those fields so adaptCfRow blanked them. Added the fields to the type; adaptCfRow now surfaces uatName + siruta. - New enrichCfLocations(rows) fills missing uatName/judetName from SIRUTA via the local GisUat table (batched, one query). Applied in both list proxies (/api/cf/orders for gis rows, /api/ancpi/orders for old legacy intern rows whose judetName was stored empty). So intern rows now read "LOCALITATE, jud. X". - Hide status='cancelled' rows from the Extrase CF list (dead — payment refused / cleaned-up bad orders, e.g. the old 354686 test). failed/review stay (actionable via Reincearca). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { prisma } from "@/core/storage/prisma";
|
import { prisma } from "@/core/storage/prisma";
|
||||||
import { requireCfAccess } from "@/core/auth/cf-access";
|
import { requireCfAccess } from "@/core/auth/cf-access";
|
||||||
|
import { enrichCfLocations } from "@/modules/parcel-sync/services/cf-enrich-location";
|
||||||
|
|
||||||
export const runtime = "nodejs";
|
export const runtime = "nodejs";
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
@@ -57,6 +58,10 @@ export async function GET(req: Request) {
|
|||||||
prisma.cfExtract.count({ where }),
|
prisma.cfExtract.count({ where }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Fill missing uatName/judetName from SIRUTA (old intern rows stored an
|
||||||
|
// empty judetName) so the list shows localitate + judet for them too.
|
||||||
|
await enrichCfLocations(orders);
|
||||||
|
|
||||||
// Build statusMap for multi-cadastral queries (or single if requested)
|
// Build statusMap for multi-cadastral queries (or single if requested)
|
||||||
if (cadastralNumbers.length > 0) {
|
if (cadastralNumbers.length > 0) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { getAuthSession } from "@/core/auth/require-auth";
|
import { getAuthSession } from "@/core/auth/require-auth";
|
||||||
import { gisApi, GisApiError } from "@/lib/gis-api-client";
|
import { gisApi, GisApiError } from "@/lib/gis-api-client";
|
||||||
|
import { enrichCfLocations } from "@/modules/parcel-sync/services/cf-enrich-location";
|
||||||
|
|
||||||
export const runtime = "nodejs";
|
export const runtime = "nodejs";
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
@@ -30,9 +31,14 @@ export async function GET(request: Request) {
|
|||||||
const offset = Number.isFinite(offsetRaw) && offsetRaw >= 0 ? offsetRaw : 0;
|
const offset = Number.isFinite(offsetRaw) && offsetRaw >= 0 ? offsetRaw : 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return NextResponse.json(
|
const data = await gisApi.enrichment.cf.list({ limit, offset, status });
|
||||||
await gisApi.enrichment.cf.list({ limit, offset, status }),
|
// gis-api returns uatName + siruta but judetName is null there — fill the
|
||||||
);
|
// county (and any missing UAT name) from the local GisUat table so the UI
|
||||||
|
// can show "localitate, jud. X" on intern rows too.
|
||||||
|
if (Array.isArray(data?.rows)) {
|
||||||
|
await enrichCfLocations(data.rows);
|
||||||
|
}
|
||||||
|
return NextResponse.json(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof GisApiError) {
|
if (err instanceof GisApiError) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
|
|||||||
@@ -55,6 +55,10 @@ export interface CfExtractRow {
|
|||||||
userId: string;
|
userId: string;
|
||||||
nrCadastral: string;
|
nrCadastral: string;
|
||||||
nrCF?: string;
|
nrCF?: string;
|
||||||
|
type?: string;
|
||||||
|
siruta?: string | null;
|
||||||
|
uatName?: string | null;
|
||||||
|
judetName?: string | null;
|
||||||
status:
|
status:
|
||||||
| "pending"
|
| "pending"
|
||||||
| "queued"
|
| "queued"
|
||||||
|
|||||||
@@ -99,10 +99,10 @@ function adaptLegacyRow(row: LegacyCfExtract): CfExtractRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert a gisApi CfExtractRow → the UI-side CfExtractRecord shape.
|
// Convert a gisApi CfExtractRow → the UI-side CfExtractRecord shape.
|
||||||
// gis-api currently doesn't surface uatName/siruta/judetName on the list
|
// gis-api returns siruta + uatName (judetName is null there, but the
|
||||||
// endpoint, so we leave them blank; the row type defaults to "intern"
|
// /api/cf/orders proxy fills it from the local GisUat by SIRUTA — see
|
||||||
// because gis_core's CfExtract is the cf-intern store (the cutover plan
|
// enrichCfLocations). The row type defaults to "intern" because gis's
|
||||||
// hasn't yet moved ePay writes here).
|
// CfExtract is primarily the cf-intern store.
|
||||||
export function adaptCfRow(row: CfExtractRow & { type?: string }): CfExtractRecord {
|
export function adaptCfRow(row: CfExtractRow & { type?: string }): CfExtractRecord {
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
@@ -110,9 +110,9 @@ export function adaptCfRow(row: CfExtractRow & { type?: string }): CfExtractReco
|
|||||||
orderId: row.orderId ?? null,
|
orderId: row.orderId ?? null,
|
||||||
nrCadastral: row.nrCadastral,
|
nrCadastral: row.nrCadastral,
|
||||||
nrCF: row.nrCF ?? null,
|
nrCF: row.nrCF ?? null,
|
||||||
siruta: null,
|
siruta: row.siruta ?? null,
|
||||||
judetName: "",
|
judetName: row.judetName ?? "",
|
||||||
uatName: "",
|
uatName: row.uatName ?? "",
|
||||||
status: row.status,
|
status: row.status,
|
||||||
epayStatus: row.epayStatus ?? null,
|
epayStatus: row.epayStatus ?? null,
|
||||||
documentName: row.documentName ?? null,
|
documentName: row.documentName ?? null,
|
||||||
@@ -167,12 +167,18 @@ async function fetchGisAc(
|
|||||||
// single timeline shows ePay + intern history together. Sort newest-
|
// single timeline shows ePay + intern history together. Sort newest-
|
||||||
// first; dedupe by id (in case the same record ever lands in both
|
// first; dedupe by id (in case the same record ever lands in both
|
||||||
// stores during the cutover migration).
|
// stores during the cutover migration).
|
||||||
|
// Cancelled rows are dead (payment refused / cleaned-up bad orders) and just
|
||||||
|
// clutter the list — hide them. failed/review stay (they're actionable).
|
||||||
|
const isListable = (r: CfExtractRecord): boolean => r.status !== "cancelled";
|
||||||
|
|
||||||
export async function fetchCfOrdersList(
|
export async function fetchCfOrdersList(
|
||||||
useGisAc: boolean,
|
useGisAc: boolean,
|
||||||
params: { limit?: number; nrCadastral?: string; status?: string } = {},
|
params: { limit?: number; nrCadastral?: string; status?: string } = {},
|
||||||
): Promise<{ orders: CfExtractRecord[]; total: number }> {
|
): Promise<{ orders: CfExtractRecord[]; total: number }> {
|
||||||
if (!useGisAc) {
|
if (!useGisAc) {
|
||||||
return fetchLegacy(params);
|
const r = await fetchLegacy(params);
|
||||||
|
const orders = r.orders.filter(isListable);
|
||||||
|
return { orders, total: orders.length };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pull more rows from each side than the caller asked for so that the
|
// Pull more rows from each side than the caller asked for so that the
|
||||||
@@ -191,6 +197,7 @@ export async function fetchCfOrdersList(
|
|||||||
|
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
const dedup = merged.filter((r) => {
|
const dedup = merged.filter((r) => {
|
||||||
|
if (!isListable(r)) return false;
|
||||||
if (seen.has(r.id)) return false;
|
if (seen.has(r.id)) return false;
|
||||||
seen.add(r.id);
|
seen.add(r.id);
|
||||||
return true;
|
return true;
|
||||||
@@ -198,14 +205,8 @@ export async function fetchCfOrdersList(
|
|||||||
|
|
||||||
dedup.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
|
dedup.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
|
||||||
|
|
||||||
const total =
|
const orders = params.limit ? dedup.slice(0, params.limit) : dedup;
|
||||||
(legacy.status === "fulfilled" ? legacy.value.total : 0) +
|
return { orders, total: orders.length };
|
||||||
(gisac.status === "fulfilled" ? gisac.value.total : 0);
|
|
||||||
|
|
||||||
return {
|
|
||||||
orders: params.limit ? dedup.slice(0, params.limit) : dedup,
|
|
||||||
total,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Existence check used by the per-parcel order button. We check both
|
// Existence check used by the per-parcel order button. We check both
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Fill in uatName + judetName on CF extract rows from their SIRUTA.
|
||||||
|
//
|
||||||
|
// Intern (cf-intern) extracts — and ePay rows on the gis-api side — often
|
||||||
|
// arrive without a judetName (it's null in gis_enrichment) and sometimes
|
||||||
|
// without a uatName. Both are derivable from `siruta` via the local GisUat
|
||||||
|
// table. This batches one query for the whole page instead of N lookups.
|
||||||
|
|
||||||
|
import { prisma } from "@/core/storage/prisma";
|
||||||
|
|
||||||
|
type LocatableRow = {
|
||||||
|
siruta?: string | null;
|
||||||
|
uatName?: string | null;
|
||||||
|
judetName?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutates rows in place: for any row with a SIRUTA whose uatName/judetName is
|
||||||
|
* blank, fill it from GisUat. Best-effort — a missing SIRUTA or a DB error
|
||||||
|
* leaves the row unchanged. Returns the same array for convenience.
|
||||||
|
*/
|
||||||
|
export async function enrichCfLocations<T extends LocatableRow>(
|
||||||
|
rows: T[],
|
||||||
|
): Promise<T[]> {
|
||||||
|
const sirutas = Array.from(
|
||||||
|
new Set(
|
||||||
|
rows
|
||||||
|
.map((r) => (r.siruta ? String(r.siruta).trim() : ""))
|
||||||
|
.filter(Boolean),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (sirutas.length === 0) return rows;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const uats = await prisma.gisUat.findMany({
|
||||||
|
where: { siruta: { in: sirutas } },
|
||||||
|
select: { siruta: true, name: true, county: true },
|
||||||
|
});
|
||||||
|
const bySiruta = new Map(uats.map((u) => [u.siruta, u]));
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const s = row.siruta ? String(row.siruta).trim() : "";
|
||||||
|
if (!s) continue;
|
||||||
|
const uat = bySiruta.get(s);
|
||||||
|
if (!uat) continue;
|
||||||
|
if (!row.uatName && uat.name) row.uatName = uat.name;
|
||||||
|
if (!row.judetName && uat.county) row.judetName = uat.county;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("[cf-enrich-location] lookup failed:", error);
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user