import { Injectable, NotFoundException, BadRequestException, ForbiddenException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, DataSource, Not } from 'typeorm'; import { Circulation } from './entities/circulation.entity'; import { CirculationRouting } from './entities/circulation-routing.entity'; import { User } from '../user/entities/user.entity'; import { CreateCirculationDto } from './dto/create-circulation.dto'; import { UpdateCirculationRoutingDto } from './dto/update-circulation-routing.dto'; import { SearchCirculationDto } from './dto/search-circulation.dto'; import { DocumentNumberingService } from '../document-numbering/services/document-numbering.service'; @Injectable() export class CirculationService { constructor( @InjectRepository(Circulation) private circulationRepo: Repository, @InjectRepository(CirculationRouting) private routingRepo: Repository, private numberingService: DocumentNumberingService, private dataSource: DataSource ) {} async create(createDto: CreateCirculationDto, user: User) { if (!user.primaryOrganizationId) { throw new BadRequestException('User must belong to an organization'); } const queryRunner = this.dataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { // Generate No. using DocumentNumberingService (Type 900 - Circulation) const result = await this.numberingService.generateNextNumber({ projectId: createDto.projectId || 0, // Use projectId from DTO or 0 originatorOrganizationId: user.primaryOrganizationId, typeId: 900, // Fixed Type ID for Circulation year: new Date().getFullYear(), customTokens: { TYPE_CODE: 'CIR', ORG_CODE: 'ORG', }, }); const circulation = queryRunner.manager.create(Circulation, { organizationId: user.primaryOrganizationId, correspondenceId: createDto.correspondenceId, circulationNo: result.number, subject: createDto.subject, statusCode: 'OPEN', createdByUserId: user.user_id, }); const savedCirculation = await queryRunner.manager.save(circulation); if (createDto.assigneeIds && createDto.assigneeIds.length > 0) { const routings = createDto.assigneeIds.map((userId, index) => queryRunner.manager.create(CirculationRouting, { circulationId: savedCirculation.id, stepNumber: index + 1, organizationId: user.primaryOrganizationId, assignedTo: userId, status: 'PENDING', }) ); await queryRunner.manager.save(routings); } await queryRunner.commitTransaction(); return savedCirculation; } catch (err) { await queryRunner.rollbackTransaction(); throw err; } finally { await queryRunner.release(); } } async findAll(searchDto: SearchCirculationDto, user: User) { const { search, status, page = 1, limit = 20 } = searchDto; const query = this.circulationRepo .createQueryBuilder('c') .leftJoinAndSelect('c.creator', 'creator') .where('c.organizationId = :orgId', { orgId: user.primaryOrganizationId, }); if (status) { query.andWhere('c.statusCode = :status', { status }); } query .orderBy('c.createdAt', 'DESC') .skip((page - 1) * limit) .take(limit); const [data, total] = await query.getManyAndCount(); return { data, meta: { total, page, limit } }; } async findOne(id: number) { const circulation = await this.circulationRepo.findOne({ where: { id }, relations: ['routings', 'routings.assignee', 'correspondence', 'creator'], order: { routings: { stepNumber: 'ASC' } }, }); if (!circulation) throw new NotFoundException('Circulation not found'); return circulation; } // ✅ Logic อัปเดตสถานะและปิดงาน async updateRoutingStatus( routingId: number, dto: UpdateCirculationRoutingDto, user: User ) { const routing = await this.routingRepo.findOne({ where: { id: routingId }, relations: ['circulation'], }); if (!routing) throw new NotFoundException('Routing task not found'); // Check Permission: คนทำต้องเป็นเจ้าของ Task if (routing.assignedTo !== user.user_id) { throw new ForbiddenException('You are not assigned to this task'); } // Update Routing routing.status = dto.status; routing.comments = dto.comments; routing.completedAt = new Date(); await this.routingRepo.save(routing); // Check: ถ้าทุกคนทำเสร็จแล้ว ให้ปิดใบเวียน (Master) const pendingCount = await this.routingRepo.count({ where: { circulationId: routing.circulationId, status: 'PENDING', // หรือ status ที่ยังไม่เสร็จ }, }); if (pendingCount === 0) { await this.circulationRepo.update(routing.circulationId, { statusCode: 'COMPLETED', closedAt: new Date(), }); } return routing; } }