n8n-nodes-contract-lite 4.0.1 → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ContractGenerator.node.js +17 -18
- package/dist/DocumentEngine.d.ts +6 -2
- package/dist/DocumentEngine.js +112 -197
- package/package.json +1 -1
|
@@ -5,12 +5,12 @@ const DocumentEngine_1 = require("./DocumentEngine");
|
|
|
5
5
|
class ContractGenerator {
|
|
6
6
|
constructor() {
|
|
7
7
|
this.description = {
|
|
8
|
-
displayName: 'Document Generator
|
|
9
|
-
name: '
|
|
10
|
-
icon: 'fa:file-
|
|
8
|
+
displayName: 'Document Generator Lite (RU)',
|
|
9
|
+
name: 'documentGeneratorLite',
|
|
10
|
+
icon: 'fa:file-pdf',
|
|
11
11
|
group: ['transform'],
|
|
12
12
|
version: 1,
|
|
13
|
-
description: '
|
|
13
|
+
description: 'Быстрая генерация PDF без зависимостей браузера',
|
|
14
14
|
defaults: { name: 'Генератор документов' },
|
|
15
15
|
inputs: ['main'],
|
|
16
16
|
outputs: ['main'],
|
|
@@ -27,23 +27,22 @@ class ContractGenerator {
|
|
|
27
27
|
default: 'invoice',
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
|
-
displayName: '
|
|
30
|
+
displayName: 'Стиль дизайна',
|
|
31
31
|
name: 'presetTheme',
|
|
32
32
|
type: 'options',
|
|
33
33
|
options: [
|
|
34
|
-
{ name: 'Modern (
|
|
35
|
-
{ name: 'Corporate (
|
|
36
|
-
{ name: 'Minimal (Минимализм)', value: 'minimal' },
|
|
34
|
+
{ name: 'Modern (Геометрия)', value: 'modern' },
|
|
35
|
+
{ name: 'Corporate (Строгий)', value: 'corporate' },
|
|
37
36
|
],
|
|
38
37
|
default: 'modern',
|
|
39
38
|
},
|
|
40
|
-
{ displayName: 'Цвет бренда', name: 'headerColor', type: 'color', default: '#
|
|
41
|
-
{ displayName: '
|
|
42
|
-
{ displayName: '
|
|
43
|
-
{ displayName: 'Текст
|
|
44
|
-
{ displayName: '
|
|
39
|
+
{ displayName: 'Цвет бренда', name: 'headerColor', type: 'color', default: '#2563eb' },
|
|
40
|
+
{ displayName: 'Компания', name: 'companyName', type: 'string', default: 'ООО Вектор' },
|
|
41
|
+
{ displayName: 'Клиент', name: 'clientName', type: 'string', default: 'ИП Алексеев' },
|
|
42
|
+
{ displayName: 'Текст', name: 'content', type: 'string', typeOptions: { rows: 4 }, default: 'Настоящий документ подтверждает факт оказания услуг.' },
|
|
43
|
+
{ displayName: 'Данные (JSON)', name: 'details', type: 'json', default: '[{"Услуга": "Консалтинг", "Цена": "15000"}]' },
|
|
45
44
|
{ displayName: 'Показать штамп', name: 'showStamp', type: 'boolean', default: true },
|
|
46
|
-
{ displayName: 'Текст
|
|
45
|
+
{ displayName: 'Текст штампа', name: 'status', type: 'string', default: 'ОПЛАЧЕНО', displayOptions: { show: { showStamp: [true] } } },
|
|
47
46
|
],
|
|
48
47
|
};
|
|
49
48
|
}
|
|
@@ -53,10 +52,6 @@ class ContractGenerator {
|
|
|
53
52
|
for (let i = 0; i < items.length; i++) {
|
|
54
53
|
const templateType = this.getNodeParameter('templateType', i);
|
|
55
54
|
const titles = { invoice: 'СЧЕТ НА ОПЛАТУ', contract: 'ДОГОВОР ПОСТАВКИ', act: 'АКТ ВЫПОЛНЕННЫХ РАБОТ' };
|
|
56
|
-
const settings = {
|
|
57
|
-
presetTheme: this.getNodeParameter('presetTheme', i),
|
|
58
|
-
headerColor: this.getNodeParameter('headerColor', i),
|
|
59
|
-
};
|
|
60
55
|
const data = {
|
|
61
56
|
title: titles[templateType] || 'ДОКУМЕНТ',
|
|
62
57
|
companyName: this.getNodeParameter('companyName', i),
|
|
@@ -66,6 +61,10 @@ class ContractGenerator {
|
|
|
66
61
|
showStamp: this.getNodeParameter('showStamp', i),
|
|
67
62
|
status: this.getNodeParameter('status', i, ''),
|
|
68
63
|
};
|
|
64
|
+
const settings = {
|
|
65
|
+
presetTheme: this.getNodeParameter('presetTheme', i),
|
|
66
|
+
headerColor: this.getNodeParameter('headerColor', i),
|
|
67
|
+
};
|
|
69
68
|
try {
|
|
70
69
|
const buffer = await DocumentEngine_1.DocumentEngine.createPDF(data, settings);
|
|
71
70
|
const binaryData = await this.helpers.prepareBinaryData(buffer, `${templateType}.pdf`, 'application/pdf');
|
package/dist/DocumentEngine.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export declare class DocumentEngine {
|
|
2
|
-
private static getExecutablePath;
|
|
3
|
-
private static getStyles;
|
|
4
2
|
static createPDF(data: any, settings: any): Promise<Buffer>;
|
|
3
|
+
private static drawBackground;
|
|
4
|
+
private static drawHeader;
|
|
5
|
+
private static drawInfoCards;
|
|
6
|
+
private static drawTable;
|
|
7
|
+
private static drawStamp;
|
|
8
|
+
private static drawFooter;
|
|
5
9
|
}
|
package/dist/DocumentEngine.js
CHANGED
|
@@ -1,215 +1,130 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
38
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
6
|
exports.DocumentEngine = void 0;
|
|
40
|
-
const
|
|
41
|
-
const puppeteer = __importStar(require("puppeteer-core"));
|
|
7
|
+
const pdfkit_1 = __importDefault(require("pdfkit"));
|
|
42
8
|
const crypto_1 = __importDefault(require("crypto"));
|
|
43
|
-
const fs_1 = __importDefault(require("fs"));
|
|
44
9
|
class DocumentEngine {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
10
|
+
static async createPDF(data, settings) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
// Создаем документ (A4)
|
|
13
|
+
const doc = new pdfkit_1.default({
|
|
14
|
+
size: 'A4',
|
|
15
|
+
margin: 0, // Мы сами будем управлять отступами
|
|
16
|
+
bufferPages: true
|
|
17
|
+
});
|
|
18
|
+
const chunks = [];
|
|
19
|
+
doc.on('data', chunk => chunks.push(chunk));
|
|
20
|
+
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
|
21
|
+
doc.on('error', reject);
|
|
22
|
+
const accent = settings.headerColor || '#3b82f6';
|
|
23
|
+
const theme = settings.presetTheme || 'modern';
|
|
24
|
+
// 1. ФОН И ДЕКОР
|
|
25
|
+
this.drawBackground(doc, accent, theme);
|
|
26
|
+
// 2. ШАПКА
|
|
27
|
+
this.drawHeader(doc, data, accent);
|
|
28
|
+
// 3. КАРТОЧКИ С ДАННЫМИ (Отправитель / Получатель)
|
|
29
|
+
this.drawInfoCards(doc, data, accent);
|
|
30
|
+
// 4. ТЕКСТОВЫЙ КОНТЕНТ
|
|
31
|
+
doc.fillColor('#333333').fontSize(11);
|
|
32
|
+
doc.text(data.content, 50, 280, { width: 500, align: 'left', lineGap: 4 });
|
|
33
|
+
// 5. ТАБЛИЦА
|
|
34
|
+
let details = [];
|
|
35
|
+
try {
|
|
36
|
+
details = typeof data.details === 'string' ? JSON.parse(data.details) : data.details;
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
details = [];
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(details) && details.length > 0) {
|
|
42
|
+
this.drawTable(doc, details, accent);
|
|
43
|
+
}
|
|
44
|
+
// 6. ШТАМП (Если нужен)
|
|
45
|
+
if (data.showStamp) {
|
|
46
|
+
this.drawStamp(doc, data.status || 'ОПЛАЧЕНО', accent);
|
|
47
|
+
}
|
|
48
|
+
// 7. ФУТЕР И ПОДПИСИ
|
|
49
|
+
this.drawFooter(doc, data, accent);
|
|
50
|
+
doc.end();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
static drawBackground(doc, accent, theme) {
|
|
54
|
+
if (theme === 'modern') {
|
|
55
|
+
// Дизайнерская полоса сверху
|
|
56
|
+
doc.rect(0, 0, 595, 10).fill(accent);
|
|
57
|
+
// Светло-серый блок внизу для футера
|
|
58
|
+
doc.rect(0, 780, 595, 62).fill('#f8fafc');
|
|
59
|
+
// Декоративный треугольник в углу
|
|
60
|
+
doc.path('M 595 0 L 595 100 L 495 0 Z').fillOpacity(0.1).fill(accent).fillOpacity(1);
|
|
58
61
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return path;
|
|
62
|
+
else if (theme === 'corporate') {
|
|
63
|
+
doc.rect(0, 0, 20, 842).fill(accent); // Полоса слева
|
|
62
64
|
}
|
|
63
|
-
throw new Error('Браузер не найден. Установите Chromium или укажите путь в переменной CHROME_BIN. ' +
|
|
64
|
-
'Если вы в Docker, добавьте установку chromium в Dockerfile.');
|
|
65
65
|
}
|
|
66
|
-
static
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
|
|
80
|
-
th { text-align: left; padding: 12px; background: #f8f9fa; border-bottom: 2px solid ${accentColor}; font-size: 12px; }
|
|
81
|
-
td { padding: 12px; border-bottom: 1px solid #eee; font-size: 12px; }
|
|
82
|
-
.footer { position: absolute; bottom: 20px; left: 40px; right: 40px; font-size: 9px; color: #999; border-top: 1px solid #eee; padding-top: 10px; }
|
|
83
|
-
`;
|
|
84
|
-
const themes = {
|
|
85
|
-
modern: `
|
|
86
|
-
${baseStyles}
|
|
87
|
-
.company-name { font-size: 24px; font-weight: 800; color: ${accentColor}; }
|
|
88
|
-
.card { padding: 15px; background: #f8fafc; border-radius: 8px; border: 1px solid #e2e8f0; }
|
|
89
|
-
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
|
90
|
-
`,
|
|
91
|
-
corporate: `
|
|
92
|
-
${baseStyles}
|
|
93
|
-
.company-name { font-size: 28px; font-weight: bold; text-align: center; width: 100%; text-decoration: underline; }
|
|
94
|
-
.header { flex-direction: column; align-items: center; border-bottom: 2px solid #000; padding-bottom: 10px; }
|
|
95
|
-
.card { margin-top: 20px; border: 1px solid #000; padding: 10px; }
|
|
96
|
-
`,
|
|
97
|
-
minimal: `
|
|
98
|
-
${baseStyles}
|
|
99
|
-
.company-name { font-size: 20px; border-bottom: 1px solid #eee; padding-bottom: 10px; }
|
|
100
|
-
.header { border-bottom: 1px solid #eee; }
|
|
101
|
-
th { background: none; text-transform: uppercase; font-size: 10px; color: #666; }
|
|
102
|
-
`
|
|
66
|
+
static drawHeader(doc, data, accent) {
|
|
67
|
+
const docId = crypto_1.default.randomBytes(3).toString('hex').toUpperCase();
|
|
68
|
+
doc.fillColor(accent).fontSize(22).font('Helvetica-Bold').text(data.companyName.toUpperCase(), 50, 40);
|
|
69
|
+
doc.fillColor('#64748b').fontSize(10).font('Helvetica').text(data.title, 50, 68);
|
|
70
|
+
// Инфо блок справа
|
|
71
|
+
doc.fillColor('#1e293b').fontSize(12).font('Helvetica-Bold').text(`№ ${docId}`, 400, 45, { align: 'right', width: 145 });
|
|
72
|
+
doc.fillColor('#64748b').fontSize(9).font('Helvetica').text(new Date().toLocaleDateString('ru-RU'), 400, 60, { align: 'right', width: 145 });
|
|
73
|
+
}
|
|
74
|
+
static drawInfoCards(doc, data, accent) {
|
|
75
|
+
const drawCard = (x, y, label, value) => {
|
|
76
|
+
doc.roundedRect(x, y, 240, 55, 8).fill('#f1f5f9');
|
|
77
|
+
doc.fillColor(accent).fontSize(7).font('Helvetica-Bold').text(label, x + 12, y + 12);
|
|
78
|
+
doc.fillColor('#1e293b').fontSize(10).font('Helvetica').text(value, x + 12, y + 25, { width: 216 });
|
|
103
79
|
};
|
|
104
|
-
|
|
80
|
+
drawCard(50, 100, 'ОТПРАВИТЕЛЬ', data.companyName);
|
|
81
|
+
drawCard(305, 100, 'ПОЛУЧАТЕЛЬ', data.clientName);
|
|
105
82
|
}
|
|
106
|
-
static
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<body>
|
|
116
|
-
<div class="page">
|
|
117
|
-
<div class="header">
|
|
118
|
-
<div>
|
|
119
|
-
<div class="company-name">{{companyName}}</div>
|
|
120
|
-
<div style="margin-top: 5px; color: #666;">{{title}}</div>
|
|
121
|
-
</div>
|
|
122
|
-
<div style="text-align: right">
|
|
123
|
-
<div style="font-weight: bold">№ {{docId}}</div>
|
|
124
|
-
<div>{{date}}</div>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
|
|
128
|
-
{{#if showStamp}}
|
|
129
|
-
<div class="stamp">{{status}}</div>
|
|
130
|
-
{{/if}}
|
|
131
|
-
|
|
132
|
-
<div class="grid">
|
|
133
|
-
<div class="card">
|
|
134
|
-
<div style="font-size: 10px; color: ${accent}; font-weight: bold;">ОТПРАВИТЕЛЬ</div>
|
|
135
|
-
<div style="font-weight: bold;">{{companyName}}</div>
|
|
136
|
-
</div>
|
|
137
|
-
<div class="card">
|
|
138
|
-
<div style="font-size: 10px; color: ${accent}; font-weight: bold;">ПОЛУЧАТЕЛЬ</div>
|
|
139
|
-
<div style="font-weight: bold;">{{clientName}}</div>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
|
|
143
|
-
<div style="margin-top: 30px; min-height: 200px;">
|
|
144
|
-
{{{content}}}
|
|
145
|
-
|
|
146
|
-
{{#if details.length}}
|
|
147
|
-
<table>
|
|
148
|
-
<thead>
|
|
149
|
-
<tr>
|
|
150
|
-
{{#each tableHeaders}}<th>{{this}}</th>{{/each}}
|
|
151
|
-
</tr>
|
|
152
|
-
</thead>
|
|
153
|
-
<tbody>
|
|
154
|
-
{{#each details}}
|
|
155
|
-
<tr>
|
|
156
|
-
{{#each this}}<td>{{this}}</td>{{/each}}
|
|
157
|
-
</tr>
|
|
158
|
-
{{/each}}
|
|
159
|
-
</tbody>
|
|
160
|
-
</table>
|
|
161
|
-
{{/if}}
|
|
162
|
-
</div>
|
|
163
|
-
|
|
164
|
-
<div style="margin-top: 50px; display: flex; justify-content: space-between;">
|
|
165
|
-
<div style="width: 200px; border-top: 1px solid #ddd; padding-top: 10px; font-size: 12px;">
|
|
166
|
-
М.П. / Подпись представителя
|
|
167
|
-
</div>
|
|
168
|
-
<div style="width: 200px; border-top: 1px solid #ddd; padding-top: 10px; font-size: 12px;">
|
|
169
|
-
Подпись клиента
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
|
|
173
|
-
<div class="footer">
|
|
174
|
-
Документ ID: {{docId}} | Сформировано автоматически | Страница 1 из 1
|
|
175
|
-
</div>
|
|
176
|
-
</div>
|
|
177
|
-
</body>
|
|
178
|
-
</html>
|
|
179
|
-
`;
|
|
180
|
-
const template = handlebars.compile(htmlLayout);
|
|
181
|
-
const docId = crypto_1.default.randomBytes(3).toString('hex').toUpperCase();
|
|
182
|
-
let details = [];
|
|
183
|
-
try {
|
|
184
|
-
details = typeof data.details === 'string' ? JSON.parse(data.details) : data.details;
|
|
185
|
-
}
|
|
186
|
-
catch (e) {
|
|
187
|
-
details = [];
|
|
188
|
-
}
|
|
189
|
-
const finalHtml = template({
|
|
190
|
-
...data,
|
|
191
|
-
docId,
|
|
192
|
-
date: new Date().toLocaleDateString('ru-RU'),
|
|
193
|
-
details: Array.isArray(details) ? details.map(Object.values) : [],
|
|
194
|
-
tableHeaders: Array.isArray(details) && details.length > 0 ? Object.keys(details[0]) : []
|
|
195
|
-
});
|
|
196
|
-
const browser = await puppeteer.launch({
|
|
197
|
-
executablePath: this.getExecutablePath(),
|
|
198
|
-
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
|
|
199
|
-
headless: true
|
|
83
|
+
static drawTable(doc, rows, accent) {
|
|
84
|
+
let y = doc.y + 30;
|
|
85
|
+
const colWidth = 120;
|
|
86
|
+
const headers = Object.keys(rows[0]);
|
|
87
|
+
// Шапка таблицы
|
|
88
|
+
doc.roundedRect(50, y, 500, 25, 4).fill(accent);
|
|
89
|
+
doc.fillColor('#ffffff').fontSize(9).font('Helvetica-Bold');
|
|
90
|
+
headers.forEach((h, i) => {
|
|
91
|
+
doc.text(h.toUpperCase(), 60 + (i * colWidth), y + 8, { width: colWidth, ellipsize: true });
|
|
200
92
|
});
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
93
|
+
// Строки
|
|
94
|
+
y += 30;
|
|
95
|
+
doc.fillColor('#334155').font('Helvetica').fontSize(9);
|
|
96
|
+
rows.forEach((row, rowIndex) => {
|
|
97
|
+
if (y > 700) {
|
|
98
|
+
doc.addPage();
|
|
99
|
+
y = 50;
|
|
100
|
+
} // Перенос страницы
|
|
101
|
+
const values = Object.values(row);
|
|
102
|
+
values.forEach((v, i) => {
|
|
103
|
+
doc.text(String(v), 60 + (i * colWidth), y);
|
|
207
104
|
});
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
105
|
+
y += 22;
|
|
106
|
+
doc.moveTo(50, y - 5).lineTo(550, y - 5).strokeColor('#e2e8f0').lineWidth(0.5).stroke();
|
|
107
|
+
});
|
|
108
|
+
doc.y = y;
|
|
109
|
+
}
|
|
110
|
+
static drawStamp(doc, text, accent) {
|
|
111
|
+
doc.save();
|
|
112
|
+
doc.rotate(-15, { origin: [450, 650] });
|
|
113
|
+
doc.rect(400, 630, 130, 40).lineWidth(2).strokeColor(accent).strokeOpacity(0.6).stroke();
|
|
114
|
+
doc.fillColor(accent).fillOpacity(0.6).fontSize(14).font('Helvetica-Bold')
|
|
115
|
+
.text(text.toUpperCase(), 400, 643, { width: 130, align: 'center' });
|
|
116
|
+
doc.restore();
|
|
117
|
+
}
|
|
118
|
+
static drawFooter(doc, data, accent) {
|
|
119
|
+
const y = 730;
|
|
120
|
+
// Линии подписей
|
|
121
|
+
doc.moveTo(50, y).lineTo(200, y).strokeColor('#cbd5e1').stroke();
|
|
122
|
+
doc.moveTo(345, y).lineTo(495, y).stroke();
|
|
123
|
+
doc.fillColor('#94a3b8').fontSize(8).font('Helvetica');
|
|
124
|
+
doc.text('Подпись отправителя', 50, y + 8);
|
|
125
|
+
doc.text('Подпись получателя', 345, y + 8);
|
|
126
|
+
doc.fillColor('#64748b').fontSize(7).text(`Hash-верификация: ${crypto_1.default.randomBytes(8).toString('hex')}`, 50, 800);
|
|
127
|
+
doc.text('Документ сформирован автоматически в системе n8n', 50, 812);
|
|
213
128
|
}
|
|
214
129
|
}
|
|
215
130
|
exports.DocumentEngine = DocumentEngine;
|