mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
feat: setup pdf on the central billing
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { Controller, Get, Param, ParseUUIDPipe } from "@nestjs/common";
|
||||
import { Controller, Get, Param, ParseUUIDPipe, Res } from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import type { Response } from "express";
|
||||
|
||||
import { FreightAdmin } from "../../common/booking-guards";
|
||||
import { BillingService } from "./billing.service";
|
||||
@@ -21,4 +22,26 @@ export class BillingController {
|
||||
findById(@Param("id", ParseUUIDPipe) id: string) {
|
||||
return this.billingService.findById(id);
|
||||
}
|
||||
|
||||
@Get("invoices/:id/document")
|
||||
@ApiOperation({ summary: "Download the sealed invoice PDF" })
|
||||
async document(@Param("id", ParseUUIDPipe) id: string, @Res() res: Response) {
|
||||
const { filename, buffer } = await this.billingService.document(id);
|
||||
sendPdf(res, filename, buffer);
|
||||
}
|
||||
|
||||
@Get("invoices/:id/receipt")
|
||||
@ApiOperation({ summary: "Download the sealed payment receipt PDF" })
|
||||
async receipt(@Param("id", ParseUUIDPipe) id: string, @Res() res: Response) {
|
||||
const { filename, buffer } = await this.billingService.receipt(id);
|
||||
sendPdf(res, filename, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/** Stream a generated PDF as a file download. */
|
||||
export function sendPdf(res: Response, filename: string, buffer: Buffer): void {
|
||||
res.setHeader("Content-Type", "application/pdf");
|
||||
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
|
||||
res.setHeader("Content-Length", buffer.length);
|
||||
res.send(buffer);
|
||||
}
|
||||
|
||||
@@ -221,19 +221,33 @@ export class BillingService {
|
||||
}
|
||||
}
|
||||
|
||||
/** Every invoice billed to a company, newest first, with billing relations. */
|
||||
findByCompany(companyId: string): Promise<Invoice[]> {
|
||||
/**
|
||||
* Every invoice billed to a company, newest first, with billing relations.
|
||||
* Optionally narrow to a single source record (e.g. a booking's invoices) via
|
||||
* `{ source, sourceId }`.
|
||||
*/
|
||||
findByCompany(
|
||||
companyId: string,
|
||||
filter: { source?: string; sourceId?: string } = {},
|
||||
): Promise<Invoice[]> {
|
||||
return this.invoices.findAll({
|
||||
where: { companyId },
|
||||
where: {
|
||||
companyId,
|
||||
...(filter.source ? { source: filter.source } : {}),
|
||||
...(filter.sourceId ? { sourceId: filter.sourceId } : {}),
|
||||
},
|
||||
relations: { company: true, companyProfile: true },
|
||||
order: { createdAt: "DESC" },
|
||||
});
|
||||
}
|
||||
|
||||
/** Invoices for the signed-in customer; empty when they have no company. */
|
||||
async findForUser(userId: string): Promise<Invoice[]> {
|
||||
async findForUser(
|
||||
userId: string,
|
||||
filter: { source?: string; sourceId?: string } = {},
|
||||
): Promise<Invoice[]> {
|
||||
const companyId = await this.resolveCompanyId(userId);
|
||||
return companyId ? this.findByCompany(companyId) : [];
|
||||
return companyId ? this.findByCompany(companyId, filter) : [];
|
||||
}
|
||||
|
||||
/** Company-scoped invoice detail (+ lines); 404 when not owned by the user. */
|
||||
@@ -267,6 +281,24 @@ export class BillingService {
|
||||
);
|
||||
}
|
||||
|
||||
/** Sealed invoice PDF for one of the customer's own invoices (ownership-checked). */
|
||||
async documentForUser(
|
||||
id: string,
|
||||
userId: string,
|
||||
): Promise<{ filename: string; buffer: Buffer }> {
|
||||
await this.findByIdForUser(id, userId);
|
||||
return this.document(id);
|
||||
}
|
||||
|
||||
/** Sealed receipt PDF for one of the customer's own invoices (ownership-checked). */
|
||||
async receiptForUser(
|
||||
id: string,
|
||||
userId: string,
|
||||
): Promise<{ filename: string; buffer: Buffer }> {
|
||||
await this.findByIdForUser(id, userId);
|
||||
return this.receipt(id);
|
||||
}
|
||||
|
||||
// ── Generation ───────────────────────────────────────────────────────────────
|
||||
|
||||
/** `<CODE>-YYYYMMDD-00001` — sequential per day & prefix, within the active transaction. */
|
||||
|
||||
@@ -5,14 +5,18 @@ import {
|
||||
Param,
|
||||
ParseUUIDPipe,
|
||||
Post,
|
||||
Query,
|
||||
Res,
|
||||
} from "@nestjs/common";
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import type { Response } from "express";
|
||||
import { CurrentUser } from "@edr/api-common";
|
||||
|
||||
import {
|
||||
type AuthUserPayload,
|
||||
resolveAuthUserId,
|
||||
} from "../../common/resolve-auth-user-id";
|
||||
import { sendPdf } from "./billing.controller";
|
||||
import { BillingService } from "./billing.service";
|
||||
import { PayInvoiceDto } from "./dto/pay-invoice.dto";
|
||||
|
||||
@@ -29,8 +33,15 @@ export class PortalBillingController {
|
||||
|
||||
@Get("my-invoices")
|
||||
@ApiOperation({ summary: "List the signed-in customer's invoices" })
|
||||
findMine(@CurrentUser() user: AuthUserPayload) {
|
||||
return this.billingService.findForUser(resolveAuthUserId(user));
|
||||
findMine(
|
||||
@CurrentUser() user: AuthUserPayload,
|
||||
@Query("source") source?: string,
|
||||
@Query("sourceId") sourceId?: string,
|
||||
) {
|
||||
return this.billingService.findForUser(resolveAuthUserId(user), {
|
||||
source,
|
||||
sourceId,
|
||||
});
|
||||
}
|
||||
|
||||
@Get("my-invoices/:id")
|
||||
@@ -42,6 +53,34 @@ export class PortalBillingController {
|
||||
return this.billingService.findByIdForUser(id, resolveAuthUserId(user));
|
||||
}
|
||||
|
||||
@Get("my-invoices/:id/document")
|
||||
@ApiOperation({ summary: "Download one of the customer's invoice PDFs" })
|
||||
async document(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@CurrentUser() user: AuthUserPayload,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
const { filename, buffer } = await this.billingService.documentForUser(
|
||||
id,
|
||||
resolveAuthUserId(user),
|
||||
);
|
||||
sendPdf(res, filename, buffer);
|
||||
}
|
||||
|
||||
@Get("my-invoices/:id/receipt")
|
||||
@ApiOperation({ summary: "Download one of the customer's payment receipt PDFs" })
|
||||
async receipt(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@CurrentUser() user: AuthUserPayload,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
const { filename, buffer } = await this.billingService.receiptForUser(
|
||||
id,
|
||||
resolveAuthUserId(user),
|
||||
);
|
||||
sendPdf(res, filename, buffer);
|
||||
}
|
||||
|
||||
@Post("my-invoices/:id/pay")
|
||||
@ApiOperation({ summary: "Initiate payment for one of the customer's invoices" })
|
||||
pay(
|
||||
|
||||
Reference in New Issue
Block a user