feat: enrichment fallback via direct parcel details endpoint
PROBLEM:
For no-geometry parcels (and many geometry parcels without application
IDs), CATEGORIE_FOLOSINTA was always '-' because:
1. fetchImmAppsByImmovable returned no apps (no applicationId)
2. Without appId, fetchParcelFolosinte was skipped entirely
3. No fallback existed
DISCOVERY (from eTerra UI investigation):
The endpoint /api/immovable/details/parcels/list/{wp}/{pk}/{page}/{size}
returns parcel use categories DIRECTLY — no applicationId needed.
Example: [{useCategory:'arabil', intravilan:'Necunoscut', parcelPk:17753903}]
FIX:
- After the app-based CATEGORIE_FOLOSINTA attempt, if result is still '-',
fall back to fetchImmovableParcelDetails (the direct endpoint)
- formatCategories now handles both API formats:
- App-based: categorieFolosinta + suprafata fields
- Direct: useCategory field (no area — shows category name only)
- When direct endpoint provides area=0, format shows just the category
name without ':0' (e.g. 'arabil; faneata' instead of 'arabil:0; faneata:0')
- Also picks up intravilan from direct endpoint if app-based was empty
- Fixed fetchImmovableParcelDetails default size: 1 → 20 (one immovable
can have multiple parcels, e.g. IE 25332 has 2: arabil + faneata)
- Results are cached in folCache to avoid duplicate requests
This commit is contained in:
@@ -80,13 +80,18 @@ const normalizeIntravilan = (values: string[]) => {
|
|||||||
const formatCategories = (entries: any[]) => {
|
const formatCategories = (entries: any[]) => {
|
||||||
const map = new Map<string, number>();
|
const map = new Map<string, number>();
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const key = String(entry?.categorieFolosinta ?? "").trim();
|
// Support both API formats:
|
||||||
|
// fetchParcelFolosinte (via app): categorieFolosinta + suprafata
|
||||||
|
// fetchImmovableParcelDetails (direct): useCategory (no area)
|
||||||
|
const key = String(
|
||||||
|
entry?.categorieFolosinta ?? entry?.useCategory ?? "",
|
||||||
|
).trim();
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
const area = Number(entry?.suprafata ?? 0);
|
const area = Number(entry?.suprafata ?? 0);
|
||||||
map.set(key, (map.get(key) ?? 0) + (Number.isFinite(area) ? area : 0));
|
map.set(key, (map.get(key) ?? 0) + (Number.isFinite(area) ? area : 0));
|
||||||
}
|
}
|
||||||
return Array.from(map.entries())
|
return Array.from(map.entries())
|
||||||
.map(([k, a]) => `${k}:${formatNumber(a)}`)
|
.map(([k, a]) => (a > 0 ? `${k}:${formatNumber(a)}` : k))
|
||||||
.join("; ");
|
.join("; ");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -482,6 +487,40 @@ export async function enrichFeatures(
|
|||||||
);
|
);
|
||||||
categorie = formatCategories(fol);
|
categorie = formatCategories(fol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback: if no application or empty categories, use direct
|
||||||
|
// parcel details endpoint (doesn't need applicationId).
|
||||||
|
// Discovered via eTerra UI: /api/immovable/details/parcels/list/{wp}/{pk}/{page}/{size}
|
||||||
|
// Returns: [{useCategory: "arabil", intravilan: "Necunoscut", ...}]
|
||||||
|
if (categorie === "-" && immovableId) {
|
||||||
|
const immPkForDetails =
|
||||||
|
immovableListById.get(normalizeId(immovableId))?.immovablePk ??
|
||||||
|
immovableId;
|
||||||
|
const detKey = `${workspaceId}:${immPkForDetails}:details`;
|
||||||
|
let details = folCache.get(detKey);
|
||||||
|
if (!details) {
|
||||||
|
try {
|
||||||
|
details = await throttled(() =>
|
||||||
|
client.fetchImmovableParcelDetails(
|
||||||
|
workspaceId as string | number,
|
||||||
|
immPkForDetails as string | number,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
details = [];
|
||||||
|
}
|
||||||
|
folCache.set(detKey, details);
|
||||||
|
}
|
||||||
|
if (details && details.length > 0) {
|
||||||
|
const detIntravilan = normalizeIntravilan(
|
||||||
|
details.map((d: any) => d?.intravilan ?? ""),
|
||||||
|
);
|
||||||
|
const detCategorie = formatCategories(details);
|
||||||
|
if (detCategorie && detCategorie !== "-") categorie = detCategorie;
|
||||||
|
if (detIntravilan && detIntravilan !== "-" && intravilan === "-")
|
||||||
|
intravilan = detIntravilan;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cadRefRaw = (attrs.NATIONAL_CADASTRAL_REFERENCE ?? "") as string;
|
const cadRefRaw = (attrs.NATIONAL_CADASTRAL_REFERENCE ?? "") as string;
|
||||||
|
|||||||
@@ -362,9 +362,7 @@ export class EterraClient {
|
|||||||
await sleep(500); // small delay before retry with smaller page
|
await sleep(500); // small delay before retry with smaller page
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw new Error(
|
throw new Error(`Failed to fetch layer ${layer.name}: ${cause}`);
|
||||||
`Failed to fetch layer ${layer.name}: ${cause}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const features = data.features ?? [];
|
const features = data.features ?? [];
|
||||||
@@ -504,7 +502,7 @@ export class EterraClient {
|
|||||||
workspaceId: string | number,
|
workspaceId: string | number,
|
||||||
immovableId: string | number,
|
immovableId: string | number,
|
||||||
page = 1,
|
page = 1,
|
||||||
size = 1,
|
size = 20,
|
||||||
): Promise<any[]> {
|
): Promise<any[]> {
|
||||||
const url = `${BASE_URL}/api/immovable/details/parcels/list/${workspaceId}/${immovableId}/${page}/${size}`;
|
const url = `${BASE_URL}/api/immovable/details/parcels/list/${workspaceId}/${immovableId}/${page}/${size}`;
|
||||||
return this.getRawJson(url);
|
return this.getRawJson(url);
|
||||||
|
|||||||
Reference in New Issue
Block a user