Skip to content

Commit bc31e65

Browse files
committed
fix(V2): batch cascade unit reference checks
1 parent 71ffdc1 commit bc31e65

3 files changed

Lines changed: 73 additions & 20 deletions

File tree

src/controllers/v2/project-v2.controller.js

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,11 @@ import { projectV2Schema } from '../../validations/v2/project-v2.validations.js'
3636
import { formatModelAssociationName } from '../../utils/model-utils.js';
3737
import { resolveOrgUid } from '../../utils/owner-utils.js';
3838
import { getProjectCascadeUnits, stageProjectChildDeletes } from '../../utils/v2-cascade-delete.js';
39-
import { checkReferences, buildReferenceConflictBody } from '../../utils/v2-reference-guards.js';
39+
import { checkReferences, buildReferenceConflictBody, checkUnitReferencesForIds } from '../../utils/v2-reference-guards.js';
4040

4141
const checkProjectCascadeUnitReferences = async (projectId) => {
4242
const units = await getProjectCascadeUnits(projectId);
43-
const referencesByTable = new Map();
44-
45-
for (const unit of units) {
46-
const unitRefResult = await checkReferences('unit', unit.cadTrustUnitId);
47-
for (const reference of unitRefResult.references) {
48-
const existing = referencesByTable.get(reference.table);
49-
if (existing) {
50-
existing.count += reference.count;
51-
} else {
52-
referencesByTable.set(reference.table, { ...reference });
53-
}
54-
}
55-
}
56-
57-
const references = [...referencesByTable.values()];
58-
return {
59-
hasReferences: references.length > 0,
60-
references,
61-
};
43+
return checkUnitReferencesForIds(units.map((unit) => unit.cadTrustUnitId));
6244
};
6345

6446
export const create = async (req, res) => {

src/utils/v2-reference-guards.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,35 @@ const countStagedReferences = async (table, snakeFkField, recordId, matchPredica
197197
return count;
198198
};
199199

200+
const countStagedReferencesForIds = async (table, snakeFkField, recordIds) => {
201+
const recordIdSet = new Set(recordIds);
202+
const stagedRows = await StagingV2.findAll({
203+
where: {
204+
table,
205+
action: { [Op.in]: ['INSERT', 'UPDATE'] },
206+
committed: false,
207+
failed_commit: false,
208+
},
209+
raw: true,
210+
});
211+
212+
let count = 0;
213+
for (const stagedRow of stagedRows) {
214+
try {
215+
const parsedData = JSON.parse(stagedRow.data);
216+
const records = Array.isArray(parsedData) ? parsedData : [parsedData];
217+
for (const record of records) {
218+
if (recordIdSet.has(record?.[snakeFkField])) {
219+
count += 1;
220+
}
221+
}
222+
} catch {
223+
continue;
224+
}
225+
}
226+
return count;
227+
};
228+
200229
const findPendingStagedDeleteIds = async (table, primaryKeyField) => {
201230
const stagedRows = await StagingV2.findAll({
202231
where: {
@@ -270,6 +299,42 @@ export const checkReferences = async (table, recordId) => {
270299
};
271300
};
272301

302+
export const checkUnitReferencesForIds = async (unitIds) => {
303+
const recordIds = unitIds.filter(Boolean);
304+
if (recordIds.length === 0) {
305+
return { hasReferences: false, references: [] };
306+
}
307+
308+
const definitions = REFERENCE_MAP.unit;
309+
const references = [];
310+
311+
for (const def of definitions) {
312+
const { model, fkField, table: refTable, label } = def;
313+
const primaryKeyAttr = model.primaryKeyAttribute;
314+
const primaryKeyField = getV2PrimaryKeyField(refTable) || toSnakeCase(primaryKeyAttr);
315+
const pendingDeleteIds = await findPendingStagedDeleteIds(refTable, primaryKeyField);
316+
const mainRecords = await model.findAll({
317+
where: { [fkField]: { [Op.in]: recordIds } },
318+
raw: true,
319+
});
320+
const mainCount = mainRecords.filter((row) => {
321+
const rowId = row?.[primaryKeyAttr] ?? row?.[primaryKeyField];
322+
return !pendingDeleteIds.has(rowId);
323+
}).length;
324+
325+
const stagedCount = await countStagedReferencesForIds(refTable, toSnakeCase(fkField), recordIds);
326+
const totalCount = mainCount + stagedCount;
327+
if (totalCount > 0) {
328+
references.push({ table: refTable, count: totalCount, label });
329+
}
330+
}
331+
332+
return {
333+
hasReferences: references.length > 0,
334+
references,
335+
};
336+
};
337+
273338
/**
274339
* Build a 409 response body from a reference check result.
275340
*

tests/v2/integration/project-v2.spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2247,6 +2247,12 @@ Test Registry,REQ-001,${testProgram.cadTrustProgramId}`;
22472247
projectRegistryName: 'Test Registry',
22482248
projectId: 'UPD-REQ-001',
22492249
projectName: 'Full Project',
2250+
projectLink: 'https://example.com/update-required',
2251+
projectSector: ['Agriculture'],
2252+
projectType: ['Landfill gas'],
2253+
projectStatus: 'Listed',
2254+
projectStatusDate: '2024-01-01',
2255+
projectUnitMetric: 'tCO2e',
22502256
orgUid: homeOrgId,
22512257
}));
22522258

0 commit comments

Comments
 (0)