690503:1547 Update #01
This commit is contained in:
@@ -111,4 +111,17 @@ export class CirculationController {
|
||||
) {
|
||||
return this.circulationService.forceClose(uuid, dto.reason, user);
|
||||
}
|
||||
|
||||
@Post(':uuid/close')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Close a Circulation when all Main/Action routings are completed (FR-C09)',
|
||||
})
|
||||
@ApiParam({ name: 'uuid', description: 'Circulation publicId' })
|
||||
@RequirePermission('circulation.manage')
|
||||
@Audit('circulation.close', 'circulation')
|
||||
close(@Param('uuid', ParseUuidPipe) uuid: string, @CurrentUser() user: User) {
|
||||
return this.circulationService.close(uuid, user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,6 +299,47 @@ export class CirculationService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EC-CIRC-00X: Close Circulation (FR-C09)
|
||||
* ต้องมีสิทธิ์ circulation.close (หรือ circulation.manage) เช็คใน controller
|
||||
*/
|
||||
async close(publicId: string, user: User) {
|
||||
const circulation = await this.circulationRepo.findOne({
|
||||
where: { publicId },
|
||||
relations: ['routings'],
|
||||
});
|
||||
if (!circulation)
|
||||
throw new NotFoundException(`Circulation publicId ${publicId}`);
|
||||
|
||||
if (
|
||||
circulation.statusCode === 'COMPLETED' ||
|
||||
circulation.statusCode === 'CANCELLED' ||
|
||||
circulation.statusCode === 'CLOSED'
|
||||
) {
|
||||
throw new ValidationException(
|
||||
`ใบเวียน ${circulation.circulationNo} ปิดไปแล้ว (${circulation.statusCode})`
|
||||
);
|
||||
}
|
||||
|
||||
const pendingCount = circulation.routings.filter(
|
||||
(r) => r.status === 'PENDING' || r.status === 'IN_PROGRESS'
|
||||
).length;
|
||||
|
||||
if (pendingCount > 0) {
|
||||
throw new ValidationException(
|
||||
'All Main/Action routings must be COMPLETED before closing'
|
||||
);
|
||||
}
|
||||
|
||||
circulation.statusCode = 'CLOSED';
|
||||
circulation.closedAt = new Date();
|
||||
await this.circulationRepo.save(circulation);
|
||||
|
||||
this.logger.log(`Circulation ${publicId} closed by user ${user.user_id}`);
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// ✅ Logic อัปเดตสถานะและปิดงาน
|
||||
async updateRoutingStatus(
|
||||
routingId: number,
|
||||
|
||||
@@ -24,6 +24,7 @@ import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module'
|
||||
import { SearchModule } from '../search/search.module';
|
||||
import { FileStorageModule } from '../../common/file-storage/file-storage.module';
|
||||
import { NotificationModule } from '../notification/notification.module';
|
||||
import { CirculationModule } from '../circulation/circulation.module';
|
||||
|
||||
/**
|
||||
* CorrespondenceModule
|
||||
@@ -51,6 +52,7 @@ import { NotificationModule } from '../notification/notification.module';
|
||||
SearchModule,
|
||||
FileStorageModule,
|
||||
NotificationModule,
|
||||
CirculationModule,
|
||||
],
|
||||
controllers: [CorrespondenceController],
|
||||
providers: [
|
||||
|
||||
@@ -20,6 +20,7 @@ import { SearchService } from '../search/search.service';
|
||||
import { FileStorageService } from '../../common/file-storage/file-storage.service';
|
||||
import { UuidResolverService } from '../../common/services/uuid-resolver.service';
|
||||
import { NotificationService } from '../notification/notification.service';
|
||||
import { CirculationService } from '../circulation/circulation.service';
|
||||
import { UpdateCorrespondenceDto } from './dto/update-correspondence.dto';
|
||||
import { CreateCorrespondenceDto } from './dto/create-correspondence.dto';
|
||||
import { User } from '../user/entities/user.entity';
|
||||
@@ -158,6 +159,12 @@ describe('CorrespondenceService', () => {
|
||||
provide: getRepositoryToken(CorrespondenceRevisionAttachment),
|
||||
useValue: createMockRepository(),
|
||||
},
|
||||
{
|
||||
provide: CirculationService,
|
||||
useValue: {
|
||||
forceClose: jest.fn().mockResolvedValue({ success: true }),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// File: src/modules/correspondence/correspondence.service.ts
|
||||
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Injectable, Logger, Inject, forwardRef } from '@nestjs/common';
|
||||
import {
|
||||
BusinessException,
|
||||
NotFoundException,
|
||||
@@ -39,6 +39,9 @@ import { SearchService } from '../search/search.service';
|
||||
import { FileStorageService } from '../../common/file-storage/file-storage.service';
|
||||
import { UuidResolverService } from '../../common/services/uuid-resolver.service';
|
||||
import { NotificationService } from '../notification/notification.service';
|
||||
import { CirculationService } from '../circulation/circulation.service';
|
||||
import { Circulation } from '../circulation/entities/circulation.entity';
|
||||
import { CirculationRouting } from '../circulation/entities/circulation-routing.entity';
|
||||
|
||||
/**
|
||||
* CorrespondenceService - Document management (CRUD)
|
||||
@@ -92,7 +95,9 @@ export class CorrespondenceService {
|
||||
private uuidResolver: UuidResolverService,
|
||||
private notificationService: NotificationService,
|
||||
@InjectRepository(CorrespondenceRevisionAttachment)
|
||||
private revAttachRepo: Repository<CorrespondenceRevisionAttachment>
|
||||
private revAttachRepo: Repository<CorrespondenceRevisionAttachment>,
|
||||
@Inject(forwardRef(() => CirculationService))
|
||||
private circulationService: CirculationService
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -984,11 +989,11 @@ export class CorrespondenceService {
|
||||
}
|
||||
|
||||
// Check if there are any active circulations
|
||||
const circulationRepo = this.dataSource.getRepository('Circulation');
|
||||
const circulationRepo = this.dataSource.getRepository(Circulation);
|
||||
const activeCirculations = await circulationRepo.find({
|
||||
where: {
|
||||
correspondenceId: correspondence.id,
|
||||
status: 'OPEN',
|
||||
statusCode: 'OPEN',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1033,24 +1038,46 @@ export class CorrespondenceService {
|
||||
}
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
// Force close all active circulations
|
||||
if (activeCirculations.length > 0) {
|
||||
await queryRunner.manager.update(
|
||||
'Circulation',
|
||||
{
|
||||
correspondenceId: correspondence.id,
|
||||
status: 'OPEN',
|
||||
},
|
||||
{
|
||||
status: 'FORCE_CLOSED',
|
||||
closedAt: new Date(),
|
||||
closedBy: user.user_id,
|
||||
closeReason: `Correspondence cancelled: ${reason}`,
|
||||
}
|
||||
);
|
||||
}
|
||||
for (const circ of activeCirculations) {
|
||||
try {
|
||||
await this.circulationService.forceClose(
|
||||
circ.publicId,
|
||||
`Correspondence cancelled: ${reason}`,
|
||||
user
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
// T012: Enqueue BullMQ notification for affected assignees
|
||||
// CirculationService.forceClose already updates status, we just need to notify.
|
||||
// Ideally we'd notify the people who were pending.
|
||||
const circWithRoutings = await this.dataSource
|
||||
.getRepository(CirculationRouting)
|
||||
.find({
|
||||
where: { circulationId: circ.id, status: 'REJECTED' },
|
||||
});
|
||||
for (const r of circWithRoutings) {
|
||||
if (r.assignedTo) {
|
||||
void this.notificationService.send({
|
||||
userId: r.assignedTo,
|
||||
title: 'Circulation Force Closed',
|
||||
message: `ใบเวียน ${circ.circulationNo} ถูกปิดแบบบังคับ เนื่องจากเอกสารต้นทางถูกยกเลิก`,
|
||||
type: 'EMAIL',
|
||||
entityType: 'circulation',
|
||||
entityId: circ.id,
|
||||
link: `/circulations/${circ.publicId}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(
|
||||
`Failed to force close circulation ${circ.publicId}: ${(e as Error).message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-index cancelled status in Elasticsearch (fire-and-forget)
|
||||
void this.searchService.indexDocument({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import _request from 'supertest';
|
||||
import { AppModule } from '../src/app.module';
|
||||
import { RoutingTemplate } from '../src/modules/correspondence/entities/routing-template.entity';
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user