fix: remove all hardcoded workspaceId=65 + add robustness for large UATs
- enrich-service: resolve workspacePk from feature attrs / GisUat DB / ArcGIS (was hardcoded 65, broke enrichment for non-BN counties) - enrich-service: skip already-enriched features (resume after crash) - no-geom-sync: use resolved wsPk in synthetic attributes - no-geom-sync: batched DB inserts (50/batch) with retry + exponential backoff - Fixes: Magic export for Cluj/other counties getting empty enrichment
This commit is contained in:
@@ -252,77 +252,109 @@ export async function syncNoGeometryParcels(
|
||||
return { imported: 0, skipped: 0, errors: 0, status: "done" };
|
||||
}
|
||||
|
||||
// 4. Import candidates
|
||||
// 4. Import candidates in batches with retry
|
||||
let imported = 0;
|
||||
let skipped = 0;
|
||||
let errors = 0;
|
||||
const total = candidates.length;
|
||||
const BATCH_SIZE = 50;
|
||||
const MAX_RETRIES = 3;
|
||||
|
||||
for (let i = 0; i < candidates.length; i++) {
|
||||
const item = candidates[i]!;
|
||||
const immPk = Number(item.immovablePk ?? 0);
|
||||
if (immPk <= 0) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
for (
|
||||
let batchStart = 0;
|
||||
batchStart < candidates.length;
|
||||
batchStart += BATCH_SIZE
|
||||
) {
|
||||
const batch = candidates.slice(batchStart, batchStart + BATCH_SIZE);
|
||||
const ops: Array<ReturnType<typeof prisma.gisFeature.upsert>> = [];
|
||||
|
||||
const cadRef = String(item.identifierDetails ?? "").trim();
|
||||
const areaValue = typeof item.area === "number" ? item.area : null;
|
||||
for (const item of batch) {
|
||||
const immPk = Number(item.immovablePk ?? 0);
|
||||
if (immPk <= 0) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Build synthetic attributes to match the eTerra GIS layer format
|
||||
const attributes: Record<string, unknown> = {
|
||||
OBJECTID: -immPk, // synthetic negative
|
||||
IMMOVABLE_ID: immPk,
|
||||
WORKSPACE_ID: item.workspacePk ?? 65,
|
||||
APPLICATION_ID: item.applicationId ?? null,
|
||||
NATIONAL_CADASTRAL_REFERENCE: cadRef,
|
||||
AREA_VALUE: areaValue,
|
||||
IS_ACTIVE: 1,
|
||||
ADMIN_UNIT_ID: Number(siruta),
|
||||
// Metadata from immovable list
|
||||
PAPER_CAD_NO: item.paperCadNo ?? null,
|
||||
PAPER_CF_NO: item.paperCfNo ?? null,
|
||||
PAPER_LB_NO: item.paperLbNo ?? null,
|
||||
TOP_NO: item.topNo ?? null,
|
||||
IMMOVABLE_TYPE: item.immovableType ?? "P",
|
||||
NO_GEOMETRY_SOURCE: "ETERRA_IMMOVABLE_LIST",
|
||||
};
|
||||
const cadRef = String(item.identifierDetails ?? "").trim();
|
||||
const areaValue = typeof item.area === "number" ? item.area : null;
|
||||
|
||||
try {
|
||||
await prisma.gisFeature.upsert({
|
||||
where: {
|
||||
layerId_objectId: {
|
||||
layerId: "TERENURI_ACTIVE",
|
||||
objectId: -immPk,
|
||||
const attributes: Record<string, unknown> = {
|
||||
OBJECTID: -immPk,
|
||||
IMMOVABLE_ID: immPk,
|
||||
WORKSPACE_ID: item.workspacePk ?? wsPk,
|
||||
APPLICATION_ID: item.applicationId ?? null,
|
||||
NATIONAL_CADASTRAL_REFERENCE: cadRef,
|
||||
AREA_VALUE: areaValue,
|
||||
IS_ACTIVE: 1,
|
||||
ADMIN_UNIT_ID: Number(siruta),
|
||||
PAPER_CAD_NO: item.paperCadNo ?? null,
|
||||
PAPER_CF_NO: item.paperCfNo ?? null,
|
||||
PAPER_LB_NO: item.paperLbNo ?? null,
|
||||
TOP_NO: item.topNo ?? null,
|
||||
IMMOVABLE_TYPE: item.immovableType ?? "P",
|
||||
NO_GEOMETRY_SOURCE: "ETERRA_IMMOVABLE_LIST",
|
||||
};
|
||||
|
||||
ops.push(
|
||||
prisma.gisFeature.upsert({
|
||||
where: {
|
||||
layerId_objectId: {
|
||||
layerId: "TERENURI_ACTIVE",
|
||||
objectId: -immPk,
|
||||
},
|
||||
},
|
||||
},
|
||||
create: {
|
||||
layerId: "TERENURI_ACTIVE",
|
||||
siruta,
|
||||
objectId: -immPk,
|
||||
cadastralRef: cadRef || null,
|
||||
areaValue,
|
||||
isActive: true,
|
||||
attributes: attributes as Prisma.InputJsonValue,
|
||||
geometry: Prisma.JsonNull,
|
||||
geometrySource: "NO_GEOMETRY",
|
||||
},
|
||||
update: {
|
||||
cadastralRef: cadRef || null,
|
||||
areaValue,
|
||||
attributes: attributes as Prisma.InputJsonValue,
|
||||
geometrySource: "NO_GEOMETRY",
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
imported++;
|
||||
} catch {
|
||||
errors++;
|
||||
create: {
|
||||
layerId: "TERENURI_ACTIVE",
|
||||
siruta,
|
||||
objectId: -immPk,
|
||||
cadastralRef: cadRef || null,
|
||||
areaValue,
|
||||
isActive: true,
|
||||
attributes: attributes as Prisma.InputJsonValue,
|
||||
geometry: Prisma.JsonNull,
|
||||
geometrySource: "NO_GEOMETRY",
|
||||
},
|
||||
update: {
|
||||
cadastralRef: cadRef || null,
|
||||
areaValue,
|
||||
attributes: attributes as Prisma.InputJsonValue,
|
||||
geometrySource: "NO_GEOMETRY",
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (i % 20 === 0 || i === total - 1) {
|
||||
options?.onProgress?.(i + 1, total, "Import parcele fără geometrie");
|
||||
// Execute batch with retry
|
||||
if (ops.length > 0) {
|
||||
let attempt = 0;
|
||||
while (attempt < MAX_RETRIES) {
|
||||
try {
|
||||
await prisma.$transaction(ops);
|
||||
imported += ops.length;
|
||||
break;
|
||||
} catch (err) {
|
||||
attempt++;
|
||||
if (attempt >= MAX_RETRIES) {
|
||||
// Fall back to individual upserts for this batch
|
||||
for (const op of ops) {
|
||||
try {
|
||||
await op;
|
||||
imported++;
|
||||
} catch {
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Wait before retry (exponential backoff)
|
||||
await new Promise((r) => setTimeout(r, 500 * attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const done = Math.min(batchStart + BATCH_SIZE, total);
|
||||
options?.onProgress?.(done, total, "Import parcele fără geometrie");
|
||||
}
|
||||
|
||||
return { imported, skipped, errors, status: "done" };
|
||||
|
||||
Reference in New Issue
Block a user