feat: build reporting workbench workflow

This commit is contained in:
hectorzhao
2026-09-02 14:30:15 +08:00
parent 53d3668306
commit 9b8196ecab
31 changed files with 7651 additions and 1973 deletions
@@ -1,10 +1,28 @@
import { BadRequestException, Body, Controller, Get, Param, Post, Put, Query, Res, UploadedFile, UseInterceptors } from '@nestjs/common';
import {
BadRequestException,
Body,
Controller,
Get,
Param,
Post,
Put,
Query,
Res,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiTags } from '@nestjs/swagger';
import { RequireRecentAuthentication } from '../auth/require-recent-authentication.decorator';
import { CurrentSessionUserId } from '../auth/current-session-user.decorator';
import { ReportMaterialsService } from './report-materials.service';
import { CreateImportProfileDto, CreateReportBatchDto, ImportCommitDto, ReviewImportItemsDto } from './report-materials.contracts';
import {
CreateImportProfileDto,
CreateReportBatchDto,
ImportCommitDto,
ReviewImportItemsDto,
SingleReportMaterialDto,
} from './report-materials.contracts';
type UploadedWorkbook = { originalname: string; mimetype: string; size: number; buffer: Buffer };
type DownloadResponse = { setHeader(name: string, value: string): void; send(content: Buffer): void };
@@ -25,17 +43,37 @@ export class ReportMaterialsController {
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.service.listPending({ reportType, tenantId, applicationId, keyword, startAt, endAt, page: Number(page), pageSize: Number(pageSize) });
return this.service.listPending({
reportType,
tenantId,
applicationId,
keyword,
startAt,
endAt,
page: Number(page),
pageSize: Number(pageSize),
});
}
@Get('templates/:reportType')
async downloadTemplate(@Param('reportType') reportType: 'signature' | 'drainage', @CurrentSessionUserId() operatorId: string | undefined, @Res() response: DownloadResponse) {
if (!['signature', 'drainage'].includes(reportType)) throw new BadRequestException('reportType must be signature or drainage');
async downloadTemplate(
@Param('reportType') reportType: 'signature' | 'drainage',
@CurrentSessionUserId() operatorId: string | undefined,
@Res() response: DownloadResponse,
) {
if (!['signature', 'drainage'].includes(reportType))
throw new BadRequestException('reportType must be signature or drainage');
this.sendWorkbook(response, await this.service.buildOfficialTemplate(reportType, operatorId));
}
@Get('pending/export')
async exportPending(@Query('reportType') reportType: 'signature' | 'drainage' | undefined, @Query('tenantId') tenantId: string | undefined, @Query('applicationId') applicationId: string | undefined, @CurrentSessionUserId() operatorId: string | undefined, @Res() response: DownloadResponse) {
async exportPending(
@Query('reportType') reportType: 'signature' | 'drainage' | undefined,
@Query('tenantId') tenantId: string | undefined,
@Query('applicationId') applicationId: string | undefined,
@CurrentSessionUserId() operatorId: string | undefined,
@Res() response: DownloadResponse,
) {
this.sendWorkbook(response, await this.service.exportPending({ reportType, tenantId, applicationId }, operatorId));
}
@@ -52,7 +90,11 @@ export class ReportMaterialsController {
@Post('imports/analyze')
@UseInterceptors(FileInterceptor('file', { limits: { fileSize: 10 * 1024 * 1024, files: 1, fields: 12, parts: 13 } }))
analyzeImport(@UploadedFile() file: UploadedWorkbook, @Body() body: Record<string, string>, @CurrentSessionUserId() operatorId?: string) {
analyzeImport(
@UploadedFile() file: UploadedWorkbook,
@Body() body: Record<string, string>,
@CurrentSessionUserId() operatorId?: string,
) {
if (!file) throw new BadRequestException('请选择 XLSX 文件');
return this.service.analyzeImport(file, {
tenantId: body.tenantId,
@@ -82,12 +124,24 @@ export class ReportMaterialsController {
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.service.listImportReviewBatches({ reportType, status, keyword, startAt, endAt, page: Number(page), pageSize: Number(pageSize) });
return this.service.listImportReviewBatches({
reportType,
status,
keyword,
startAt,
endAt,
page: Number(page),
pageSize: Number(pageSize),
});
}
@Post('imports/:id/review')
@RequireRecentAuthentication()
reviewImportItems(@Param('id') id: string, @Body() body: ReviewImportItemsDto, @CurrentSessionUserId() reviewerId?: string) {
reviewImportItems(
@Param('id') id: string,
@Body() body: ReviewImportItemsDto,
@CurrentSessionUserId() reviewerId?: string,
) {
return this.service.reviewImportItems(id, { ...body, reviewerId });
}
@@ -102,6 +156,31 @@ export class ReportMaterialsController {
return this.service.listBatches({ keyword, startAt, endAt, page: Number(page), pageSize: Number(pageSize) });
}
@Get('batches/:id')
getBatch(@Param('id') id: string) {
return this.service.getBatch(id);
}
@Get('batches/:id/tasks')
listBatchTasks(
@Param('id') id: string,
@Query('keyword') keyword?: string,
@Query('reportType') reportType?: string,
@Query('status') status?: string,
@Query('channelId') channelId?: string,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
return this.service.listBatchTasks(id, {
keyword,
reportType,
status,
channelId,
page: Number(page),
pageSize: Number(pageSize),
});
}
@Post('batches/preflight')
preflightBatch(@Body() body: CreateReportBatchDto) {
return this.service.preflightBatch(body);
@@ -113,6 +192,20 @@ export class ReportMaterialsController {
return this.service.createBatch({ ...body, createdById: operatorId });
}
@Post('single-detail')
getSingleMaterialDetail(@Body() body: SingleReportMaterialDto) {
return this.service.getSingleMaterialDetail(body);
}
@Post('single-export')
@RequireRecentAuthentication()
async exportSingleMaterial(
@Body() body: SingleReportMaterialDto,
@CurrentSessionUserId() operatorId: string | undefined,
@Res() response: DownloadResponse,
) {
this.sendWorkbook(response, await this.service.exportSingleMaterial(body, operatorId));
}
private sendWorkbook(response: DownloadResponse, exported: { fileName: string; content: Buffer }) {
response.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');