Merge pull request 'Delegated the PDF creation task for FE, and BE now only returns the necessary data for it' (#270) from s.yahyaee/yara724-api:main into main

Reviewed-on: Yara724/api#270
This commit is contained in:
2026-08-16 11:53:04 +03:30
5 changed files with 16 additions and 68 deletions

View File

@@ -1,36 +0,0 @@
import { Injectable } from "@nestjs/common";
import {
createPersianPdfDocument,
persianPdfToBuffer,
} from "src/helpers/persian-pdf-document";
import {
InsurerFileReportPdfResult,
InsurerFileReportViewModel,
} from "./case-expert-report.types";
import { PR } from "./persian-report-labels";
@Injectable()
export class CaseExpertReportPdfService {
async render(model: InsurerFileReportViewModel): Promise<InsurerFileReportPdfResult> {
const pdf = createPersianPdfDocument();
pdf.addTitle(model.title);
pdf.addKeyValue(PR.publicId, model.publicId);
pdf.addKeyValue(PR.requestNo, model.requestNo);
pdf.addBlank();
for (const section of model.sections) {
pdf.addSection(section.title);
for (const field of section.fields) {
pdf.addKeyValue(field.label, field.value);
}
pdf.addBlank();
}
const buffer = await persianPdfToBuffer(pdf);
const safeId = (model.publicId || "file").replace(/[^\w.-]+/g, "_");
return {
buffer,
filename: `insurer-file-report-${safeId}.pdf`,
};
}
}

View File

@@ -4,14 +4,12 @@ import {
HttpException, HttpException,
InternalServerErrorException, InternalServerErrorException,
Param, Param,
StreamableFile,
UseGuards, UseGuards,
} from "@nestjs/common"; } from "@nestjs/common";
import { import {
ApiBearerAuth, ApiBearerAuth,
ApiOperation, ApiOperation,
ApiParam, ApiParam,
ApiProduces,
ApiResponse, ApiResponse,
ApiTags, ApiTags,
} from "@nestjs/swagger"; } from "@nestjs/swagger";
@@ -21,6 +19,7 @@ import { Roles } from "src/decorators/roles.decorator";
import { CurrentUser } from "src/decorators/user.decorator"; import { CurrentUser } from "src/decorators/user.decorator";
import { RoleEnum } from "src/Types&Enums/role.enum"; import { RoleEnum } from "src/Types&Enums/role.enum";
import { CaseExpertReportService } from "./case-expert-report.service"; import { CaseExpertReportService } from "./case-expert-report.service";
import { InsurerFileReportViewModel } from "./case-expert-report.types";
@ApiTags("expert-insurer-panel") @ApiTags("expert-insurer-panel")
@Controller("expert-insurer") @Controller("expert-insurer")
@@ -32,36 +31,30 @@ export class CaseExpertReportInsurerController {
private readonly caseExpertReportService: CaseExpertReportService, private readonly caseExpertReportService: CaseExpertReportService,
) {} ) {}
@Get("files/:publicId/report.pdf") @Get("files/:publicId/report")
@ApiOperation({ @ApiOperation({
summary: "Download insurer file report PDF", summary: "Get insurer file report data",
description: description:
"Generates a PDF for the shared publicId (blame + claim combined): damaged owner, driver when different, insurance, vehicle, and accident report sections.", "Returns the structured report data for the given publicId (blame + claim combined): damaged owner, driver when different, insurance, vehicle, and accident report sections. The front-end uses this data to render and generate the PDF.",
}) })
@ApiParam({ name: "publicId" }) @ApiParam({ name: "publicId" })
@ApiProduces("application/pdf") @ApiResponse({ status: 200, description: "Report data" })
@ApiResponse({ status: 200, description: "PDF file" })
@ApiResponse({ status: 404, description: "File not found for this publicId" }) @ApiResponse({ status: 404, description: "File not found for this publicId" })
async downloadInsurerReport( async getInsurerReport(
@CurrentUser() insurer: { clientKey?: string }, @CurrentUser() insurer: { clientKey?: string },
@Param("publicId") publicId: string, @Param("publicId") publicId: string,
): Promise<StreamableFile> { ): Promise<InsurerFileReportViewModel> {
try { try {
const { buffer, filename } = return await this.caseExpertReportService.generateForInsurer(
await this.caseExpertReportService.generateForInsurer( publicId,
publicId, insurer,
insurer, );
);
return new StreamableFile(buffer, {
type: "application/pdf",
disposition: `attachment; filename="${filename}"`,
});
} catch (error) { } catch (error) {
if (error instanceof HttpException) throw error; if (error instanceof HttpException) throw error;
throw new InternalServerErrorException( throw new InternalServerErrorException(
error instanceof Error error instanceof Error
? error.message ? error.message
: "Failed to generate insurer file report PDF", : "Failed to retrieve insurer file report data",
); );
} }
} }

