Skip to main content

Report Files Module

Location: backend/src/report-files/

Server-side generation of all report files - student report-card PDFs, year-end PDFs, the designed "report card", class-summary PDF/CSV/XLSX, exam reports, and a bulk zip of a whole class's report cards. The files are generated on demand and streamed to the client.

Generation used to live in the browser (jsPDF, xlsx, @react-pdf/renderer); it now runs here. The grade data being rendered comes from the Calculation Module, so no calculation logic is duplicated - only the rendering moved.

Files

FilePurpose
report-files.module.tsModule definition
report-files.controller.tsStreaming endpoints
report-files.service.tsFetch data → generate → { buffer, filename, contentType }
generation/pdf.generator.tsjsPDF: student term report + class summary
generation/year-pdf.generator.tsjsPDF: year-end report + year class summary
generation/export.generator.tsCSV + XLSX (write-excel-file, async) class summary
generation/year-export.generator.tsCSV + XLSX year class summary
generation/student-report.generator.tsxreact-pdf designed report card
generation/exam-report.generator.tsxreact-pdf exam report
generation/class-summary.transform.tstermResultsToClassSummary + ClassSummary type
generation/grading-rules.tsGrading-model display rules (ported from the frontend)

Endpoints

Under AuthGuard + PermissionGuard + ClassTeacherGuard. Every endpoint takes studentGroupId so the guard can resolve the class. File responses set Content-Type + Content-Disposition.

MethodRouteQueryPermission
GET/reports/files/student-term.pdfstudentId, termId, studentGroupIdreporting:read
GET/reports/files/student-year.pdfstudentId, academicYearId, studentGroupIdreporting:read
GET/reports/files/student-report-card.pdfstudentId, termId, studentGroupIdreporting:read
GET/reports/files/class-summarystudentGroupId, termId, reportType, format=pdf|csv|xlsxreporting:read
GET/reports/files/exam-report.pdfstudentGroupId, termId, reportTypereporting:read
GET/reports/files/class-zipstudentGroupId, termId, reportTypereporting:read
POST/reports/files/class-summary/persist{ studentGroupId, termId, reportType }reporting:create

persist generates the three class-summary formats and stores them via the Reporting Module's uploadClassSummaryFile, replacing the old client "generate & upload all" flow.

Bulk zip streaming

class-zip is built for large classes. It plans the entries (auth + the cached calculation fetch) before writing any response, then calls reply.hijack() and pipes an archiver stream straight to the socket. PDFs are generated one at a time - the next entry is appended only after the previous is flushed - so at most one PDF buffer is live and memory stays flat regardless of class size. Because hijacking bypasses the framework CORS layer, the handler sets Access-Control-Allow-Origin/-Credentials and Access-Control-Expose-Headers: Content-Disposition manually.

Notes

  • The .tsx generators require "jsx": "react" in the backend tsconfig.json and the react + @react-pdf/renderer dependencies; they render with renderToBuffer (no browser/DOM).
  • archiver is pinned to v7 (v8 is a typeless ESM-only rewrite).
  • CORS exposes Content-Disposition globally (createApp.ts) so the frontend can read the server-provided filename.