feat(eims): printable receipt PDF with RRN and QR

eims-receipt-document.mapper.ts maps an EimsReceipt onto the shared
InvoiceDocumentModel layout, reading amounts back out of the stored request
body. Refuses to render anything not REGISTERED. GET
invoices/:id/eims/receipts/:receiptId/document, scoped to the invoice.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Hagernesh
2026-08-15 06:44:53 +00:00
parent d66cfe2328
commit 8e70352407
6 changed files with 239 additions and 5 deletions

View File

@@ -1,8 +1,10 @@
import { Body, Controller, Get, Param, ParseUUIDPipe, Post } from "@nestjs/common";
import { Body, Controller, Get, Param, ParseUUIDPipe, Post, Res } from "@nestjs/common";
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
import type { Response } from "express";
import { BookingStaff } from "../../common/booking-guards";
import { FREIGHT_PERMS } from "../../seed/freight-permissions.registry";
import { sendPdf } from "../billing/billing.controller";
import { CancelEimsRegistrationDto } from "./dto/cancel-eims-registration.dto";
import { RegisterSalesReceiptDto } from "./dto/register-sales-receipt.dto";
import { RegisterWithholdingReceiptDto } from "./dto/register-withholding-receipt.dto";
@@ -110,4 +112,16 @@ export class EimsInvoiceController {
listReceipts(@Param("id", ParseUUIDPipe) id: string) {
return this.receipts.listReceipts(id);
}
@Get(":id/eims/receipts/:receiptId/document")
@BookingStaff(FREIGHT_PERMS.invoices.export)
@ApiOperation({ summary: "Download the sealed receipt PDF (RRN + QR) for a filed EIMS receipt" })
async receiptDocument(
@Param("id", ParseUUIDPipe) id: string,
@Param("receiptId", ParseUUIDPipe) receiptId: string,
@Res() res: Response,
) {
const { filename, buffer } = await this.receipts.document(id, receiptId);
sendPdf(res, filename, buffer);
}
}