fix: show remoteGisCount (505) as cu geometrie, add no-geom cleanup step
- UI: scan card now shows remoteGisCount instead of matchedCount (withGeometry) as the primary 'cu geometrie' number — this is the true GIS layer feature count - UI: workflow preview step 1 shows remoteGisCount for download count - UI: mismatch note reworded as secondary detail about cross-reference matching - Import: automatic cleanup step at start of syncNoGeometryParcels - Builds valid immovablePk set from fresh list (active + identification/area) - Deletes stale NO_GEOMETRY records not in the valid set - Reports cleaned count in result + progress note - NoGeomSyncResult type: added 'cleaned' field - Gitignore: temp-db-check.cjs
This commit is contained in:
BIN
Binary file not shown.
@@ -282,10 +282,14 @@ export async function POST(req: Request) {
|
|||||||
pushProgress();
|
pushProgress();
|
||||||
} else {
|
} else {
|
||||||
noGeomImported = noGeomResult.imported;
|
noGeomImported = noGeomResult.imported;
|
||||||
|
const cleanedNote =
|
||||||
|
noGeomResult.cleaned > 0
|
||||||
|
? `, ${noGeomResult.cleaned} vechi șterse`
|
||||||
|
: "";
|
||||||
note =
|
note =
|
||||||
noGeomImported > 0
|
noGeomImported > 0
|
||||||
? `${noGeomImported} parcele noi fără geometrie importate`
|
? `${noGeomImported} parcele noi fără geometrie importate${cleanedNote}`
|
||||||
: "Nicio parcelă nouă fără geometrie";
|
: `Nicio parcelă nouă fără geometrie${cleanedNote}`;
|
||||||
pushProgress();
|
pushProgress();
|
||||||
}
|
}
|
||||||
updatePhaseProgress(1, 1);
|
updatePhaseProgress(1, 1);
|
||||||
|
|||||||
@@ -2523,7 +2523,7 @@ export function ParcelSyncModule() {
|
|||||||
{noGeomScan.localSyncFresh &&
|
{noGeomScan.localSyncFresh &&
|
||||||
noGeomScan.localDbWithGeom > 0
|
noGeomScan.localDbWithGeom > 0
|
||||||
? "skip (date proaspete în DB)"
|
? "skip (date proaspete în DB)"
|
||||||
: `descarcă ${noGeomScan.withGeometry.toLocaleString("ro-RO")} features`}
|
: `descarcă ${noGeomScan.remoteGisCount.toLocaleString("ro-RO")} features`}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
{includeNoGeom && (
|
{includeNoGeom && (
|
||||||
@@ -2601,7 +2601,7 @@ export function ParcelSyncModule() {
|
|||||||
</span>{" "}
|
</span>{" "}
|
||||||
imobile în eTerra:{" "}
|
imobile în eTerra:{" "}
|
||||||
<span className="text-emerald-600 dark:text-emerald-400 font-medium">
|
<span className="text-emerald-600 dark:text-emerald-400 font-medium">
|
||||||
{noGeomScan.withGeometry.toLocaleString("ro-RO")}
|
{noGeomScan.remoteGisCount.toLocaleString("ro-RO")}
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
cu geometrie,{" "}
|
cu geometrie,{" "}
|
||||||
<span className="font-semibold text-amber-600 dark:text-amber-400">
|
<span className="font-semibold text-amber-600 dark:text-amber-400">
|
||||||
@@ -2609,17 +2609,13 @@ export function ParcelSyncModule() {
|
|||||||
</span>{" "}
|
</span>{" "}
|
||||||
<span className="font-medium">fără geometrie</span>
|
<span className="font-medium">fără geometrie</span>
|
||||||
</p>
|
</p>
|
||||||
{noGeomScan.remoteGisCount > 0 &&
|
{noGeomScan.withGeometry <
|
||||||
noGeomScan.remoteGisCount !==
|
noGeomScan.remoteGisCount && (
|
||||||
noGeomScan.withGeometry && (
|
|
||||||
<p className="text-[10px] text-muted-foreground/70 mt-0.5">
|
<p className="text-[10px] text-muted-foreground/70 mt-0.5">
|
||||||
Layerul GIS are{" "}
|
|
||||||
{noGeomScan.remoteGisCount.toLocaleString(
|
|
||||||
"ro-RO",
|
|
||||||
)}{" "}
|
|
||||||
features, dar doar{" "}
|
|
||||||
{noGeomScan.withGeometry.toLocaleString("ro-RO")}{" "}
|
{noGeomScan.withGeometry.toLocaleString("ro-RO")}{" "}
|
||||||
se potrivesc cu lista de imobile
|
din{" "}
|
||||||
|
{noGeomScan.remoteGisCount.toLocaleString("ro-RO")}{" "}
|
||||||
|
features GIS au corespondent în lista de imobile
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p className="text-[11px] text-muted-foreground mt-0.5">
|
<p className="text-[11px] text-muted-foreground mt-0.5">
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export type NoGeomScanResult = {
|
|||||||
export type NoGeomSyncResult = {
|
export type NoGeomSyncResult = {
|
||||||
imported: number;
|
imported: number;
|
||||||
skipped: number;
|
skipped: number;
|
||||||
|
cleaned: number;
|
||||||
errors: number;
|
errors: number;
|
||||||
status: "done" | "error";
|
status: "done" | "error";
|
||||||
error?: string;
|
error?: string;
|
||||||
@@ -410,6 +411,7 @@ export async function syncNoGeometryParcels(
|
|||||||
return {
|
return {
|
||||||
imported: 0,
|
imported: 0,
|
||||||
skipped: 0,
|
skipped: 0,
|
||||||
|
cleaned: 0,
|
||||||
errors: 0,
|
errors: 0,
|
||||||
status: "error",
|
status: "error",
|
||||||
error: `Nu s-a putut determina workspace-ul pentru SIRUTA ${siruta}`,
|
error: `Nu s-a putut determina workspace-ul pentru SIRUTA ${siruta}`,
|
||||||
@@ -420,7 +422,58 @@ export async function syncNoGeometryParcels(
|
|||||||
options?.onProgress?.(0, 1, "Descărcare listă imobile (fără geometrie)");
|
options?.onProgress?.(0, 1, "Descărcare listă imobile (fără geometrie)");
|
||||||
const allImmovables = await fetchAllImmovables(client, siruta, wsPk);
|
const allImmovables = await fetchAllImmovables(client, siruta, wsPk);
|
||||||
|
|
||||||
// 2. Get existing features from DB
|
// 2. Cleanup: remove stale/orphan no-geom records from DB
|
||||||
|
// Build a set of valid immovablePks from the fresh immovable list.
|
||||||
|
// Any NO_GEOMETRY record whose immovablePk is NOT in the fresh list
|
||||||
|
// (or whose immovable is inactive/empty) gets deleted.
|
||||||
|
const validImmPks = new Set<number>();
|
||||||
|
for (const item of allImmovables) {
|
||||||
|
const pk = Number(item.immovablePk ?? 0);
|
||||||
|
if (pk > 0) {
|
||||||
|
const status = typeof item.status === "number" ? item.status : 1;
|
||||||
|
const cadRef = (item.identifierDetails ?? "").toString().trim();
|
||||||
|
const hasPaperLb = !!(item.paperLbNo ?? "").toString().trim();
|
||||||
|
const hasPaperCad = !!(item.paperCadNo ?? "").toString().trim();
|
||||||
|
const hasArea =
|
||||||
|
(typeof item.measuredArea === "number" && item.measuredArea > 0) ||
|
||||||
|
(typeof item.legalArea === "number" && item.legalArea > 0);
|
||||||
|
const hasIdentification = !!cadRef || hasPaperLb || hasPaperCad;
|
||||||
|
|
||||||
|
// Only keep items that pass the quality gate (active + identification/area)
|
||||||
|
if (status === 1 && (hasIdentification || hasArea)) {
|
||||||
|
validImmPks.add(pk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find stale no-geom records: objectId < 0 means no-geom (objectId = -immPk)
|
||||||
|
const existingNoGeom = await prisma.gisFeature.findMany({
|
||||||
|
where: {
|
||||||
|
layerId: "TERENURI_ACTIVE",
|
||||||
|
siruta,
|
||||||
|
geometrySource: "NO_GEOMETRY",
|
||||||
|
},
|
||||||
|
select: { id: true, objectId: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const staleIds: string[] = [];
|
||||||
|
for (const f of existingNoGeom) {
|
||||||
|
const immPk = -f.objectId; // objectId = -immovablePk for no-geom
|
||||||
|
if (!validImmPks.has(immPk)) {
|
||||||
|
staleIds.push(f.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (staleIds.length > 0) {
|
||||||
|
await prisma.gisFeature.deleteMany({
|
||||||
|
where: { id: { in: staleIds } },
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
`[no-geom-sync] Cleanup: removed ${staleIds.length} stale/invalid no-geom records`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Get existing features from DB (after cleanup)
|
||||||
const existingFeatures = await prisma.gisFeature.findMany({
|
const existingFeatures = await prisma.gisFeature.findMany({
|
||||||
where: { layerId: "TERENURI_ACTIVE", siruta },
|
where: { layerId: "TERENURI_ACTIVE", siruta },
|
||||||
select: { cadastralRef: true, objectId: true },
|
select: { cadastralRef: true, objectId: true },
|
||||||
@@ -433,7 +486,7 @@ export async function syncNoGeometryParcels(
|
|||||||
existingObjIds.add(f.objectId);
|
existingObjIds.add(f.objectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Filter: not yet in DB + quality gate
|
// 4. Filter: not yet in DB + quality gate
|
||||||
// Quality: must be ACTIVE (status=1) AND have identification OR area.
|
// Quality: must be ACTIVE (status=1) AND have identification OR area.
|
||||||
// Items that are inactive, or have no identification AND no area = noise.
|
// Items that are inactive, or have no identification AND no area = noise.
|
||||||
let filteredOut = 0;
|
let filteredOut = 0;
|
||||||
@@ -475,6 +528,7 @@ export async function syncNoGeometryParcels(
|
|||||||
return {
|
return {
|
||||||
imported: 0,
|
imported: 0,
|
||||||
skipped: filteredOut,
|
skipped: filteredOut,
|
||||||
|
cleaned: staleIds.length,
|
||||||
errors: 0,
|
errors: 0,
|
||||||
status: "done",
|
status: "done",
|
||||||
};
|
};
|
||||||
@@ -594,10 +648,23 @@ export async function syncNoGeometryParcels(
|
|||||||
options?.onProgress?.(done, total, "Import parcele fără geometrie");
|
options?.onProgress?.(done, total, "Import parcele fără geometrie");
|
||||||
}
|
}
|
||||||
|
|
||||||
return { imported, skipped: skipped + filteredOut, errors, status: "done" };
|
return {
|
||||||
|
imported,
|
||||||
|
skipped: skipped + filteredOut,
|
||||||
|
cleaned: staleIds.length,
|
||||||
|
errors,
|
||||||
|
status: "done",
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const msg = error instanceof Error ? error.message : "Unknown error";
|
const msg = error instanceof Error ? error.message : "Unknown error";
|
||||||
return { imported: 0, skipped: 0, errors: 0, status: "error", error: msg };
|
return {
|
||||||
|
imported: 0,
|
||||||
|
skipped: 0,
|
||||||
|
cleaned: 0,
|
||||||
|
errors: 0,
|
||||||
|
status: "error",
|
||||||
|
error: msg,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
const { PrismaClient } = require("@prisma/client");
|
||||||
|
const p = new PrismaClient({
|
||||||
|
datasourceUrl:
|
||||||
|
"postgresql://architools_user:stictMyFon34!_gonY@10.10.10.166:5432/architools_db?schema=public",
|
||||||
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const features = await p.$queryRawUnsafe(`
|
||||||
|
SELECT "layerId", siruta, "geometrySource", COUNT(*)::int as nr
|
||||||
|
FROM "GisFeature"
|
||||||
|
GROUP BY "layerId", siruta, "geometrySource"
|
||||||
|
ORDER BY "layerId", siruta, "geometrySource"
|
||||||
|
`);
|
||||||
|
console.log("=== GisFeature ===");
|
||||||
|
console.table(features);
|
||||||
|
|
||||||
|
const syncs = await p.$queryRawUnsafe(`
|
||||||
|
SELECT "layerId", siruta, status, COUNT(*)::int as nr
|
||||||
|
FROM "GisSyncRun"
|
||||||
|
GROUP BY "layerId", siruta, status
|
||||||
|
ORDER BY "layerId", siruta
|
||||||
|
`);
|
||||||
|
console.log("=== GisSyncRun ===");
|
||||||
|
console.table(syncs);
|
||||||
|
|
||||||
|
const uats = await p.$queryRawUnsafe(`
|
||||||
|
SELECT siruta, name, county, "workspacePk"
|
||||||
|
FROM "GisUat"
|
||||||
|
WHERE "workspacePk" IS NOT NULL AND "workspacePk" > 0
|
||||||
|
LIMIT 20
|
||||||
|
`);
|
||||||
|
console.log("=== GisUat (with workspacePk) ===");
|
||||||
|
console.table(uats);
|
||||||
|
|
||||||
|
await p.$disconnect();
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user