add test, and fix script
This commit is contained in:
parent
638362df1c
commit
f9d626a499
6 changed files with 653 additions and 73 deletions
27
jest.config.js
Normal file
27
jest.config.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
roots: ['<rootDir>/src'],
|
||||||
|
testMatch: ['**/__tests__/**/*.spec.ts', '**/__tests__/**/*.test.ts'],
|
||||||
|
transform: {
|
||||||
|
'^.+\\.ts$': ['ts-jest', {
|
||||||
|
diagnostics: {
|
||||||
|
ignoreCodes: [151002],
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
collectCoverageFrom: [
|
||||||
|
'src/**/*.ts',
|
||||||
|
'!src/**/*.d.ts',
|
||||||
|
'!src/app.ts',
|
||||||
|
'!src/database/**',
|
||||||
|
'!src/__tests__/**',
|
||||||
|
],
|
||||||
|
coverageDirectory: 'coverage',
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^@/(.*)$': '<rootDir>/src/$1',
|
||||||
|
},
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
|
||||||
|
testTimeout: 10000,
|
||||||
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
||||||
|
};
|
||||||
|
|
@ -10,7 +10,10 @@
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"build": "tsoa spec-and-routes && tsc",
|
"build": "tsoa spec-and-routes && tsc",
|
||||||
"migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/database/data-source.ts",
|
"migration:generate": "typeorm-ts-node-commonjs migration:generate -d src/database/data-source.ts",
|
||||||
"migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts"
|
"migration:run": "typeorm-ts-node-commonjs migration:run -d src/database/data-source.ts",
|
||||||
|
"test": "jest",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"test:coverage": "jest --coverage"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
@ -19,12 +22,15 @@
|
||||||
"@types/amqplib": "^0.10.5",
|
"@types/amqplib": "^0.10.5",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/jest": "^29.5.11",
|
||||||
"@types/node": "^20.11.5",
|
"@types/node": "^20.11.5",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
"@types/swagger-ui-express": "^4.1.6",
|
"@types/swagger-ui-express": "^4.1.6",
|
||||||
"@types/ws": "^8.5.14",
|
"@types/ws": "^8.5.14",
|
||||||
|
"jest": "^29.7.0",
|
||||||
"nodemon": "^3.0.3",
|
"nodemon": "^3.0.3",
|
||||||
"prettier": "^3.2.2",
|
"prettier": "^3.2.2",
|
||||||
|
"ts-jest": "^29.1.1",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
17
src/__tests__/setup.ts
Normal file
17
src/__tests__/setup.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Test setup file for Jest
|
||||||
|
// Mock environment variables
|
||||||
|
process.env.NODE_ENV = 'test';
|
||||||
|
process.env.DB_HOST = 'localhost';
|
||||||
|
process.env.DB_PORT = '3306';
|
||||||
|
process.env.DB_USERNAME = 'test';
|
||||||
|
process.env.DB_PASSWORD = 'test';
|
||||||
|
process.env.DB_DATABASE = 'test_db';
|
||||||
|
|
||||||
|
// Mock console methods to reduce noise in tests
|
||||||
|
global.console = {
|
||||||
|
...console,
|
||||||
|
error: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
log: jest.fn(),
|
||||||
|
};
|
||||||
54
src/__tests__/unit/OrgMapping.spec.ts
Normal file
54
src/__tests__/unit/OrgMapping.spec.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
/**
|
||||||
|
* Unit tests for move-draft-to-current helper functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { OrgIdMapping, AllOrgMappings } from '../../interfaces/OrgMapping';
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
jest.mock('../../database/data-source', () => ({
|
||||||
|
AppDataSource: {
|
||||||
|
createQueryRunner: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('OrgMapping Interfaces', () => {
|
||||||
|
describe('OrgIdMapping', () => {
|
||||||
|
it('should create a valid OrgIdMapping', () => {
|
||||||
|
const mapping: OrgIdMapping = {
|
||||||
|
byAncestorDNA: new Map(),
|
||||||
|
byDraftId: new Map(),
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(mapping.byAncestorDNA).toBeInstanceOf(Map);
|
||||||
|
expect(mapping.byDraftId).toBeInstanceOf(Map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should store and retrieve values correctly', () => {
|
||||||
|
const mapping: OrgIdMapping = {
|
||||||
|
byAncestorDNA: new Map([['dna1', 'id1']]),
|
||||||
|
byDraftId: new Map([['draftId1', 'currentId1']]),
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(mapping.byAncestorDNA.get('dna1')).toBe('id1');
|
||||||
|
expect(mapping.byDraftId.get('draftId1')).toBe('currentId1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AllOrgMappings', () => {
|
||||||
|
it('should create a valid AllOrgMappings', () => {
|
||||||
|
const mappings: AllOrgMappings = {
|
||||||
|
orgRoot: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
||||||
|
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() },
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(mappings.orgRoot).toBeDefined();
|
||||||
|
expect(mappings.orgChild1).toBeDefined();
|
||||||
|
expect(mappings.orgChild2).toBeDefined();
|
||||||
|
expect(mappings.orgChild3).toBeDefined();
|
||||||
|
expect(mappings.orgChild4).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
460
src/__tests__/unit/OrganizationController.spec.ts
Normal file
460
src/__tests__/unit/OrganizationController.spec.ts
Normal file
|
|
@ -0,0 +1,460 @@
|
||||||
|
/**
|
||||||
|
* Unit tests for OrganizationController move-draft-to-current helper functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { OrgIdMapping } from '../../interfaces/OrgMapping';
|
||||||
|
|
||||||
|
// Mock typeorm
|
||||||
|
jest.mock('typeorm', () => ({
|
||||||
|
Entity: jest.fn(),
|
||||||
|
Column: jest.fn(),
|
||||||
|
ManyToOne: jest.fn(),
|
||||||
|
JoinColumn: jest.fn(),
|
||||||
|
OneToMany: jest.fn(),
|
||||||
|
In: jest.fn((val: any) => val),
|
||||||
|
Like: jest.fn((val: any) => val),
|
||||||
|
IsNull: jest.fn(),
|
||||||
|
Not: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock entities
|
||||||
|
jest.mock('../../entities/OrgRoot', () => ({ OrgRoot: {} }));
|
||||||
|
jest.mock('../../entities/OrgChild1', () => ({ OrgChild1: {} }));
|
||||||
|
jest.mock('../../entities/OrgChild2', () => ({ OrgChild2: {} }));
|
||||||
|
jest.mock('../../entities/OrgChild3', () => ({ OrgChild3: {} }));
|
||||||
|
jest.mock('../../entities/OrgChild4', () => ({ OrgChild4: {} }));
|
||||||
|
jest.mock('../../entities/PosMaster', () => ({ PosMaster: {} }));
|
||||||
|
jest.mock('../../entities/Position', () => ({ Position: {} }));
|
||||||
|
|
||||||
|
// Import after mocking
|
||||||
|
import { In, Like } from 'typeorm';
|
||||||
|
import { OrgRoot } from '../../entities/OrgRoot';
|
||||||
|
import { OrgChild1 } from '../../entities/OrgChild1';
|
||||||
|
import { OrgChild2 } from '../../entities/OrgChild2';
|
||||||
|
import { OrgChild3 } from '../../entities/OrgChild3';
|
||||||
|
import { OrgChild4 } from '../../entities/OrgChild4';
|
||||||
|
import { PosMaster } from '../../entities/PosMaster';
|
||||||
|
import { Position } from '../../entities/Position';
|
||||||
|
|
||||||
|
describe('OrganizationController - Helper Functions', () => {
|
||||||
|
let mockQueryRunner: any;
|
||||||
|
let mockController: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Mock queryRunner
|
||||||
|
mockQueryRunner = {
|
||||||
|
manager: {
|
||||||
|
find: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
create: jest.fn(),
|
||||||
|
save: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Import the controller class (we'll need to mock the private methods)
|
||||||
|
// Since we're testing private methods, we'll create a test class
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resolveOrgId()', () => {
|
||||||
|
it('should return null when draftId is null', () => {
|
||||||
|
const mapping: OrgIdMapping = {
|
||||||
|
byAncestorDNA: new Map(),
|
||||||
|
byDraftId: new Map(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simulate the function logic
|
||||||
|
const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => {
|
||||||
|
if (!draftId) return null;
|
||||||
|
return mapping.byDraftId.get(draftId) ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(resolveOrgId(null, mapping)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when draftId is undefined', () => {
|
||||||
|
const mapping: OrgIdMapping = {
|
||||||
|
byAncestorDNA: new Map(),
|
||||||
|
byDraftId: new Map(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveOrgId = (draftId: string | null | undefined, mapping: OrgIdMapping): string | null => {
|
||||||
|
if (!draftId) return null;
|
||||||
|
return mapping.byDraftId.get(draftId) ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(resolveOrgId(undefined, mapping)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return mapped ID when draftId exists in mapping', () => {
|
||||||
|
const mapping: OrgIdMapping = {
|
||||||
|
byAncestorDNA: new Map(),
|
||||||
|
byDraftId: new Map([['draft1', 'current1']]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => {
|
||||||
|
if (!draftId) return null;
|
||||||
|
return mapping.byDraftId.get(draftId) ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(resolveOrgId('draft1', mapping)).toBe('current1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null when draftId does not exist in mapping', () => {
|
||||||
|
const mapping: OrgIdMapping = {
|
||||||
|
byAncestorDNA: new Map(),
|
||||||
|
byDraftId: new Map(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveOrgId = (draftId: string | null, mapping: OrgIdMapping): string | null => {
|
||||||
|
if (!draftId) return null;
|
||||||
|
return mapping.byDraftId.get(draftId) ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(resolveOrgId('nonexistent', mapping)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cascadeDeletePositions()', () => {
|
||||||
|
it('should delete positions with orgRootId when entityClass is OrgRoot', async () => {
|
||||||
|
const node = {
|
||||||
|
id: 'node1',
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
};
|
||||||
|
|
||||||
|
await mockQueryRunner.manager.delete(PosMaster, {
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
orgRootId: 'node1',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, {
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
orgRootId: 'node1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete positions with orgChild1Id when entityClass is OrgChild1', async () => {
|
||||||
|
const node = {
|
||||||
|
id: 'node1',
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
};
|
||||||
|
|
||||||
|
await mockQueryRunner.manager.delete(PosMaster, {
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
orgChild1Id: 'node1',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, {
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
orgChild1Id: 'node1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete positions with orgChild2Id when entityClass is OrgChild2', async () => {
|
||||||
|
const node = {
|
||||||
|
id: 'node1',
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
};
|
||||||
|
|
||||||
|
await mockQueryRunner.manager.delete(PosMaster, {
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
orgChild2Id: 'node1',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, {
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
orgChild2Id: 'node1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete positions with orgChild3Id when entityClass is OrgChild3', async () => {
|
||||||
|
const node = {
|
||||||
|
id: 'node1',
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
};
|
||||||
|
|
||||||
|
await mockQueryRunner.manager.delete(PosMaster, {
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
orgChild3Id: 'node1',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, {
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
orgChild3Id: 'node1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete positions with orgChild4Id when entityClass is OrgChild4', async () => {
|
||||||
|
const node = {
|
||||||
|
id: 'node1',
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
};
|
||||||
|
|
||||||
|
await mockQueryRunner.manager.delete(PosMaster, {
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
orgChild4Id: 'node1',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(PosMaster, {
|
||||||
|
orgRevisionId: 'rev1',
|
||||||
|
orgChild4Id: 'node1',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('syncOrgLevel()', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockQueryRunner.manager.find.mockResolvedValue([]);
|
||||||
|
mockQueryRunner.manager.delete.mockResolvedValue({ affected: 0 });
|
||||||
|
mockQueryRunner.manager.update.mockResolvedValue({ affected: 0 });
|
||||||
|
mockQueryRunner.manager.create.mockReturnValue({});
|
||||||
|
mockQueryRunner.manager.save.mockResolvedValue({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch draft and current nodes with Like filter', async () => {
|
||||||
|
const repository = {
|
||||||
|
find: jest.fn().mockResolvedValue([]),
|
||||||
|
};
|
||||||
|
|
||||||
|
await repository.find({
|
||||||
|
where: {
|
||||||
|
orgRevisionId: 'draftRev1',
|
||||||
|
ancestorDNA: Like('root-dna%'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await repository.find({
|
||||||
|
where: {
|
||||||
|
orgRevisionId: 'currentRev1',
|
||||||
|
ancestorDNA: Like('root-dna%'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(repository.find).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should build lookup maps from draft and current nodes', () => {
|
||||||
|
const draftNodes = [
|
||||||
|
{ id: 'draft1', ancestorDNA: 'root-dna/child1' },
|
||||||
|
{ id: 'draft2', ancestorDNA: 'root-dna/child2' },
|
||||||
|
];
|
||||||
|
const currentNodes = [
|
||||||
|
{ id: 'current1', ancestorDNA: 'root-dna/child1' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const draftByDNA = new Map(draftNodes.map(n => [n.ancestorDNA, n]));
|
||||||
|
const currentByDNA = new Map(currentNodes.map(n => [n.ancestorDNA, n]));
|
||||||
|
|
||||||
|
expect(draftByDNA.size).toBe(2);
|
||||||
|
expect(currentByDNA.size).toBe(1);
|
||||||
|
expect(draftByDNA.get('root-dna/child1')).toEqual(draftNodes[0]);
|
||||||
|
expect(currentByDNA.get('root-dna/child1')).toEqual(currentNodes[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should identify nodes to delete (in current but not in draft)', () => {
|
||||||
|
const draftNodes = [
|
||||||
|
{ id: 'draft1', ancestorDNA: 'root-dna/child1' },
|
||||||
|
];
|
||||||
|
const currentNodes = [
|
||||||
|
{ id: 'current1', ancestorDNA: 'root-dna/child1' },
|
||||||
|
{ id: 'current2', ancestorDNA: 'root-dna/child2' }, // Not in draft
|
||||||
|
];
|
||||||
|
|
||||||
|
const draftByDNA = new Map(draftNodes.map((n: any) => [n.ancestorDNA, n]));
|
||||||
|
const toDelete = currentNodes.filter((curr: any) => !draftByDNA.has(curr.ancestorDNA));
|
||||||
|
|
||||||
|
expect(toDelete).toHaveLength(1);
|
||||||
|
expect(toDelete[0].id).toBe('current2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should identify nodes to update (in both draft and current)', () => {
|
||||||
|
const draftNodes = [
|
||||||
|
{ id: 'draft1', ancestorDNA: 'root-dna/child1' },
|
||||||
|
{ id: 'draft2', ancestorDNA: 'root-dna/child2' },
|
||||||
|
];
|
||||||
|
const currentNodes = [
|
||||||
|
{ id: 'current1', ancestorDNA: 'root-dna/child1' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentByDNA = new Map(currentNodes.map((n: any) => [n.ancestorDNA, n]));
|
||||||
|
const toUpdate = draftNodes.filter((draft: any) => currentByDNA.has(draft.ancestorDNA));
|
||||||
|
|
||||||
|
expect(toUpdate).toHaveLength(1);
|
||||||
|
expect(toUpdate[0].id).toBe('draft1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should identify nodes to insert (in draft but not in current)', () => {
|
||||||
|
const draftNodes = [
|
||||||
|
{ id: 'draft1', ancestorDNA: 'root-dna/child1' },
|
||||||
|
{ id: 'draft2', ancestorDNA: 'root-dna/child2' },
|
||||||
|
];
|
||||||
|
const currentNodes = [
|
||||||
|
{ id: 'current1', ancestorDNA: 'root-dna/child1' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentByDNA = new Map(currentNodes.map((n: any) => [n.ancestorDNA, n]));
|
||||||
|
const toInsert = draftNodes.filter((draft: any) => !currentByDNA.has(draft.ancestorDNA));
|
||||||
|
|
||||||
|
expect(toInsert).toHaveLength(1);
|
||||||
|
expect(toInsert[0].id).toBe('draft2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return correct mapping after sync', () => {
|
||||||
|
const mapping: OrgIdMapping = {
|
||||||
|
byAncestorDNA: new Map([
|
||||||
|
['root-dna/child1', 'current1'],
|
||||||
|
['root-dna/child2', 'current2'],
|
||||||
|
]),
|
||||||
|
byDraftId: new Map([
|
||||||
|
['draft1', 'current1'],
|
||||||
|
['draft2', 'current2'],
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(mapping.byAncestorDNA.get('root-dna/child1')).toBe('current1');
|
||||||
|
expect(mapping.byDraftId.get('draft1')).toBe('current1');
|
||||||
|
expect(mapping.byDraftId.get('draft2')).toBe('current2');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('syncPositionsForPosMaster()', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockQueryRunner.manager.find.mockResolvedValue([]);
|
||||||
|
mockQueryRunner.manager.delete.mockResolvedValue({ affected: 0 });
|
||||||
|
mockQueryRunner.manager.update.mockResolvedValue({ affected: 0 });
|
||||||
|
mockQueryRunner.manager.create.mockReturnValue({});
|
||||||
|
mockQueryRunner.manager.save.mockResolvedValue({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch draft and current positions for a posMaster', async () => {
|
||||||
|
const draftPosMasterId = 'draft-pos-1';
|
||||||
|
const currentPosMasterId = 'current-pos-1';
|
||||||
|
|
||||||
|
mockQueryRunner.manager.find
|
||||||
|
.mockResolvedValueOnce([{ id: 'pos1', posMasterId: draftPosMasterId }])
|
||||||
|
.mockResolvedValueOnce([{ id: 'pos2', posMasterId: currentPosMasterId }]);
|
||||||
|
|
||||||
|
await mockQueryRunner.manager.find(Position, {
|
||||||
|
where: { posMasterId: draftPosMasterId },
|
||||||
|
order: { orderNo: 'ASC' },
|
||||||
|
});
|
||||||
|
|
||||||
|
await mockQueryRunner.manager.find(Position, {
|
||||||
|
where: { posMasterId: currentPosMasterId },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockQueryRunner.manager.find).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete all current positions when no draft positions exist', async () => {
|
||||||
|
const currentPositions = [
|
||||||
|
{ id: 'pos1', orderNo: 1 },
|
||||||
|
{ id: 'pos2', orderNo: 2 },
|
||||||
|
];
|
||||||
|
|
||||||
|
mockQueryRunner.manager.find
|
||||||
|
.mockResolvedValueOnce([]) // No draft positions
|
||||||
|
.mockResolvedValueOnce(currentPositions);
|
||||||
|
|
||||||
|
await mockQueryRunner.manager.delete(Position, ['pos1', 'pos2']);
|
||||||
|
|
||||||
|
expect(mockQueryRunner.manager.delete).toHaveBeenCalledWith(Position, ['pos1', 'pos2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete positions not in draft (by orderNo)', async () => {
|
||||||
|
const draftPositions = [
|
||||||
|
{ id: 'dpos1', orderNo: 1 },
|
||||||
|
{ id: 'dpos2', orderNo: 2 },
|
||||||
|
];
|
||||||
|
const currentPositions = [
|
||||||
|
{ id: 'cpos1', orderNo: 1 },
|
||||||
|
{ id: 'cpos2', orderNo: 2 },
|
||||||
|
{ id: 'cpos3', orderNo: 3 }, // Not in draft
|
||||||
|
];
|
||||||
|
|
||||||
|
mockQueryRunner.manager.find
|
||||||
|
.mockResolvedValueOnce(draftPositions)
|
||||||
|
.mockResolvedValueOnce(currentPositions);
|
||||||
|
|
||||||
|
const draftOrderNos = new Set(draftPositions.map((p: any) => p.orderNo));
|
||||||
|
const toDelete = currentPositions.filter((p: any) => !draftOrderNos.has(p.orderNo));
|
||||||
|
|
||||||
|
expect(toDelete).toHaveLength(1);
|
||||||
|
expect(toDelete[0].id).toBe('cpos3');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update existing positions (matched by orderNo)', async () => {
|
||||||
|
const draftPositions = [
|
||||||
|
{
|
||||||
|
id: 'dpos1',
|
||||||
|
orderNo: 1,
|
||||||
|
positionName: 'Updated Name',
|
||||||
|
positionField: 'field1',
|
||||||
|
posTypeId: 'type1',
|
||||||
|
posLevelId: 'level1',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const currentPositions = [
|
||||||
|
{ id: 'cpos1', orderNo: 1, positionName: 'Old Name' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p]));
|
||||||
|
const draftPos = draftPositions[0];
|
||||||
|
const current = currentByOrderNo.get(draftPos.orderNo);
|
||||||
|
|
||||||
|
expect(current).toBeDefined();
|
||||||
|
|
||||||
|
if (current) {
|
||||||
|
const updateData = {
|
||||||
|
positionName: draftPos.positionName,
|
||||||
|
positionField: draftPos.positionField,
|
||||||
|
posTypeId: draftPos.posTypeId,
|
||||||
|
posLevelId: draftPos.posLevelId,
|
||||||
|
};
|
||||||
|
|
||||||
|
await mockQueryRunner.manager.update(Position, current.id, updateData);
|
||||||
|
|
||||||
|
expect(mockQueryRunner.manager.update).toHaveBeenCalledWith(
|
||||||
|
Position,
|
||||||
|
'cpos1',
|
||||||
|
expect.objectContaining({ positionName: 'Updated Name' })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert new positions not in current', async () => {
|
||||||
|
const draftPositions = [
|
||||||
|
{ id: 'dpos1', orderNo: 1, positionName: 'New Position' },
|
||||||
|
];
|
||||||
|
const currentPositions: any[] = [];
|
||||||
|
|
||||||
|
const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p]));
|
||||||
|
const draftPos = draftPositions[0];
|
||||||
|
const current = currentByOrderNo.get(draftPos.orderNo);
|
||||||
|
const currentPosMasterId = 'current-pos-1';
|
||||||
|
|
||||||
|
expect(current).toBeUndefined();
|
||||||
|
|
||||||
|
if (!current) {
|
||||||
|
const newPosition = {
|
||||||
|
...draftPos,
|
||||||
|
id: undefined,
|
||||||
|
posMasterId: currentPosMasterId,
|
||||||
|
};
|
||||||
|
|
||||||
|
await mockQueryRunner.manager.create(Position, newPosition);
|
||||||
|
await mockQueryRunner.manager.save(newPosition);
|
||||||
|
|
||||||
|
expect(mockQueryRunner.manager.create).toHaveBeenCalledWith(Position, {
|
||||||
|
...draftPos,
|
||||||
|
id: undefined,
|
||||||
|
posMasterId: currentPosMasterId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -7813,7 +7813,7 @@ export class OrganizationController extends Controller {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Post("move-draft-to-current/{rootDnaId}")
|
@Post("move-draft-to-current/{rootDnaId}")
|
||||||
async moveDraftToCurrent(@Request() request: RequestWithUser) {
|
async moveDraftToCurrent(@Path() rootDnaId: string, @Request() request: RequestWithUser) {
|
||||||
const queryRunner = AppDataSource.createQueryRunner();
|
const queryRunner = AppDataSource.createQueryRunner();
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
await queryRunner.startTransaction();
|
await queryRunner.startTransaction();
|
||||||
|
|
@ -7850,14 +7850,14 @@ export class OrganizationController extends Controller {
|
||||||
const [orgRootDraft, orgRootCurrent] = await Promise.all([
|
const [orgRootDraft, orgRootCurrent] = await Promise.all([
|
||||||
this.orgRootRepository.findOne({
|
this.orgRootRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
ancestorDNA: request.params.rootDnaId,
|
ancestorDNA: rootDnaId,
|
||||||
orgRevisionId: drafRevisionId,
|
orgRevisionId: drafRevisionId,
|
||||||
},
|
},
|
||||||
select: ["id"],
|
select: ["id"],
|
||||||
}),
|
}),
|
||||||
this.orgRootRepository.findOne({
|
this.orgRootRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
ancestorDNA: request.params.rootDnaId,
|
ancestorDNA: rootDnaId,
|
||||||
orgRevisionId: currentRevisionId,
|
orgRevisionId: currentRevisionId,
|
||||||
},
|
},
|
||||||
select: ["id"],
|
select: ["id"],
|
||||||
|
|
@ -7873,43 +7873,62 @@ export class OrganizationController extends Controller {
|
||||||
orgChild1: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
orgChild1: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
||||||
orgChild2: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
orgChild2: { byAncestorDNA: new Map(), byDraftId: new Map() },
|
||||||
orgChild3: { 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
|
// Process from bottom (Child4) to top (Root) to handle foreign key constraints
|
||||||
// Child4 (leaf nodes - no children depending on them)
|
// Child4 (leaf nodes - no children depending on them)
|
||||||
allMappings.orgChild4 = await this.syncOrgLevel(
|
allMappings.orgChild4 = await this.syncOrgLevel(
|
||||||
queryRunner, OrgChild4, this.child4Repository,
|
queryRunner,
|
||||||
drafRevisionId, currentRevisionId,
|
OrgChild4,
|
||||||
request.params.rootDnaId, allMappings
|
this.child4Repository,
|
||||||
|
drafRevisionId,
|
||||||
|
currentRevisionId,
|
||||||
|
rootDnaId,
|
||||||
|
allMappings,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Child3
|
// Child3
|
||||||
allMappings.orgChild3 = await this.syncOrgLevel(
|
allMappings.orgChild3 = await this.syncOrgLevel(
|
||||||
queryRunner, OrgChild3, this.child3Repository,
|
queryRunner,
|
||||||
drafRevisionId, currentRevisionId,
|
OrgChild3,
|
||||||
request.params.rootDnaId, allMappings
|
this.child3Repository,
|
||||||
|
drafRevisionId,
|
||||||
|
currentRevisionId,
|
||||||
|
rootDnaId,
|
||||||
|
allMappings,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Child2
|
// Child2
|
||||||
allMappings.orgChild2 = await this.syncOrgLevel(
|
allMappings.orgChild2 = await this.syncOrgLevel(
|
||||||
queryRunner, OrgChild2, this.child2Repository,
|
queryRunner,
|
||||||
drafRevisionId, currentRevisionId,
|
OrgChild2,
|
||||||
request.params.rootDnaId, allMappings
|
this.child2Repository,
|
||||||
|
drafRevisionId,
|
||||||
|
currentRevisionId,
|
||||||
|
rootDnaId,
|
||||||
|
allMappings,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Child1
|
// Child1
|
||||||
allMappings.orgChild1 = await this.syncOrgLevel(
|
allMappings.orgChild1 = await this.syncOrgLevel(
|
||||||
queryRunner, OrgChild1, this.child1Repository,
|
queryRunner,
|
||||||
drafRevisionId, currentRevisionId,
|
OrgChild1,
|
||||||
request.params.rootDnaId, allMappings
|
this.child1Repository,
|
||||||
|
drafRevisionId,
|
||||||
|
currentRevisionId,
|
||||||
|
rootDnaId,
|
||||||
|
allMappings,
|
||||||
);
|
);
|
||||||
|
|
||||||
// OrgRoot (root level - no parent mapping needed)
|
// OrgRoot (root level - no parent mapping needed)
|
||||||
allMappings.orgRoot = await this.syncOrgLevel(
|
allMappings.orgRoot = await this.syncOrgLevel(
|
||||||
queryRunner, OrgRoot, this.orgRootRepository,
|
queryRunner,
|
||||||
drafRevisionId, currentRevisionId,
|
OrgRoot,
|
||||||
request.params.rootDnaId
|
this.orgRootRepository,
|
||||||
|
drafRevisionId,
|
||||||
|
currentRevisionId,
|
||||||
|
rootDnaId,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Part 2: Sync position data using new org IDs from Part 1
|
// 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
|
// Clear current_holderId for positions that will have new holders
|
||||||
const nextHolderIds = posMasterDraft
|
const nextHolderIds = posMasterDraft
|
||||||
.filter(x => x.next_holderId != null)
|
.filter((x) => x.next_holderId != null)
|
||||||
.map(x => x.next_holderId);
|
.map((x) => x.next_holderId);
|
||||||
|
|
||||||
if (nextHolderIds.length > 0) {
|
if (nextHolderIds.length > 0) {
|
||||||
await queryRunner.manager.update(
|
await queryRunner.manager.update(
|
||||||
PosMaster,
|
PosMaster,
|
||||||
{
|
{
|
||||||
orgRevisionId: currentRevisionId,
|
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
|
// Build lookup map
|
||||||
const currentByDNA = new Map(
|
const currentByDNA = new Map(posMasterCurrent.map((p) => [p.ancestorDNA, p]));
|
||||||
posMasterCurrent.map(p => [p.ancestorDNA, p])
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2.3 Batch DELETE: positions in current but not in draft
|
// 2.3 Batch DELETE: positions in current but not in draft
|
||||||
const toDelete = posMasterCurrent.filter(
|
const toDelete = posMasterCurrent.filter(
|
||||||
curr => !posMasterDraft.some(d => d.ancestorDNA === curr.ancestorDNA)
|
(curr) => !posMasterDraft.some((d) => d.ancestorDNA === curr.ancestorDNA),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (toDelete.length > 0) {
|
if (toDelete.length > 0) {
|
||||||
const toDeleteIds = toDelete.map(p => p.id);
|
const toDeleteIds = toDelete.map((p) => p.id);
|
||||||
|
|
||||||
// Cascade delete positions first
|
// Cascade delete positions first
|
||||||
await queryRunner.manager.delete(
|
await queryRunner.manager.delete(Position, { posMasterId: In(toDeleteIds) });
|
||||||
Position,
|
|
||||||
{ posMasterId: In(toDeleteIds) }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Then delete posMaster records
|
// Then delete posMaster records
|
||||||
await queryRunner.manager.delete(
|
await queryRunner.manager.delete(PosMaster, toDeleteIds);
|
||||||
PosMaster,
|
|
||||||
toDeleteIds
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.4 Process draft positions (UPDATE or INSERT)
|
// 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
|
// saved is an array, map each to its draft ID
|
||||||
if (Array.isArray(saved)) {
|
if (Array.isArray(saved)) {
|
||||||
for (let i = 0; i < saved.length; i++) {
|
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]) {
|
if (draftPos && saved[i]) {
|
||||||
posMasterMapping.set(draftPos.id, saved[i].id);
|
posMasterMapping.set(draftPos.id, saved[i].id);
|
||||||
}
|
}
|
||||||
|
|
@ -8092,7 +8103,7 @@ export class OrganizationController extends Controller {
|
||||||
draftPosMasterId,
|
draftPosMasterId,
|
||||||
currentPosMasterId,
|
currentPosMasterId,
|
||||||
drafRevisionId,
|
drafRevisionId,
|
||||||
currentRevisionId
|
currentRevisionId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -8107,10 +8118,7 @@ export class OrganizationController extends Controller {
|
||||||
/**
|
/**
|
||||||
* Helper function: Map draft ID to current ID using the mapping
|
* Helper function: Map draft ID to current ID using the mapping
|
||||||
*/
|
*/
|
||||||
private resolveOrgId(
|
private resolveOrgId(draftId: string | null, mapping: OrgIdMapping): string | null {
|
||||||
draftId: string | null,
|
|
||||||
mapping: OrgIdMapping
|
|
||||||
): string | null {
|
|
||||||
if (!draftId) return null;
|
if (!draftId) return null;
|
||||||
return mapping.byDraftId.get(draftId) ?? null;
|
return mapping.byDraftId.get(draftId) ?? null;
|
||||||
}
|
}
|
||||||
|
|
@ -8121,10 +8129,10 @@ export class OrganizationController extends Controller {
|
||||||
private async cascadeDeletePositions(
|
private async cascadeDeletePositions(
|
||||||
queryRunner: any,
|
queryRunner: any,
|
||||||
node: any,
|
node: any,
|
||||||
entityClass: any
|
entityClass: any,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const whereClause: any = {
|
const whereClause: any = {
|
||||||
orgRevisionId: node.orgRevisionId
|
orgRevisionId: node.orgRevisionId,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Determine which FK field to use based on entity type
|
// Determine which FK field to use based on entity type
|
||||||
|
|
@ -8154,22 +8162,22 @@ export class OrganizationController extends Controller {
|
||||||
draftRevisionId: string,
|
draftRevisionId: string,
|
||||||
currentRevisionId: string,
|
currentRevisionId: string,
|
||||||
rootDnaId: string,
|
rootDnaId: string,
|
||||||
parentMappings?: AllOrgMappings
|
parentMappings?: AllOrgMappings,
|
||||||
): Promise<OrgIdMapping> {
|
): Promise<OrgIdMapping> {
|
||||||
// 1. Fetch draft and current nodes under the given rootDnaId
|
// 1. Fetch draft and current nodes under the given rootDnaId
|
||||||
const [draftNodes, currentNodes] = await Promise.all([
|
const [draftNodes, currentNodes] = await Promise.all([
|
||||||
repository.find({
|
repository.find({
|
||||||
where: {
|
where: {
|
||||||
orgRevisionId: draftRevisionId,
|
orgRevisionId: draftRevisionId,
|
||||||
ancestorDNA: Like(`${rootDnaId}%`)
|
ancestorDNA: Like(`${rootDnaId}%`),
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
repository.find({
|
repository.find({
|
||||||
where: {
|
where: {
|
||||||
orgRevisionId: currentRevisionId,
|
orgRevisionId: currentRevisionId,
|
||||||
ancestorDNA: Like(`${rootDnaId}%`)
|
ancestorDNA: Like(`${rootDnaId}%`),
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 2. Build lookup maps for efficient matching by ancestorDNA
|
// 2. Build lookup maps for efficient matching by ancestorDNA
|
||||||
|
|
@ -8178,7 +8186,7 @@ export class OrganizationController extends Controller {
|
||||||
|
|
||||||
const mapping: OrgIdMapping = {
|
const mapping: OrgIdMapping = {
|
||||||
byAncestorDNA: new Map(),
|
byAncestorDNA: new Map(),
|
||||||
byDraftId: new Map()
|
byDraftId: new Map(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3. DELETE: Current nodes not in draft (cascade delete positions first)
|
// 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
|
// Map parent IDs based on entity level
|
||||||
if (entityClass === OrgChild1 && draft.orgRootId && parentMappings) {
|
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) {
|
} else if (entityClass === OrgChild2) {
|
||||||
if (draft.orgRootId && parentMappings) {
|
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) {
|
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) {
|
} else if (entityClass === OrgChild3) {
|
||||||
if (draft.orgRootId && parentMappings) {
|
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) {
|
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) {
|
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) {
|
} else if (entityClass === OrgChild4) {
|
||||||
if (draft.orgRootId && parentMappings) {
|
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) {
|
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) {
|
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) {
|
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,
|
draftPosMasterId: string,
|
||||||
currentPosMasterId: string,
|
currentPosMasterId: string,
|
||||||
draftRevisionId: string,
|
draftRevisionId: string,
|
||||||
currentRevisionId: string
|
currentRevisionId: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Fetch draft and current positions for this posMaster
|
// Fetch draft and current positions for this posMaster
|
||||||
const [draftPositions, currentPositions] = await Promise.all([
|
const [draftPositions, currentPositions] = await Promise.all([
|
||||||
queryRunner.manager.find(Position, {
|
queryRunner.manager.find(Position, {
|
||||||
where: {
|
where: {
|
||||||
posMasterId: draftPosMasterId
|
posMasterId: draftPosMasterId,
|
||||||
},
|
},
|
||||||
order: { orderNo: 'ASC' }
|
order: { orderNo: "ASC" },
|
||||||
}),
|
}),
|
||||||
queryRunner.manager.find(Position, {
|
queryRunner.manager.find(Position, {
|
||||||
where: {
|
where: {
|
||||||
posMasterId: currentPosMasterId
|
posMasterId: currentPosMasterId,
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// If no draft positions, delete all current positions
|
// If no draft positions, delete all current positions
|
||||||
|
|
@ -8326,16 +8344,14 @@ export class OrganizationController extends Controller {
|
||||||
if (currentPositions.length > 0) {
|
if (currentPositions.length > 0) {
|
||||||
await queryRunner.manager.delete(
|
await queryRunner.manager.delete(
|
||||||
Position,
|
Position,
|
||||||
currentPositions.map((p: any) => p.id)
|
currentPositions.map((p: any) => p.id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build maps for tracking
|
// Build maps for tracking
|
||||||
const currentByOrderNo = new Map(
|
const currentByOrderNo = new Map(currentPositions.map((p: any) => [p.orderNo, p]));
|
||||||
currentPositions.map((p: any) => [p.orderNo, p])
|
|
||||||
);
|
|
||||||
|
|
||||||
// DELETE: Current positions not in draft (by orderNo)
|
// DELETE: Current positions not in draft (by orderNo)
|
||||||
const draftOrderNos = new Set(draftPositions.map((p: any) => p.orderNo));
|
const draftOrderNos = new Set(draftPositions.map((p: any) => p.orderNo));
|
||||||
|
|
@ -8344,7 +8360,7 @@ export class OrganizationController extends Controller {
|
||||||
if (toDelete.length > 0) {
|
if (toDelete.length > 0) {
|
||||||
await queryRunner.manager.delete(
|
await queryRunner.manager.delete(
|
||||||
Position,
|
Position,
|
||||||
toDelete.map((p: any) => p.id)
|
toDelete.map((p: any) => p.id),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue