Files
iistwin/server/documents/worker/pdf-converter.service.ts
Ильяс Султанов 1f5ecb6da4 fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric,
  чтобы браузер не округлял значения через input type=number
- пробелы при вставке в number-поля удаляются
- бэкенд нормализует значения number-полей в строку перед сохранением
- добавлен хелпер normalizeFieldValueForStorage

Closes: искажение расчётного счёта и других длинных числовых полей
2026-07-07 21:03:40 +03:00

69 lines
2.0 KiB
TypeScript

import { chromium, type Browser, type Page } from 'playwright';
export interface PDFOptions {
format?: string;
landscape?: boolean;
margin?: { top: number; right: number; bottom: number; left: number };
printBackground?: boolean;
headerTemplate?: string;
footerTemplate?: string;
}
export class PDFConverterService {
private browser: Browser | null = null;
async initialize(): Promise<void> {
if (this.browser) return;
const launchOptions: Record<string, unknown> = {
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
};
if (process.env.PLAYWRIGHT_CHROMIUM_PATH) {
launchOptions.executablePath = process.env.PLAYWRIGHT_CHROMIUM_PATH;
}
this.browser = await chromium.launch(launchOptions);
}
async close(): Promise<void> {
if (this.browser) {
await this.browser.close();
this.browser = null;
}
}
async convert(html: string, options: PDFOptions = {}): Promise<Buffer> {
await this.initialize();
if (!this.browser) throw new Error('PDF converter not initialized');
const page: Page = await this.browser.newPage();
try {
await page.setContent(html, { waitUntil: 'networkidle' });
await page.waitForTimeout(300);
const pdf = await page.pdf({
format: options.format as any || 'A4',
landscape: options.landscape || false,
printBackground: options.printBackground !== false,
margin: options.margin
? {
top: `${options.margin.top / 1440}in`,
right: `${options.margin.right / 1440}in`,
bottom: `${options.margin.bottom / 1440}in`,
left: `${options.margin.left / 1440}in`,
}
: undefined,
displayHeaderFooter: !!(options.headerTemplate || options.footerTemplate),
headerTemplate: options.headerTemplate || '<div></div>',
footerTemplate: options.footerTemplate || '<div></div>',
});
return pdf;
} finally {
await page.close();
}
}
}