260322:1648 Correct Coresspondence / Doing RFA / Correct CI
This commit is contained in:
@@ -88,7 +88,7 @@ export class CorrespondenceWorkflowService {
|
||||
};
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(`Failed to submit workflow: ${error}`);
|
||||
this.logger.error(`Failed to submit workflow: ${String(error)}`);
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
@@ -113,7 +113,7 @@ export class CorrespondenceWorkflowService {
|
||||
|
||||
if (instance && instance.entityType === 'correspondence_revision') {
|
||||
const revision = await this.revisionRepo.findOne({
|
||||
where: { id: parseInt(instance.entityId) },
|
||||
where: { id: Number(instance.entityId) },
|
||||
});
|
||||
if (revision) {
|
||||
await this.syncStatus(revision, result.nextState);
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('CorrespondenceController', () => {
|
||||
subject: 'Test Subject',
|
||||
};
|
||||
|
||||
const result = await controller.create(
|
||||
const _result = await controller.create(
|
||||
createDto as Parameters<typeof controller.create>[0],
|
||||
mockReq as Parameters<typeof controller.create>[1]
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { CorrespondenceService } from './correspondence.service';
|
||||
import { Correspondence } from './entities/correspondence.entity';
|
||||
import { CorrespondenceRevision } from './entities/correspondence-revision.entity';
|
||||
@@ -15,13 +15,15 @@ import { WorkflowEngineService } from '../workflow-engine/workflow-engine.servic
|
||||
import { UserService } from '../user/user.service';
|
||||
import { SearchService } from '../search/search.service';
|
||||
import { FileStorageService } from '../../common/file-storage/file-storage.service';
|
||||
import { UpdateCorrespondenceDto } from './dto/update-correspondence.dto';
|
||||
import { User } from '../user/entities/user.entity';
|
||||
|
||||
describe('CorrespondenceService', () => {
|
||||
let service: CorrespondenceService;
|
||||
let numberingService: DocumentNumberingService;
|
||||
let correspondenceRepo: any;
|
||||
let revisionRepo: any;
|
||||
let dataSource: any;
|
||||
let correspondenceRepo: Repository<Correspondence>;
|
||||
let revisionRepo: Repository<CorrespondenceRevision>;
|
||||
let _dataSource: DataSource;
|
||||
|
||||
const createMockRepository = () => ({
|
||||
find: jest.fn(),
|
||||
@@ -88,6 +90,10 @@ describe('CorrespondenceService', () => {
|
||||
provide: getRepositoryToken(Organization),
|
||||
useValue: createMockRepository(),
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(CorrespondenceRecipient),
|
||||
useValue: createMockRepository(),
|
||||
},
|
||||
{
|
||||
provide: DocumentNumberingService,
|
||||
useValue: {
|
||||
@@ -130,9 +136,13 @@ describe('CorrespondenceService', () => {
|
||||
numberingService = module.get<DocumentNumberingService>(
|
||||
DocumentNumberingService
|
||||
);
|
||||
correspondenceRepo = module.get(getRepositoryToken(Correspondence));
|
||||
revisionRepo = module.get(getRepositoryToken(CorrespondenceRevision));
|
||||
dataSource = module.get(DataSource);
|
||||
correspondenceRepo = module.get<Repository<Correspondence>>(
|
||||
getRepositoryToken(Correspondence)
|
||||
);
|
||||
revisionRepo = module.get<Repository<CorrespondenceRevision>>(
|
||||
getRepositoryToken(CorrespondenceRevision)
|
||||
);
|
||||
_dataSource = module.get<DataSource>(DataSource);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
@@ -141,20 +151,17 @@ describe('CorrespondenceService', () => {
|
||||
|
||||
describe('update', () => {
|
||||
it('should NOT regenerate number if critical fields unchanged', async () => {
|
||||
const mockUser = { user_id: 1, primaryOrganizationId: 10 } as any;
|
||||
const mockUser = { id: 1, primaryOrganizationId: 10 } as unknown as User;
|
||||
const mockRevision = {
|
||||
id: 100,
|
||||
correspondenceId: 1,
|
||||
isCurrent: true,
|
||||
statusId: 5,
|
||||
}; // Status 5 = Draft handled by logic?
|
||||
// Mock status repo to return DRAFT
|
||||
// But strict logic: revision.statusId check
|
||||
jest.spyOn(revisionRepo, 'findOne').mockResolvedValue(mockRevision);
|
||||
const mockStatus = { id: 5, statusCode: 'DRAFT' };
|
||||
// Need to set statusRepo mock behavior... simplified here for brevity or assume defaults
|
||||
// Injecting internal access to statusRepo is hard without `module.get` if I didn't save it.
|
||||
// Let's assume it passes check for now.
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(revisionRepo, 'findOne')
|
||||
.mockResolvedValue(mockRevision as unknown as CorrespondenceRevision);
|
||||
|
||||
const mockCorr = {
|
||||
id: 1,
|
||||
@@ -165,89 +172,105 @@ describe('CorrespondenceService', () => {
|
||||
correspondenceNumber: 'OLD-NUM',
|
||||
recipients: [{ recipientType: 'TO', recipientOrganizationId: 99 }],
|
||||
};
|
||||
jest.spyOn(correspondenceRepo, 'findOne').mockResolvedValue(mockCorr);
|
||||
jest
|
||||
.spyOn(correspondenceRepo, 'findOne')
|
||||
.mockResolvedValue(mockCorr as unknown as Correspondence);
|
||||
|
||||
// Update DTO with same values
|
||||
const updateDto = {
|
||||
const updateDto: UpdateCorrespondenceDto = {
|
||||
projectId: 1,
|
||||
disciplineId: 3,
|
||||
// recipients missing -> imply no change
|
||||
};
|
||||
|
||||
await service.update(1, updateDto as any, mockUser);
|
||||
await service.update(1, updateDto, mockUser);
|
||||
|
||||
// Check that updateNumberForDraft was NOT called
|
||||
expect(numberingService.updateNumberForDraft).not.toHaveBeenCalled();
|
||||
expect(
|
||||
numberingService.updateNumberForDraft as jest.Mock
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should regenerate number if Project ID changes', async () => {
|
||||
const mockUser = { user_id: 1, primaryOrganizationId: 10 } as any;
|
||||
const mockUser = { id: 1, primaryOrganizationId: 10 } as unknown as User;
|
||||
const mockRevision = {
|
||||
id: 100,
|
||||
correspondenceId: 1,
|
||||
isCurrent: true,
|
||||
statusId: 5,
|
||||
};
|
||||
jest.spyOn(revisionRepo, 'findOne').mockResolvedValue(mockRevision);
|
||||
jest
|
||||
.spyOn(revisionRepo, 'findOne')
|
||||
.mockResolvedValue(mockRevision as unknown as CorrespondenceRevision);
|
||||
|
||||
const mockCorr = {
|
||||
id: 1,
|
||||
projectId: 1, // Old Project
|
||||
projectId: 1,
|
||||
correspondenceTypeId: 2,
|
||||
disciplineId: 3,
|
||||
originatorId: 10,
|
||||
correspondenceNumber: 'OLD-NUM',
|
||||
recipients: [{ recipientType: 'TO', recipientOrganizationId: 99 }],
|
||||
};
|
||||
jest.spyOn(correspondenceRepo, 'findOne').mockResolvedValue(mockCorr);
|
||||
jest
|
||||
.spyOn(correspondenceRepo, 'findOne')
|
||||
.mockResolvedValue(mockCorr as unknown as Correspondence);
|
||||
|
||||
const updateDto = {
|
||||
projectId: 2, // New Project -> Change!
|
||||
const updateDto: UpdateCorrespondenceDto = {
|
||||
projectId: 2,
|
||||
};
|
||||
|
||||
await service.update(1, updateDto as any, mockUser);
|
||||
await service.update(1, updateDto, mockUser);
|
||||
|
||||
expect(numberingService.updateNumberForDraft).toHaveBeenCalled();
|
||||
expect(
|
||||
numberingService.updateNumberForDraft as jest.Mock
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should regenerate number if Document Type changes', async () => {
|
||||
const mockUser = { user_id: 1, primaryOrganizationId: 10 } as any;
|
||||
const mockUser = { id: 1, primaryOrganizationId: 10 } as unknown as User;
|
||||
const mockRevision = {
|
||||
id: 100,
|
||||
correspondenceId: 1,
|
||||
isCurrent: true,
|
||||
statusId: 5,
|
||||
};
|
||||
jest.spyOn(revisionRepo, 'findOne').mockResolvedValue(mockRevision);
|
||||
jest
|
||||
.spyOn(revisionRepo, 'findOne')
|
||||
.mockResolvedValue(mockRevision as unknown as CorrespondenceRevision);
|
||||
|
||||
const mockCorr = {
|
||||
id: 1,
|
||||
projectId: 1,
|
||||
correspondenceTypeId: 2, // Old Type
|
||||
correspondenceTypeId: 2,
|
||||
disciplineId: 3,
|
||||
originatorId: 10,
|
||||
correspondenceNumber: 'OLD-NUM',
|
||||
recipients: [{ recipientType: 'TO', recipientOrganizationId: 99 }],
|
||||
};
|
||||
jest.spyOn(correspondenceRepo, 'findOne').mockResolvedValue(mockCorr);
|
||||
jest
|
||||
.spyOn(correspondenceRepo, 'findOne')
|
||||
.mockResolvedValue(mockCorr as unknown as Correspondence);
|
||||
|
||||
const updateDto = {
|
||||
typeId: 999, // New Type
|
||||
const updateDto: UpdateCorrespondenceDto = {
|
||||
typeId: 999,
|
||||
};
|
||||
|
||||
await service.update(1, updateDto as any, mockUser);
|
||||
await service.update(1, updateDto, mockUser);
|
||||
|
||||
expect(numberingService.updateNumberForDraft).toHaveBeenCalled();
|
||||
expect(
|
||||
numberingService.updateNumberForDraft as jest.Mock
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should regenerate number if Recipient Organization changes', async () => {
|
||||
const mockUser = { user_id: 1, primaryOrganizationId: 10 } as any;
|
||||
const mockUser = { id: 1, primaryOrganizationId: 10 } as unknown as User;
|
||||
const mockRevision = {
|
||||
id: 100,
|
||||
correspondenceId: 1,
|
||||
isCurrent: true,
|
||||
statusId: 5,
|
||||
};
|
||||
jest.spyOn(revisionRepo, 'findOne').mockResolvedValue(mockRevision);
|
||||
jest
|
||||
.spyOn(revisionRepo, 'findOne')
|
||||
.mockResolvedValue(mockRevision as unknown as CorrespondenceRevision);
|
||||
|
||||
const mockCorr = {
|
||||
id: 1,
|
||||
@@ -256,20 +279,30 @@ describe('CorrespondenceService', () => {
|
||||
disciplineId: 3,
|
||||
originatorId: 10,
|
||||
correspondenceNumber: 'OLD-NUM',
|
||||
recipients: [{ recipientType: 'TO', recipientOrganizationId: 99 }], // Old Recipient 99
|
||||
recipients: [{ recipientType: 'TO', recipientOrganizationId: 99 }],
|
||||
};
|
||||
jest.spyOn(correspondenceRepo, 'findOne').mockResolvedValue(mockCorr);
|
||||
jest
|
||||
.spyOn(service['orgRepo'], 'findOne')
|
||||
.mockResolvedValue({ id: 88, organizationCode: 'NEW-ORG' } as any);
|
||||
.spyOn(correspondenceRepo, 'findOne')
|
||||
.mockResolvedValue(mockCorr as unknown as Correspondence);
|
||||
|
||||
const updateDto = {
|
||||
recipients: [{ type: 'TO', organizationId: 88 }], // New Recipient 88
|
||||
// Access private property for mocking via casting
|
||||
const internalService = service as unknown as {
|
||||
orgRepo: Repository<Organization>;
|
||||
};
|
||||
jest.spyOn(internalService.orgRepo, 'findOne').mockResolvedValue({
|
||||
id: 88,
|
||||
organizationCode: 'NEW-ORG',
|
||||
} as unknown as Organization);
|
||||
|
||||
const updateDto: UpdateCorrespondenceDto = {
|
||||
recipients: [{ type: 'TO', organizationId: 88 }],
|
||||
};
|
||||
|
||||
await service.update(1, updateDto as any, mockUser);
|
||||
await service.update(1, updateDto, mockUser);
|
||||
|
||||
expect(numberingService.updateNumberForDraft).toHaveBeenCalled();
|
||||
expect(
|
||||
numberingService.updateNumberForDraft as jest.Mock
|
||||
).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,10 +39,11 @@ import { UuidResolverService } from '../../common/services/uuid-resolver.service
|
||||
|
||||
/**
|
||||
* CorrespondenceService - Document management (CRUD)
|
||||
*
|
||||
* NOTE: Workflow operations (submit, processAction) have been moved to
|
||||
* CorrespondenceWorkflowService which uses the Unified Workflow Engine.
|
||||
*/
|
||||
interface ResolvedRecipient {
|
||||
organizationId: number;
|
||||
type: 'TO' | 'CC';
|
||||
}
|
||||
@Injectable()
|
||||
export class CorrespondenceService {
|
||||
private readonly logger = new Logger(CorrespondenceService.name);
|
||||
@@ -78,12 +79,14 @@ export class CorrespondenceService {
|
||||
: undefined;
|
||||
const resolvedRecipients = createDto.recipients
|
||||
? await Promise.all(
|
||||
createDto.recipients.map(async (r) => ({
|
||||
organizationId: await this.uuidResolver.resolveOrganizationId(
|
||||
r.organizationId
|
||||
),
|
||||
type: r.type,
|
||||
}))
|
||||
createDto.recipients.map(
|
||||
async (r): Promise<ResolvedRecipient> => ({
|
||||
organizationId: await this.uuidResolver.resolveOrganizationId(
|
||||
r.organizationId
|
||||
),
|
||||
type: r.type,
|
||||
})
|
||||
)
|
||||
)
|
||||
: undefined;
|
||||
const type = await this.typeRepo.findOne({
|
||||
@@ -257,9 +260,9 @@ export class CorrespondenceService {
|
||||
originatorId: userOrgId,
|
||||
disciplineId: createDto.disciplineId,
|
||||
initiatorId: user.user_id,
|
||||
}
|
||||
} as Record<string, unknown>
|
||||
);
|
||||
} catch (error) {
|
||||
} catch (error: unknown) {
|
||||
this.logger.warn(
|
||||
`Workflow not started for ${docNumber.number} (Code: CORRESPONDENCE_${type.typeCode}): ${(error as Error).message}`
|
||||
);
|
||||
@@ -491,12 +494,14 @@ export class CorrespondenceService {
|
||||
: undefined;
|
||||
const updResolvedRecipients = updateDto.recipients
|
||||
? await Promise.all(
|
||||
updateDto.recipients.map(async (r) => ({
|
||||
organizationId: await this.uuidResolver.resolveOrganizationId(
|
||||
r.organizationId
|
||||
),
|
||||
type: r.type,
|
||||
}))
|
||||
updateDto.recipients.map(
|
||||
async (r): Promise<ResolvedRecipient> => ({
|
||||
organizationId: await this.uuidResolver.resolveOrganizationId(
|
||||
r.organizationId
|
||||
),
|
||||
type: r.type,
|
||||
})
|
||||
)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
@@ -699,12 +704,14 @@ export class CorrespondenceService {
|
||||
: undefined;
|
||||
const previewRecipients = createDto.recipients
|
||||
? await Promise.all(
|
||||
createDto.recipients.map(async (r) => ({
|
||||
organizationId: await this.uuidResolver.resolveOrganizationId(
|
||||
r.organizationId
|
||||
),
|
||||
type: r.type,
|
||||
}))
|
||||
createDto.recipients.map(
|
||||
async (r): Promise<ResolvedRecipient> => ({
|
||||
organizationId: await this.uuidResolver.resolveOrganizationId(
|
||||
r.organizationId
|
||||
),
|
||||
type: r.type,
|
||||
})
|
||||
)
|
||||
)
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
IsInt,
|
||||
IsArray,
|
||||
ValidateNested,
|
||||
IsEnum,
|
||||
IsBoolean,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Entity, _Column, PrimaryColumn, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { Correspondence } from './correspondence.entity';
|
||||
import { Organization } from '../../organization/entities/organization.entity';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user