add test, and fix script

This commit is contained in:
Warunee Tamkoo 2026-02-09 17:45:50 +07:00
parent 638362df1c
commit f9d626a499
6 changed files with 653 additions and 73 deletions

View file

@ -7813,7 +7813,7 @@ export class OrganizationController extends Controller {
*
*/
@Post("move-draft-to-current/{rootDnaId}")
async moveDraftToCurrent(@Request() request: RequestWithUser) {
async moveDraftToCurrent(@Path() rootDnaId: string, @Request() request: RequestWithUser) {
const queryRunner = AppDataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
@ -7850,14 +7850,14 @@ export class OrganizationController extends Controller {
const [orgRootDraft, orgRootCurrent] = await Promise.all([
this.orgRootRepository.findOne({
where: {
ancestorDNA: request.params.rootDnaId,
ancestorDNA: rootDnaId,
orgRevisionId: drafRevisionId,
},
select: ["id"],
}),
this.orgRootRepository.findOne({
where: {
ancestorDNA: request.params.rootDnaId,
ancestorDNA: rootDnaId,
orgRevisionId: currentRevisionId,
},
select: ["id"],
@ -7873,43 +7873,62 @@ export class OrganizationController extends Controller {
orgChild1: { byAncestorDNA: new Map(), byDraftId: new Map() },
orgChild2: { byAncestorDNA: new Map(), byDraftId: new Map() },
orgChild3: { byAncestorDNA: new Map(), byDraftId: new Map() },
orgChild4: { byAncestorDNA: new Map(), byDraftId: new Map() }
orgChild4: { byAncestorDNA: new Map(), byDraftId: new Map() },
};
// Process from bottom (Child4) to top (Root) to handle foreign key constraints
// Child4 (leaf nodes - no children depending on them)
allMappings.orgChild4 = await this.syncOrgLevel(
queryRunner, OrgChild4, this.child4Repository,
drafRevisionId, currentRevisionId,
request.params.rootDnaId, allMappings
queryRunner,
OrgChild4,
this.child4Repository,
drafRevisionId,
currentRevisionId,
rootDnaId,
allMappings,
);
// Child3
allMappings.orgChild3 = await this.syncOrgLevel(
queryRunner, OrgChild3, this.child3Repository,
drafRevisionId, currentRevisionId,
request.params.rootDnaId, allMappings
queryRunner,
OrgChild3,
this.child3Repository,
drafRevisionId,
currentRevisionId,
rootDnaId,
allMappings,
);
// Child2
allMappings.orgChild2 = await this.syncOrgLevel(
queryRunner, OrgChild2, this.child2Repository,
drafRevisionId, currentRevisionId,
request.params.rootDnaId, allMappings
queryRunner,
OrgChild2,
this.child2Repository,
drafRevisionId,
currentRevisionId,
rootDnaId,
allMappings,
);
// Child1
allMappings.orgChild1 = await this.syncOrgLevel(
queryRunner, OrgChild1, this.child1Repository,
drafRevisionId, currentRevisionId,
request.params.rootDnaId, allMappings
queryRunner,
OrgChild1,
this.child1Repository,
drafRevisionId,
currentRevisionId,
rootDnaId,
allMappings,
);
// OrgRoot (root level - no parent mapping needed)
allMappings.orgRoot = await this.syncOrgLevel(
queryRunner, OrgRoot, this.orgRootRepository,
drafRevisionId, currentRevisionId,
request.params.rootDnaId
queryRunner,
OrgRoot,
this.orgRootRepository,
drafRevisionId,
currentRevisionId,
rootDnaId,
);
// Part 2: Sync position data using new org IDs from Part 1
@ -7939,17 +7958,17 @@ export class OrganizationController extends Controller {
// Clear current_holderId for positions that will have new holders
const nextHolderIds = posMasterDraft
.filter(x => x.next_holderId != null)
.map(x => x.next_holderId);
.filter((x) => x.next_holderId != null)
.map((x) => x.next_holderId);
if (nextHolderIds.length > 0) {
await queryRunner.manager.update(
PosMaster,
{
orgRevisionId: currentRevisionId,
current_holderId: In(nextHolderIds)
current_holderId: In(nextHolderIds),
},
{ current_holderId: null, isSit: false }
{ current_holderId: null, isSit: false },
);
}
@ -7974,29 +7993,21 @@ export class OrganizationController extends Controller {
});
// Build lookup map
const currentByDNA = new Map(
posMasterCurrent.map(p => [p.ancestorDNA, p])
);
const currentByDNA = new Map(posMasterCurrent.map((p) => [p.ancestorDNA, p]));
// 2.3 Batch DELETE: positions in current but not in draft
const toDelete = posMasterCurrent.filter(
curr => !posMasterDraft.some(d => d.ancestorDNA === curr.ancestorDNA)
(curr) => !posMasterDraft.some((d) => d.ancestorDNA === curr.ancestorDNA),
);
if (toDelete.length > 0) {
const toDeleteIds = toDelete.map(p => p.id);
const toDeleteIds = toDelete.map((p) => p.id);
// Cascade delete positions first
await queryRunner.manager.delete(
Position,
{ posMasterId: In(toDeleteIds) }
);
await queryRunner.manager.delete(Position, { posMasterId: In(toDeleteIds) });
// Then delete posMaster records
await queryRunner.manager.delete(
PosMaster,
toDeleteIds
);
await queryRunner.manager.delete(PosMaster, toDeleteIds);
}
// 2.4 Process draft positions (UPDATE or INSERT)
@ -8077,7 +8088,7 @@ export class OrganizationController extends Controller {
// saved is an array, map each to its draft ID
if (Array.isArray(saved)) {
for (let i = 0; i < saved.length; i++) {
const draftPos = posMasterDraft.filter(d => !currentByDNA.has(d.ancestorDNA))[i];
const draftPos = posMasterDraft.filter((d) => !currentByDNA.has(d.ancestorDNA))[i];
if (draftPos && saved[i]) {
posMasterMapping.set(draftPos.id, saved[i].id);
}
@ -8092,7 +8103,7 @@ export class OrganizationController extends Controller {
draftPosMasterId,
currentPosMasterId,
drafRevisionId,
currentRevisionId
currentRevisionId,
);
}
@ -8107,10 +8118,7 @@ export class OrganizationController extends Controller {
/**
* Helper function: Map draft ID to current ID using the mapping
*/
private resolveOrgId(
draftId: string | null,
mapping: OrgIdMapping
): string | null {
private resolveOrgId(draftId: string | null, mapping: OrgIdMapping): string | null {
if (!draftId) return null;
return mapping.byDraftId.get(draftId) ?? null;
}
@ -8121,10 +8129,10 @@ export class OrganizationController extends Controller {
private async cascadeDeletePositions(
queryRunner: any,
node: any,
entityClass: any
entityClass: any,
): Promise<void> {
const whereClause: any = {
orgRevisionId: node.orgRevisionId
orgRevisionId: node.orgRevisionId,
};
// Determine which FK field to use based on entity type
@ -8154,22 +8162,22 @@ export class OrganizationController extends Controller {
draftRevisionId: string,
currentRevisionId: string,
rootDnaId: string,
parentMappings?: AllOrgMappings
parentMappings?: AllOrgMappings,
): Promise<OrgIdMapping> {
// 1. Fetch draft and current nodes under the given rootDnaId
const [draftNodes, currentNodes] = await Promise.all([
repository.find({
where: {
orgRevisionId: draftRevisionId,
ancestorDNA: Like(`${rootDnaId}%`)
}
ancestorDNA: Like(`${rootDnaId}%`),
},
}),
repository.find({
where: {
orgRevisionId: currentRevisionId,
ancestorDNA: Like(`${rootDnaId}%`)
}
})
ancestorDNA: Like(`${rootDnaId}%`),
},
}),
]);
// 2. Build lookup maps for efficient matching by ancestorDNA
@ -8178,7 +8186,7 @@ export class OrganizationController extends Controller {
const mapping: OrgIdMapping = {
byAncestorDNA: new Map(),
byDraftId: new Map()
byDraftId: new Map(),
};
// 3. DELETE: Current nodes not in draft (cascade delete positions first)
@ -8203,36 +8211,46 @@ export class OrganizationController extends Controller {
// Map parent IDs based on entity level
if (entityClass === OrgChild1 && draft.orgRootId && parentMappings) {
updateData.orgRootId = parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId;
updateData.orgRootId =
parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId;
} else if (entityClass === OrgChild2) {
if (draft.orgRootId && parentMappings) {
updateData.orgRootId = parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId;
updateData.orgRootId =
parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId;
}
if (draft.orgChild1Id && parentMappings) {
updateData.orgChild1Id = parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id) ?? draft.orgChild1Id;
updateData.orgChild1Id =
parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id) ?? draft.orgChild1Id;
}
} else if (entityClass === OrgChild3) {
if (draft.orgRootId && parentMappings) {
updateData.orgRootId = parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId;
updateData.orgRootId =
parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId;
}
if (draft.orgChild1Id && parentMappings) {
updateData.orgChild1Id = parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id) ?? draft.orgChild1Id;
updateData.orgChild1Id =
parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id) ?? draft.orgChild1Id;
}
if (draft.orgChild2Id && parentMappings) {
updateData.orgChild2Id = parentMappings.orgChild2.byDraftId.get(draft.orgChild2Id) ?? draft.orgChild2Id;
updateData.orgChild2Id =
parentMappings.orgChild2.byDraftId.get(draft.orgChild2Id) ?? draft.orgChild2Id;
}
} else if (entityClass === OrgChild4) {
if (draft.orgRootId && parentMappings) {
updateData.orgRootId = parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId;
updateData.orgRootId =
parentMappings.orgRoot.byDraftId.get(draft.orgRootId) ?? draft.orgRootId;
}
if (draft.orgChild1Id && parentMappings) {
updateData.orgChild1Id = parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id) ?? draft.orgChild1Id;
updateData.orgChild1Id =
parentMappings.orgChild1.byDraftId.get(draft.orgChild1Id) ?? draft.orgChild1Id;
}
if (draft.orgChild2Id && parentMappings) {
updateData.orgChild2Id = parentMappings.orgChild2.byDraftId.get(draft.orgChild2Id) ?? draft.orgChild2Id;
updateData.orgChild2Id =
parentMappings.orgChild2.byDraftId.get(draft.orgChild2Id) ?? draft.orgChild2Id;
}
if (draft.orgChild3Id && parentMappings) {
updateData.orgChild3Id = parentMappings.orgChild3.byDraftId.get(draft.orgChild3Id) ?? draft.orgChild3Id;
updateData.orgChild3Id =
parentMappings.orgChild3.byDraftId.get(draft.orgChild3Id) ?? draft.orgChild3Id;
}
}
@ -8304,21 +8322,21 @@ export class OrganizationController extends Controller {
draftPosMasterId: string,
currentPosMasterId: string,
draftRevisionId: string,
currentRevisionId: string
currentRevisionId: string,
): Promise<void> {
// Fetch draft and current positions for this posMaster
const [draftPositions, currentPositions] = await Promise.all([
queryRunner.manager.find(Position, {
where: {
posMasterId: draftPosMasterId
posMasterId: draftPosMasterId,
},
order: { orderNo: 'ASC' }
order: { orderNo: "ASC" },
}),
queryRunner.manager.find(Position, {
where: {
posMasterId: currentPosMasterId
}
})
posMasterId: currentPosMasterId,
},
}),
]);
// If no draft positions, delete all current positions
@ -8326,16 +8344,14 @@ export class OrganizationController extends Controller {
if (currentPositions.length > 0) {
await queryRunner.manager.delete(
Position,
currentPositions.map((p: any) => p.id)
currentPositions.map((p: any) => p.id),
);
}
return;
}
// Build maps for tracking
const currentByOrderNo = new Map(
currentPositions.map((p: any) => [p.orderNo, p])
);
const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p]));
// DELETE: Current positions not in draft (by orderNo)
const draftOrderNos = new Set(draftPositions.map((p: any) => p.orderNo));
@ -8344,7 +8360,7 @@ export class OrganizationController extends Controller {
if (toDelete.length > 0) {
await queryRunner.manager.delete(
Position,
toDelete.map((p: any) => p.id)
toDelete.map((p: any) => p.id),
);
}