Files
Goa-gel-fullstack/backend/src/modules/approvals/approvals.controller.ts

405 lines
12 KiB
TypeScript
Raw Normal View History

import {
Controller,
Post,
Get,
Put,
Param,
Body,
UseGuards,
HttpCode,
HttpStatus,
Query,
Logger,
ForbiddenException,
} from '@nestjs/common';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBearerAuth,
ApiParam,
ApiQuery,
} from '@nestjs/swagger';
import { ApprovalsService } from './approvals.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { ApproveRequestDto } from './dto/approve-request.dto';
import { RejectRequestDto } from './dto/reject-request.dto';
import { RequestChangesDto } from './dto/request-changes.dto';
import { RevalidateDto } from './dto/revalidate.dto';
import { ApprovalResponseDto } from './dto/approval-response.dto';
import { CorrelationId } from '../../common/decorators/correlation-id.decorator';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import { JwtPayload } from '../../common/interfaces/request-context.interface';
import { Department } from '../../database/models/department.model';
import { Inject, BadRequestException } from '@nestjs/common';
import { UuidValidationPipe } from '../../common/pipes/uuid-validation.pipe';
@ApiTags('Approvals')
@Controller('approvals')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
export class ApprovalsController {
private readonly logger = new Logger(ApprovalsController.name);
constructor(
private readonly approvalsService: ApprovalsService,
@Inject(Department)
private readonly departmentModel: typeof Department,
) {}
@Post(':requestId/approve')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Approve request (short form)',
description: 'Approve a license request for a specific department',
})
@ApiParam({
name: 'requestId',
description: 'License request ID (UUID)',
})
@ApiResponse({
status: 200,
description: 'Request approved successfully',
type: ApprovalResponseDto,
})
@ApiResponse({
status: 400,
description: 'Invalid request or no pending approval found',
})
@ApiResponse({
status: 404,
description: 'Request not found',
})
async approveShort(
@Param('requestId', UuidValidationPipe) requestId: string,
@Body() dto: ApproveRequestDto,
@CurrentUser() user: JwtPayload,
@CorrelationId() correlationId: string,
): Promise<ApprovalResponseDto> {
this.logger.debug(`[${correlationId}] Approving request: ${requestId}`);
if (!user.departmentCode) {
throw new ForbiddenException('Department code not found in user context');
}
// Look up department by code to get ID
const department = await this.departmentModel.query().findOne({ code: user.departmentCode });
if (!department) {
throw new BadRequestException(`Department not found: ${user.departmentCode}`);
}
return this.approvalsService.approve(requestId, department.id, dto, user.sub);
}
@Post('requests/:requestId/approve')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Approve request',
description: 'Approve a license request for a specific department',
})
@ApiParam({
name: 'requestId',
description: 'License request ID (UUID)',
})
@ApiResponse({
status: 200,
description: 'Request approved successfully',
type: ApprovalResponseDto,
})
@ApiResponse({
status: 400,
description: 'Invalid request or no pending approval found',
})
@ApiResponse({
status: 404,
description: 'Request not found',
})
async approve(
@Param('requestId', UuidValidationPipe) requestId: string,
@Body() dto: ApproveRequestDto,
@CurrentUser() user: JwtPayload,
@CorrelationId() correlationId: string,
): Promise<ApprovalResponseDto> {
this.logger.debug(`[${correlationId}] Approving request: ${requestId}`);
if (!user.departmentCode) {
throw new ForbiddenException('Department code not found in user context');
}
// Look up department by code to get ID
const department = await this.departmentModel.query().findOne({ code: user.departmentCode });
if (!department) {
throw new BadRequestException(`Department not found: ${user.departmentCode}`);
}
return this.approvalsService.approve(requestId, department.id, dto, user.sub);
}
@Post(':requestId/reject')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Reject request (short form)',
description: 'Reject a license request for a specific department',
})
@ApiParam({
name: 'requestId',
description: 'License request ID (UUID)',
})
@ApiResponse({
status: 200,
description: 'Request rejected successfully',
type: ApprovalResponseDto,
})
@ApiResponse({
status: 400,
description: 'Invalid request or no pending approval found',
})
@ApiResponse({
status: 404,
description: 'Request not found',
})
async rejectShort(
@Param('requestId', UuidValidationPipe) requestId: string,
@Body() dto: RejectRequestDto,
@CurrentUser() user: JwtPayload,
@CorrelationId() correlationId: string,
): Promise<ApprovalResponseDto> {
this.logger.debug(`[${correlationId}] Rejecting request: ${requestId}`);
if (!user.departmentCode) {
throw new ForbiddenException('Department code not found in user context');
}
// Look up department by code to get ID
const department = await this.departmentModel.query().findOne({ code: user.departmentCode });
if (!department) {
throw new BadRequestException(`Department not found: ${user.departmentCode}`);
}
return this.approvalsService.reject(requestId, department.id, dto, user.sub);
}
@Post('requests/:requestId/reject')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Reject request',
description: 'Reject a license request for a specific department',
})
@ApiParam({
name: 'requestId',
description: 'License request ID (UUID)',
})
@ApiResponse({
status: 200,
description: 'Request rejected successfully',
type: ApprovalResponseDto,
})
@ApiResponse({
status: 400,
description: 'Invalid request or no pending approval found',
})
@ApiResponse({
status: 404,
description: 'Request not found',
})
async reject(
@Param('requestId', UuidValidationPipe) requestId: string,
@Body() dto: RejectRequestDto,
@CurrentUser() user: JwtPayload,
@CorrelationId() correlationId: string,
): Promise<ApprovalResponseDto> {
this.logger.debug(`[${correlationId}] Rejecting request: ${requestId}`);
if (!user.departmentCode) {
throw new ForbiddenException('Department code not found in user context');
}
// Look up department by code to get ID
const department = await this.departmentModel.query().findOne({ code: user.departmentCode });
if (!department) {
throw new BadRequestException(`Department not found: ${user.departmentCode}`);
}
return this.approvalsService.reject(requestId, department.id, dto, user.sub);
}
@Post('requests/:requestId/request-changes')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Request changes on request',
description: 'Request changes from applicant for a license request',
})
@ApiParam({
name: 'requestId',
description: 'License request ID (UUID)',
})
@ApiResponse({
status: 200,
description: 'Changes requested successfully',
type: ApprovalResponseDto,
})
@ApiResponse({
status: 400,
description: 'Invalid request or no pending approval found',
})
@ApiResponse({
status: 404,
description: 'Request not found',
})
async requestChanges(
@Param('requestId', UuidValidationPipe) requestId: string,
@Body() dto: RequestChangesDto,
@CurrentUser() user: JwtPayload,
@CorrelationId() correlationId: string,
): Promise<ApprovalResponseDto> {
this.logger.debug(`[${correlationId}] Requesting changes for request: ${requestId}`);
if (!user.departmentCode) {
throw new ForbiddenException('Department code not found in user context');
}
// Look up department by code to get ID
const department = await this.departmentModel.query().findOne({ code: user.departmentCode });
if (!department) {
throw new BadRequestException(`Department not found: ${user.departmentCode}`);
}
return this.approvalsService.requestChanges(requestId, department.id, dto, user.sub);
}
@Get(':approvalId')
@ApiOperation({
summary: 'Get approval by ID',
description: 'Retrieve approval details by approval ID',
})
@ApiParam({
name: 'approvalId',
description: 'Approval ID (UUID)',
})
@ApiResponse({
status: 200,
description: 'Approval details',
type: ApprovalResponseDto,
})
@ApiResponse({
status: 404,
description: 'Approval not found',
})
async findById(
@Param('approvalId', UuidValidationPipe) approvalId: string,
@CorrelationId() correlationId: string,
): Promise<ApprovalResponseDto> {
this.logger.debug(`[${correlationId}] Fetching approval: ${approvalId}`);
return this.approvalsService.findById(approvalId);
}
@Get('requests/:requestId')
@ApiOperation({
summary: 'Get approvals for request',
description: 'Retrieve all approvals for a license request',
})
@ApiParam({
name: 'requestId',
description: 'License request ID (UUID)',
})
@ApiQuery({
name: 'includeInvalidated',
required: false,
description: 'Include invalidated approvals (default: false)',
example: 'false',
})
@ApiResponse({
status: 200,
description: 'List of approvals',
type: [ApprovalResponseDto],
})
@ApiResponse({
status: 404,
description: 'Request not found',
})
async findByRequestId(
@Param('requestId', UuidValidationPipe) requestId: string,
@Query('includeInvalidated') includeInvalidated?: string,
@CorrelationId() correlationId?: string,
): Promise<ApprovalResponseDto[]> {
this.logger.debug(`[${correlationId}] Fetching approvals for request: ${requestId}`);
return this.approvalsService.findByRequestId(
requestId,
includeInvalidated === 'true',
);
}
@Get('department/:departmentCode')
@ApiOperation({
summary: 'Get approvals by department',
description: 'Retrieve approvals for a specific department with pagination',
})
@ApiParam({
name: 'departmentCode',
description: 'Department code',
})
@ApiQuery({
name: 'page',
required: false,
description: 'Page number (default: 1)',
type: Number,
})
@ApiQuery({
name: 'limit',
required: false,
description: 'Items per page (default: 20)',
type: Number,
})
@ApiResponse({
status: 200,
description: 'Paginated list of approvals',
})
async findByDepartment(
@Param('departmentCode') departmentCode: string,
@Query() pagination: any,
@CorrelationId() correlationId: string,
) {
this.logger.debug(`[${correlationId}] Fetching approvals for department: ${departmentCode}`);
// Look up department by code to get ID
const department = await this.departmentModel.query().findOne({ code: departmentCode });
if (!department) {
throw new BadRequestException(`Department not found: ${departmentCode}`);
}
return this.approvalsService.findByDepartment(department.id, pagination);
}
@Put(':approvalId/revalidate')
@HttpCode(HttpStatus.OK)
@ApiOperation({
summary: 'Revalidate approval',
description: 'Revalidate an invalidated approval after document updates',
})
@ApiParam({
name: 'approvalId',
description: 'Approval ID (UUID)',
})
@ApiResponse({
status: 200,
description: 'Approval revalidated successfully',
type: ApprovalResponseDto,
})
@ApiResponse({
status: 400,
description: 'Approval is not in invalidated state',
})
@ApiResponse({
status: 404,
description: 'Approval not found',
})
async revalidate(
@Param('approvalId', UuidValidationPipe) approvalId: string,
@Body() dto: RevalidateDto,
@CorrelationId() correlationId: string,
): Promise<ApprovalResponseDto> {
this.logger.debug(`[${correlationId}] Revalidating approval: ${approvalId}`);
return this.approvalsService.revalidateApproval(approvalId, dto);
}
}