261 lines
7.9 KiB
TypeScript
261 lines
7.9 KiB
TypeScript
import {
|
||
Controller,
|
||
Get,
|
||
Post,
|
||
Put,
|
||
Delete,
|
||
Body,
|
||
Param,
|
||
Query,
|
||
UseGuards,
|
||
Request,
|
||
Res,
|
||
UseInterceptors,
|
||
UploadedFile,
|
||
BadRequestException,
|
||
} from '@nestjs/common';
|
||
import { FileInterceptor } from '@nestjs/platform-express';
|
||
import type { Response } from 'express';
|
||
import * as fs from 'fs';
|
||
import { ClassroomRentalsService } from './classroom-rentals.service';
|
||
import { CreateRentalDto, UpdateRentalDto } from './dto/rental.dto';
|
||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||
import { OperationLogsService } from '../operation-logs/operation-logs.service';
|
||
import { extractRequestInfo } from '../common/request-utils';
|
||
import { RequirePermission } from '../auth/decorators/permission.decorator';
|
||
|
||
@UseGuards(JwtAuthGuard)
|
||
@Controller('classroom-rentals')
|
||
export class ClassroomRentalsController {
|
||
constructor(
|
||
private service: ClassroomRentalsService,
|
||
private logService: OperationLogsService,
|
||
) {}
|
||
|
||
@Get()
|
||
@RequirePermission('rental:view')
|
||
findAll(
|
||
@Query('classroomId') classroomId?: string,
|
||
@Query('lesseeOrganizationId') lesseeOrganizationId?: string,
|
||
@Query('month') month?: string,
|
||
@Query('includeEnded') includeEnded?: string,
|
||
) {
|
||
return this.service.findAll({
|
||
classroomId: classroomId ? +classroomId : undefined,
|
||
lesseeOrganizationId: lesseeOrganizationId ? +lesseeOrganizationId : undefined,
|
||
month,
|
||
includeEnded: includeEnded === 'true',
|
||
});
|
||
}
|
||
|
||
@Get('schedule')
|
||
@RequirePermission('rental:view')
|
||
getSchedule(@Query('year') year?: string, @Query('month') month?: string) {
|
||
const now = new Date();
|
||
const y = year ? +year : now.getFullYear();
|
||
const m = month ? +month : now.getMonth() + 1;
|
||
if (m < 1 || m > 12) throw new BadRequestException('月份必须在 1-12 之间');
|
||
return this.service.getSchedule(y, m);
|
||
}
|
||
|
||
@Get('unavailable-dates')
|
||
@RequirePermission('rental:view')
|
||
getUnavailableDates(
|
||
@Query('classroomId') classroomId?: string,
|
||
@Query('year') year?: string,
|
||
@Query('month') month?: string,
|
||
@Query('excludeId') excludeId?: string,
|
||
) {
|
||
const parsedClassroomId = Number(classroomId);
|
||
const parsedYear = Number(year);
|
||
const parsedMonth = Number(month);
|
||
if (!Number.isInteger(parsedClassroomId) || parsedClassroomId <= 0) {
|
||
throw new BadRequestException('请选择有效教室');
|
||
}
|
||
if (!Number.isInteger(parsedYear) || parsedYear < 2000 || parsedYear > 2100) {
|
||
throw new BadRequestException('年份不合法');
|
||
}
|
||
if (!Number.isInteger(parsedMonth) || parsedMonth < 1 || parsedMonth > 12) {
|
||
throw new BadRequestException('月份必须在 1-12 之间');
|
||
}
|
||
const parsedExcludeId = excludeId === undefined ? undefined : Number(excludeId);
|
||
if (
|
||
parsedExcludeId !== undefined &&
|
||
(!Number.isInteger(parsedExcludeId) || parsedExcludeId <= 0)
|
||
) {
|
||
throw new BadRequestException('排除的租赁订单不合法');
|
||
}
|
||
return this.service.getUnavailableDates(
|
||
parsedClassroomId,
|
||
parsedYear,
|
||
parsedMonth,
|
||
parsedExcludeId,
|
||
);
|
||
}
|
||
|
||
@Get(':id')
|
||
@RequirePermission('rental:view')
|
||
findOne(@Param('id') id: string) {
|
||
return this.service.findOne(+id);
|
||
}
|
||
|
||
@Post()
|
||
@RequirePermission('rental:create')
|
||
async create(@Body() dto: CreateRentalDto, @Request() req: any) {
|
||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||
const result = await this.service.create(dto, req.user?.id);
|
||
await this.logService.log({
|
||
userId: req.user?.id,
|
||
username: req.user?.username,
|
||
module: '教室租赁',
|
||
action: '新增租赁',
|
||
targetId: result.id,
|
||
targetType: 'classroom-rental',
|
||
detail: `教室${dto.classroomId} 承租机构${dto.lesseeOrganizationId} ${dto.startDate}~${dto.endDate}`,
|
||
ipAddress,
|
||
userAgent,
|
||
});
|
||
return result;
|
||
}
|
||
|
||
@Put(':id')
|
||
@RequirePermission('rental:edit')
|
||
async update(@Param('id') id: string, @Body() dto: UpdateRentalDto, @Request() req: any) {
|
||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||
const result = await this.service.update(+id, dto);
|
||
await this.logService.log({
|
||
userId: req.user?.id,
|
||
username: req.user?.username,
|
||
module: '教室租赁',
|
||
action: '编辑租赁',
|
||
targetId: +id,
|
||
targetType: 'classroom-rental',
|
||
detail: JSON.stringify(dto),
|
||
ipAddress,
|
||
userAgent,
|
||
});
|
||
return result;
|
||
}
|
||
|
||
@Put(':id/cancel')
|
||
@RequirePermission('rental:edit')
|
||
async cancel(@Param('id') id: string, @Request() req: any) {
|
||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||
const result = await this.service.cancel(+id);
|
||
await this.logService.log({
|
||
userId: req.user?.id,
|
||
username: req.user?.username,
|
||
module: '教室租赁',
|
||
action: '取消租赁',
|
||
targetId: +id,
|
||
targetType: 'classroom-rental',
|
||
ipAddress,
|
||
userAgent,
|
||
});
|
||
return result;
|
||
}
|
||
|
||
@Put(':id/end')
|
||
@RequirePermission('rental:edit')
|
||
async end(@Param('id') id: string, @Request() req: any) {
|
||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||
const result = await this.service.end(+id);
|
||
await this.logService.log({
|
||
userId: req.user?.id,
|
||
username: req.user?.username,
|
||
module: '教室租赁',
|
||
action: '结束租赁',
|
||
targetId: +id,
|
||
targetType: 'classroom-rental',
|
||
ipAddress,
|
||
userAgent,
|
||
});
|
||
return result;
|
||
}
|
||
|
||
@Delete(':id')
|
||
@RequirePermission('rental:delete')
|
||
async remove(@Param('id') id: string, @Request() req: any) {
|
||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||
const result = await this.service.remove(+id);
|
||
await this.logService.log({
|
||
userId: req.user?.id,
|
||
username: req.user?.username,
|
||
module: '教室租赁',
|
||
action: '删除租赁',
|
||
targetId: +id,
|
||
targetType: 'classroom-rental',
|
||
ipAddress,
|
||
userAgent,
|
||
});
|
||
return result;
|
||
}
|
||
|
||
// 合同上传:multer 限制 10MB + 仅 PDF
|
||
@Post(':id/contract')
|
||
@RequirePermission('rental:edit')
|
||
@UseInterceptors(
|
||
FileInterceptor('file', {
|
||
limits: { fileSize: 10 * 1024 * 1024 },
|
||
fileFilter: (_req, file, cb) => {
|
||
if (file.mimetype !== 'application/pdf') {
|
||
return cb(new BadRequestException('仅支持 PDF 文件'), false);
|
||
}
|
||
cb(null, true);
|
||
},
|
||
}),
|
||
)
|
||
async uploadContract(
|
||
@Param('id') id: string,
|
||
@UploadedFile() file: Express.Multer.File,
|
||
@Request() req: any,
|
||
) {
|
||
if (!file) throw new BadRequestException('请上传合同文件');
|
||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||
const result = await this.service.attachContract(+id, file);
|
||
await this.logService.log({
|
||
userId: req.user?.id,
|
||
username: req.user?.username,
|
||
module: '教室租赁',
|
||
action: '上传合同',
|
||
targetId: +id,
|
||
targetType: 'classroom-rental',
|
||
detail: file.originalname,
|
||
ipAddress,
|
||
userAgent,
|
||
});
|
||
return result;
|
||
}
|
||
|
||
@Get(':id/contract')
|
||
@RequirePermission('rental:view')
|
||
async downloadContract(@Param('id') id: string, @Res() res: Response) {
|
||
const { fullPath, originalName } = await this.service.getContractPath(+id);
|
||
res.setHeader('Content-Type', 'application/pdf');
|
||
res.setHeader(
|
||
'Content-Disposition',
|
||
`attachment; filename="${encodeURIComponent(originalName)}"`,
|
||
);
|
||
const stream = fs.createReadStream(fullPath);
|
||
stream.pipe(res);
|
||
}
|
||
|
||
@Delete(':id/contract')
|
||
@RequirePermission('rental:edit')
|
||
async deleteContract(@Param('id') id: string, @Request() req: any) {
|
||
const { ipAddress, userAgent } = extractRequestInfo(req);
|
||
const result = await this.service.removeContract(+id);
|
||
await this.logService.log({
|
||
userId: req.user?.id,
|
||
username: req.user?.username,
|
||
module: '教室租赁',
|
||
action: '删除合同',
|
||
targetId: +id,
|
||
targetType: 'classroom-rental',
|
||
ipAddress,
|
||
userAgent,
|
||
});
|
||
return result;
|
||
}
|
||
}
|