Files
iistwin/server/documents/external-pdf.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

129 lines
4.2 KiB
TypeScript

export interface PDFOptions {
format?: string;
landscape?: boolean;
margin?: { top: number; right: number; bottom: number; left: number };
printBackground?: boolean;
headerTemplate?: string;
footerTemplate?: string;
}
const DOC_WORKER_URL = (process.env.DOC_WORKER_URL || "http://document-worker:3000").replace(/\/+$/, "");
function getTimeout(): number {
const env = process.env.DOC_WORKER_TIMEOUT_MS;
return env ? parseInt(env, 10) : 30000;
}
/**
* Конвертирует HTML в PDF через внешний document-worker сервис.
* Возвращает Buffer или null при ошибке.
*/
export async function convertHtmlToPdf(html: string, options: PDFOptions = {}): Promise<Buffer | null> {
try {
const response = await fetch(`${DOC_WORKER_URL}/convert/pdf`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ html, options }),
signal: AbortSignal.timeout(getTimeout()),
});
if (!response.ok) {
const text = await response.text().catch(() => "");
console.error("[doc-worker] PDF conversion HTTP error:", response.status, text);
return null;
}
const data = await response.json();
if (!data.success || !data.pdf) {
console.error("[doc-worker] PDF conversion failed:", data.error || "unknown");
return null;
}
return Buffer.from(data.pdf, "base64");
} catch (err) {
console.error("[doc-worker] PDF conversion request failed:", err);
return null;
}
}
/**
* Конвертирует DOCX (Buffer) в PDF через внешний document-worker сервис.
* Возвращает Buffer или null при ошибке.
*/
export async function convertDocxToPdf(docxBuffer: Buffer): Promise<Buffer | null> {
try {
const response = await fetch(`${DOC_WORKER_URL}/convert/docx-to-pdf`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ docxBase64: docxBuffer.toString("base64") }),
signal: AbortSignal.timeout(getTimeout()),
});
if (!response.ok) {
const text = await response.text().catch(() => "");
console.error("[doc-worker] DOCX→PDF conversion HTTP error:", response.status, text);
return null;
}
const data = await response.json();
if (!data.success || !data.pdf) {
console.error("[doc-worker] DOCX→PDF conversion failed:", data.error || "unknown");
return null;
}
return Buffer.from(data.pdf, "base64");
} catch (err) {
console.error("[doc-worker] DOCX→PDF conversion request failed:", err);
return null;
}
}
/**
* Конвертирует EMF/WMF (Buffer) в PNG через внешний document-worker сервис.
* Возвращает Buffer или null при ошибке.
*/
export async function convertEmfToPng(emfBuffer: Buffer): Promise<Buffer | null> {
try {
const response = await fetch(`${DOC_WORKER_URL}/convert/emf-to-png`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ emfBase64: emfBuffer.toString("base64") }),
signal: AbortSignal.timeout(getTimeout()),
});
if (!response.ok) {
const text = await response.text().catch(() => "");
console.error("[doc-worker] EMF→PNG conversion HTTP error:", response.status, text);
return null;
}
const data = await response.json();
if (!data.success || !data.png) {
console.error("[doc-worker] EMF→PNG conversion failed:", data.error || "unknown");
return null;
}
return Buffer.from(data.png, "base64");
} catch (err) {
console.error("[doc-worker] EMF→PNG conversion request failed:", err);
return null;
}
}
/**
* Проверяет доступность document-worker сервиса.
*/
export async function checkDocumentWorkerHealth(): Promise<{ ok: boolean; error?: string }> {
try {
const response = await fetch(`${DOC_WORKER_URL}/health`, {
signal: AbortSignal.timeout(5000),
});
if (!response.ok) {
return { ok: false, error: `HTTP ${response.status}` };
}
return { ok: true };
} catch (err) {
return { ok: false, error: err instanceof Error ? err.message : String(err) };
}
}