feat: botón Generar PDF en los reportes HTML (Chrome sin pantalla, papel y márgenes de la plantilla fijos)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d358f0cf1
commit
f71577e792
13 files changed
+287
-19
No files matched your search
@@ -7,6 +7,7 @@ import { PermissionsService } from '../permissions/permissions.service.js';
|
||||
import { SapDbService } from '../sapdb/sapdb.service.js';
|
||||
import { HTML_REPORTS } from './html-reports.js';
|
||||
import { createRenderer } from './html-render.js';
|
||||
import { HtmlPdfService } from './html-pdf.service.js';
|
||||
import type { HtmlReportDef, HtmlReportInfo, HtmlReportValues } from './html-reports.types.js';
|
||||
|
||||
/** Ruta de menú que permite ejecutar los reportes HTML. */
|
||||
@@ -55,6 +56,7 @@ export class HtmlReportsService {
|
||||
private readonly permissions: PermissionsService,
|
||||
private readonly audit: AuditService,
|
||||
private readonly sapdb: SapDbService,
|
||||
private readonly pdfService: HtmlPdfService,
|
||||
) {}
|
||||
|
||||
private async assertCanRun(user: AuthUser): Promise<void> {
|
||||
@@ -70,21 +72,30 @@ export class HtmlReportsService {
|
||||
|
||||
/** El HTML del reporte con los valores indicados. */
|
||||
async render(user: AuthUser, code: string, raw: Readonly<Record<string, unknown>>, ip: string): Promise<string> {
|
||||
return this.run(user, code, raw, ip, 'html', (html) => html);
|
||||
}
|
||||
|
||||
/** El PDF del reporte: el mismo HTML pasado por Chrome sin pantalla, con el papel y los márgenes de la plantilla. */
|
||||
async pdf(user: AuthUser, code: string, raw: Readonly<Record<string, unknown>>, ip: string): Promise<Buffer> {
|
||||
return this.run(user, code, raw, ip, 'pdf', (html) => this.pdfService.toPdf(html));
|
||||
}
|
||||
|
||||
private async run<T>(user: AuthUser, code: string, raw: Readonly<Record<string, unknown>>, ip: string, format: 'html' | 'pdf', finish: (html: string) => T | Promise<T>): Promise<T> {
|
||||
await this.assertCanRun(user);
|
||||
const def = HTML_REPORTS.get(code);
|
||||
if (!def) throw new NotFoundException(`No existe el reporte ${code}`);
|
||||
const values = parseHtmlValues(def, raw);
|
||||
// solo los nombres de los parámetros: los valores pueden ser datos de clientes
|
||||
const detail = { params: def.params.map((p) => p.name) };
|
||||
const detail = { format, params: def.params.map((p) => p.name) };
|
||||
try {
|
||||
const data = await def.data(values, { query: async (name, params) => (await this.sapdb.run(user, name, params ?? {}, ip)).rows });
|
||||
this.renderer ??= createRenderer(this.config.get('HTML_REPORTS_DIR'));
|
||||
const html = this.renderer.render(def.template, { ...data, params: values });
|
||||
const out = await finish(this.renderer.render(def.template, { ...data, params: values }));
|
||||
await this.audit.record({ action: 'html_report', webUser: user.webUser, ip, resource: code, detail });
|
||||
return html;
|
||||
return out;
|
||||
} catch (e) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
this.logger.error(`Reporte HTML ${code} falló: ${message}`);
|
||||
this.logger.error(`Reporte HTML ${code} (${format}) falló: ${message}`);
|
||||
await this.audit.record({ action: 'html_report', webUser: user.webUser, ip, resource: code, outcome: 'error', detail: { ...detail, error: message.slice(0, 300) } });
|
||||
throw e;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user