Files
jorgecuadros-platform/apps/api/src/reports/reports.controller.ts
T
rmancinas 8802f08d4f
Build and Push Images / Build jorgecuadros-web (push) Successful in 1m35s
Build and Push Images / Build jorgecuadros-api (push) Successful in 2m11s
feat(reports): reports module + /inicio + edo-cuenta-datos prefill
- New reports backend (registry, service, controller, outputs, types)
  with catalog endpoint + slug/CSV/XLSX/PDF/print outputs.
- /reportes catalog + /reportes/[slug] runner; ReportRunner + ContextReports
  components wire pre-filtered links from domain pages.
- Fix: /reportes/[slug] now reads searchParams and forwards initialParams to
  ReportRunner so /reportes/edo-cuenta-datos?customerId=... auto-runs
  instead of dropping the id and forcing a manual customer search.
- /inicio landing page; root + login redirect to /inicio.
- Company header env vars + logo asset for PDF/print rendering.
- exceljs + pdfkit deps.
2026-07-23 23:20:41 -07:00

131 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
Body,
Controller,
Get,
Header,
Param,
Post,
Query,
Res,
UseGuards,
} from "@nestjs/common";
import type { Response } from "express";
import { AuthenticatedGuard } from "../auth/authenticated.guard";
import { ReportsService } from "./reports.service";
import {
renderCsv,
renderPdf,
renderPrintHtml,
renderXlsx,
} from "./outputs";
import { findReport } from "./reports.registry";
/**
* Reports routes. Every report is dispatched by slug; outputs are
* differentiated by `?format=...` (default `json`). Reads only — gated
* by AuthenticatedGuard alone, like every other read in the app.
*/
@UseGuards(AuthenticatedGuard)
@Controller("reports")
export class ReportsController {
constructor(private readonly reports: ReportsService) {}
/** Catalog of all registered reports (the /reportes index). */
@Get()
catalog() {
return { items: this.reports.catalog() };
}
/** Run a report and return the JSON result (rows + totals + the def's columns). */
@Get(":slug")
async runJson(
@Param("slug") slug: string,
@Query() query: Record<string, string | undefined>,
) {
const def = findReport(slug);
const result = await this.reports.run(slug, query);
return { ...result, columns: def?.columns ?? [] };
}
/** CSV download. */
@Get(":slug/csv")
@Header("Content-Type", "text/csv; charset=utf-8")
async runCsv(
@Param("slug") slug: string,
@Query() query: Record<string, string | undefined>,
@Res() res: Response,
) {
const def = findReport(slug);
const result = await this.reports.run(slug, query);
const filename = `${def?.title ?? slug}-${new Date().toISOString().slice(0, 10)}.csv`;
res.setHeader(
"Content-Disposition",
`attachment; filename="${filename.replace(/[^\wÀ-ſ .-]/g, "_")}"`,
);
res.send(renderCsv(def?.columns ?? [], result));
}
/** XLSX download. */
@Get(":slug/xlsx")
async runXlsx(
@Param("slug") slug: string,
@Query() query: Record<string, string | undefined>,
@Res() res: Response,
) {
const def = findReport(slug);
const result = await this.reports.run(slug, query);
const filename = `${def?.title ?? slug}-${new Date().toISOString().slice(0, 10)}.xlsx`;
const buf = await renderXlsx(def?.columns ?? [], result);
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
);
res.setHeader(
"Content-Disposition",
`attachment; filename="${filename.replace(/[^\wÀ-ſ .-]/g, "_")}"`,
);
res.send(buf);
}
/** PDF download. */
@Get(":slug/pdf")
async runPdf(
@Param("slug") slug: string,
@Query() query: Record<string, string | undefined>,
@Res() res: Response,
) {
const def = findReport(slug);
const result = await this.reports.run(slug, query);
const buf = await renderPdf(def?.columns ?? [], result, def?.title ?? slug);
const filename = `${def?.title ?? slug}-${new Date().toISOString().slice(0, 10)}.pdf`;
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename="${filename.replace(/[^\wÀ-ſ .-]/g, "_")}"`,
);
res.send(buf);
}
/** Browser-printable HTML view (the user hits Print → Save as PDF). */
@Get(":slug/print")
@Header("Content-Type", "text/html; charset=utf-8")
async runPrint(
@Param("slug") slug: string,
@Query() query: Record<string, string | undefined>,
) {
const def = findReport(slug);
const result = await this.reports.run(slug, query);
return renderPrintHtml(def?.columns ?? [], result, def?.title ?? slug);
}
/** POST a customer-picker-driven report (statement). Mirrors GET to keep
* the param contract simple: same body shape, same response. */
@Post(":slug")
async runPost(
@Param("slug") slug: string,
@Body() body: Record<string, string | undefined>,
) {
return this.reports.run(slug, body);
}
}