690329:1904 Fixing revision number Kimi K2.5 #01
CI / CD Pipeline / build (push) Successful in 18m7s
CI / CD Pipeline / deploy (push) Failing after 3m15s

This commit is contained in:
2026-03-29 19:04:24 +07:00
parent 65aaae9d90
commit 43e380e5c1
15 changed files with 4120 additions and 3 deletions
@@ -427,5 +427,297 @@ describe('CorrespondenceService', () => {
expect.objectContaining({ originatorId: 10 })
);
});
it('should set revisionLabel to "A" for RFA type', async () => {
const mockUser = {
user_id: 1,
primaryOrganizationId: 10,
} as unknown as User;
const createDto: CreateCorrespondenceDto = {
projectId: 'project-uuid',
typeId: 1,
subject: 'Test Subject',
recipients: [{ organizationId: 'recipient-uuid', type: 'TO' }],
};
const typeRepo = testingModule.get<Repository<CorrespondenceType>>(
getRepositoryToken(CorrespondenceType)
);
const statusRepo = testingModule.get<Repository<CorrespondenceStatus>>(
getRepositoryToken(CorrespondenceStatus)
);
const uuidResolver =
testingModule.get<UuidResolverService>(UuidResolverService);
(uuidResolver.resolveProjectId as jest.Mock).mockResolvedValue(100);
(uuidResolver.resolveOrganizationId as jest.Mock).mockResolvedValue(20);
(typeRepo.findOne as jest.Mock).mockResolvedValue({
id: 1,
typeCode: 'RFA',
});
(statusRepo.findOne as jest.Mock).mockResolvedValue({
id: 1,
statusCode: 'DRAFT',
});
(numberingService.generateNextNumber as jest.Mock).mockResolvedValue({
number: 'DOC-001',
});
mockDataSource.manager.findOne
.mockResolvedValueOnce({ id: 10, organizationCode: 'ORG' })
.mockResolvedValueOnce({ id: 20, organizationCode: 'REC' });
const queryRunner = {
connect: jest.fn(),
startTransaction: jest.fn(),
commitTransaction: jest.fn(),
rollbackTransaction: jest.fn(),
release: jest.fn(),
manager: {
create: jest.fn(
(_entity: unknown, payload: Record<string, unknown>) => payload
),
save: jest
.fn()
.mockResolvedValueOnce({ id: 999, publicId: 'corr-uuid' })
.mockResolvedValueOnce({ id: 1000 })
.mockResolvedValueOnce([]),
findOne: jest.fn(),
},
};
(mockDataSource.createQueryRunner as jest.Mock).mockReturnValue(
queryRunner
);
await service.create(createDto, mockUser);
expect(queryRunner.manager.create).toHaveBeenCalledWith(
CorrespondenceRevision,
expect.objectContaining({ revisionLabel: 'A' })
);
});
it('should set revisionLabel to "A" for RFI type', async () => {
const mockUser = {
user_id: 1,
primaryOrganizationId: 10,
} as unknown as User;
const createDto: CreateCorrespondenceDto = {
projectId: 'project-uuid',
typeId: 1,
subject: 'Test Subject',
recipients: [{ organizationId: 'recipient-uuid', type: 'TO' }],
};
const typeRepo = testingModule.get<Repository<CorrespondenceType>>(
getRepositoryToken(CorrespondenceType)
);
const statusRepo = testingModule.get<Repository<CorrespondenceStatus>>(
getRepositoryToken(CorrespondenceStatus)
);
const uuidResolver =
testingModule.get<UuidResolverService>(UuidResolverService);
(uuidResolver.resolveProjectId as jest.Mock).mockResolvedValue(100);
(uuidResolver.resolveOrganizationId as jest.Mock).mockResolvedValue(20);
(typeRepo.findOne as jest.Mock).mockResolvedValue({
id: 1,
typeCode: 'RFI',
});
(statusRepo.findOne as jest.Mock).mockResolvedValue({
id: 1,
statusCode: 'DRAFT',
});
(numberingService.generateNextNumber as jest.Mock).mockResolvedValue({
number: 'DOC-001',
});
mockDataSource.manager.findOne
.mockResolvedValueOnce({ id: 10, organizationCode: 'ORG' })
.mockResolvedValueOnce({ id: 20, organizationCode: 'REC' });
const queryRunner = {
connect: jest.fn(),
startTransaction: jest.fn(),
commitTransaction: jest.fn(),
rollbackTransaction: jest.fn(),
release: jest.fn(),
manager: {
create: jest.fn(
(_entity: unknown, payload: Record<string, unknown>) => payload
),
save: jest
.fn()
.mockResolvedValueOnce({ id: 999, publicId: 'corr-uuid' })
.mockResolvedValueOnce({ id: 1000 })
.mockResolvedValueOnce([]),
findOne: jest.fn(),
},
};
(mockDataSource.createQueryRunner as jest.Mock).mockReturnValue(
queryRunner
);
await service.create(createDto, mockUser);
expect(queryRunner.manager.create).toHaveBeenCalledWith(
CorrespondenceRevision,
expect.objectContaining({ revisionLabel: 'A' })
);
});
it('should set revisionLabel to null for LETTER type', async () => {
const mockUser = {
user_id: 1,
primaryOrganizationId: 10,
} as unknown as User;
const createDto: CreateCorrespondenceDto = {
projectId: 'project-uuid',
typeId: 1,
subject: 'Test Subject',
recipients: [{ organizationId: 'recipient-uuid', type: 'TO' }],
};
const typeRepo = testingModule.get<Repository<CorrespondenceType>>(
getRepositoryToken(CorrespondenceType)
);
const statusRepo = testingModule.get<Repository<CorrespondenceStatus>>(
getRepositoryToken(CorrespondenceStatus)
);
const uuidResolver =
testingModule.get<UuidResolverService>(UuidResolverService);
(uuidResolver.resolveProjectId as jest.Mock).mockResolvedValue(100);
(uuidResolver.resolveOrganizationId as jest.Mock).mockResolvedValue(20);
(typeRepo.findOne as jest.Mock).mockResolvedValue({
id: 1,
typeCode: 'LETTER',
});
(statusRepo.findOne as jest.Mock).mockResolvedValue({
id: 1,
statusCode: 'DRAFT',
});
(numberingService.generateNextNumber as jest.Mock).mockResolvedValue({
number: 'DOC-001',
});
mockDataSource.manager.findOne
.mockResolvedValueOnce({ id: 10, organizationCode: 'ORG' })
.mockResolvedValueOnce({ id: 20, organizationCode: 'REC' });
const queryRunner = {
connect: jest.fn(),
startTransaction: jest.fn(),
commitTransaction: jest.fn(),
rollbackTransaction: jest.fn(),
release: jest.fn(),
manager: {
create: jest.fn(
(_entity: unknown, payload: Record<string, unknown>) => payload
),
save: jest
.fn()
.mockResolvedValueOnce({ id: 999, publicId: 'corr-uuid' })
.mockResolvedValueOnce({ id: 1000 })
.mockResolvedValueOnce([]),
findOne: jest.fn(),
},
};
(mockDataSource.createQueryRunner as jest.Mock).mockReturnValue(
queryRunner
);
await service.create(createDto, mockUser);
expect(queryRunner.manager.create).toHaveBeenCalledWith(
CorrespondenceRevision,
expect.objectContaining({ revisionLabel: null })
);
});
it('should set revisionLabel to null for MEMO type', async () => {
const mockUser = {
user_id: 1,
primaryOrganizationId: 10,
} as unknown as User;
const createDto: CreateCorrespondenceDto = {
projectId: 'project-uuid',
typeId: 1,
subject: 'Test Subject',
recipients: [{ organizationId: 'recipient-uuid', type: 'TO' }],
};
const typeRepo = testingModule.get<Repository<CorrespondenceType>>(
getRepositoryToken(CorrespondenceType)
);
const statusRepo = testingModule.get<Repository<CorrespondenceStatus>>(
getRepositoryToken(CorrespondenceStatus)
);
const uuidResolver =
testingModule.get<UuidResolverService>(UuidResolverService);
(uuidResolver.resolveProjectId as jest.Mock).mockResolvedValue(100);
(uuidResolver.resolveOrganizationId as jest.Mock).mockResolvedValue(20);
(typeRepo.findOne as jest.Mock).mockResolvedValue({
id: 1,
typeCode: 'MEMO',
});
(statusRepo.findOne as jest.Mock).mockResolvedValue({
id: 1,
statusCode: 'DRAFT',
});
(numberingService.generateNextNumber as jest.Mock).mockResolvedValue({
number: 'DOC-001',
});
mockDataSource.manager.findOne
.mockResolvedValueOnce({ id: 10, organizationCode: 'ORG' })
.mockResolvedValueOnce({ id: 20, organizationCode: 'REC' });
const queryRunner = {
connect: jest.fn(),
startTransaction: jest.fn(),
commitTransaction: jest.fn(),
rollbackTransaction: jest.fn(),
release: jest.fn(),
manager: {
create: jest.fn(
(_entity: unknown, payload: Record<string, unknown>) => payload
),
save: jest
.fn()
.mockResolvedValueOnce({ id: 999, publicId: 'corr-uuid' })
.mockResolvedValueOnce({ id: 1000 })
.mockResolvedValueOnce([]),
findOne: jest.fn(),
},
};
(mockDataSource.createQueryRunner as jest.Mock).mockReturnValue(
queryRunner
);
await service.create(createDto, mockUser);
expect(queryRunner.manager.create).toHaveBeenCalledWith(
CorrespondenceRevision,
expect.objectContaining({ revisionLabel: null })
);
});
});
});
@@ -55,6 +55,19 @@ export class CorrespondenceService {
return permissions.includes('system.manage_all');
}
/**
* Business Rule: Revision Label Strategy
* - RFA, RFI: Use alphabet starting with 'A' (A, B, C...)
* - Other types (LETTER, MEMO, etc.): Use numeric (null for first, then 1, 2, 3...)
*/
private getInitialRevisionLabel(typeCode: string): string | null {
const alphabetTypes = ['RFA', 'RFI'];
if (alphabetTypes.includes(typeCode.toUpperCase())) {
return 'A'; // Alphabet for RFA, RFI
}
return null; // Numeric (null for revision 0)
}
constructor(
@InjectRepository(Correspondence)
private correspondenceRepo: Repository<Correspondence>,
@@ -288,7 +301,7 @@ export class CorrespondenceService {
const revision = queryRunner.manager.create(CorrespondenceRevision, {
correspondenceId: savedCorr.id,
revisionNumber: 0,
revisionLabel: 'A',
revisionLabel: this.getInitialRevisionLabel(type.typeCode),
isCurrent: true,
statusId: statusDraft.id,
subject: createDto.subject,