View File

@@ -1,13 +1,12 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { ExpertInsurerModule } from "src/expert-insurer/expert-insurer.module"; import { ExpertInsurerModule } from "src/expert-insurer/expert-insurer.module";
import { CaseExpertReportInsurerController } from "./case-expert-report.controller"; import { CaseExpertReportInsurerController } from "./case-expert-report.controller";
import { CaseExpertReportPdfService } from "./case-expert-report-pdf.service";
import { CaseExpertReportService } from "./case-expert-report.service"; import { CaseExpertReportService } from "./case-expert-report.service";
@Module({ @Module({
imports: [ExpertInsurerModule], imports: [ExpertInsurerModule],
controllers: [CaseExpertReportInsurerController], controllers: [CaseExpertReportInsurerController],
providers: [CaseExpertReportService, CaseExpertReportPdfService], providers: [CaseExpertReportService],
exports: [CaseExpertReportService], exports: [CaseExpertReportService],
}) })
export class CaseExpertReportModule {} export class CaseExpertReportModule {}

View File

@@ -1,20 +1,18 @@
import { Injectable, NotFoundException } from "@nestjs/common"; import { Injectable, NotFoundException } from "@nestjs/common";
import { ExpertInsurerService } from "src/expert-insurer/expert-insurer.service"; import { ExpertInsurerService } from "src/expert-insurer/expert-insurer.service";
import { buildInsurerFileReport } from "./case-expert-report.builder"; import { buildInsurerFileReport } from "./case-expert-report.builder";
import { CaseExpertReportPdfService } from "./case-expert-report-pdf.service"; import { InsurerFileReportViewModel } from "./case-expert-report.types";
import { InsurerFileReportPdfResult } from "./case-expert-report.types";
@Injectable() @Injectable()
export class CaseExpertReportService { export class CaseExpertReportService {
constructor( constructor(
private readonly expertInsurerService: ExpertInsurerService, private readonly expertInsurerService: ExpertInsurerService,
private readonly pdfService: CaseExpertReportPdfService,
) {} ) {}
async generateForInsurer( async generateForInsurer(
publicId: string, publicId: string,
actor: { clientKey?: string }, actor: { clientKey?: string },
): Promise<InsurerFileReportPdfResult> { ): Promise<InsurerFileReportViewModel> {
const clientKey = actor?.clientKey; const clientKey = actor?.clientKey;
if (!clientKey) { if (!clientKey) {
throw new NotFoundException("Insurer context not found"); throw new NotFoundException("Insurer context not found");
@@ -24,7 +22,6 @@ export class CaseExpertReportService {
clientKey, clientKey,
publicId, publicId,
); );
const model = buildInsurerFileReport(file); return buildInsurerFileReport(file);
return this.pdfService.render(model);
} }
} }

View File

@@ -14,8 +14,3 @@ export type InsurerFileReportViewModel = {
requestNo?: string; requestNo?: string;
sections: InsurerFileReportSection[]; sections: InsurerFileReportSection[];
}; };
export type InsurerFileReportPdfResult = {
buffer: Buffer;
filename: string;
};