n8n-nodes-contract-lite 3.0.2 → 4.0.1
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 +16 -40
- package/dist/DocumentEngine.d.ts +1 -0
- package/dist/DocumentEngine.js +111 -88
- package/package.json +2 -2
|
@@ -7,10 +7,10 @@ class ContractGenerator {
|
|
|
7
7
|
this.description = {
|
|
8
8
|
displayName: 'Document Generator Pro (HTML)',
|
|
9
9
|
name: 'documentGeneratorProHtml',
|
|
10
|
-
icon: 'fa:file-
|
|
10
|
+
icon: 'fa:file-invoice',
|
|
11
11
|
group: ['transform'],
|
|
12
12
|
version: 1,
|
|
13
|
-
description: 'Генерация
|
|
13
|
+
description: 'Генерация PDF через профессиональные HTML-шаблоны',
|
|
14
14
|
defaults: { name: 'Генератор документов' },
|
|
15
15
|
inputs: ['main'],
|
|
16
16
|
outputs: ['main'],
|
|
@@ -27,43 +27,23 @@ 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 (
|
|
34
|
+
{ name: 'Modern (Светлый, современный)', value: 'modern' },
|
|
35
|
+
{ name: 'Corporate (Строгий, ГОСТ-style)', value: 'corporate' },
|
|
36
|
+
{ name: 'Minimal (Минимализм)', value: 'minimal' },
|
|
37
37
|
],
|
|
38
38
|
default: 'modern',
|
|
39
39
|
},
|
|
40
|
-
{ displayName: 'Цвет бренда', name: 'headerColor', type: 'color', default: '#
|
|
41
|
-
|
|
42
|
-
{ displayName: '
|
|
43
|
-
{ displayName: '
|
|
44
|
-
|
|
40
|
+
{ displayName: 'Цвет бренда', name: 'headerColor', type: 'color', default: '#3b82f6' },
|
|
41
|
+
{ displayName: 'Название компании', name: 'companyName', type: 'string', default: 'ООО Моя Компания' },
|
|
42
|
+
{ displayName: 'ФИО Клиента', name: 'clientName', type: 'string', default: 'Петров Петр Петрович' },
|
|
43
|
+
{ displayName: 'Текст документа (HTML)', name: 'content', type: 'string', typeOptions: { rows: 4 }, default: 'Настоящим подтверждаем оказание услуг в полном объеме.' },
|
|
44
|
+
{ displayName: 'Табличные данные (JSON)', name: 'details', type: 'json', default: '[{"Наименование": "Консалтинг", "Цена": "5000"}]' },
|
|
45
45
|
{ displayName: 'Показать штамп', name: 'showStamp', type: 'boolean', default: true },
|
|
46
|
-
{
|
|
47
|
-
displayName: 'Текст штампа',
|
|
48
|
-
name: 'status',
|
|
49
|
-
type: 'string',
|
|
50
|
-
default: 'ОПЛАЧЕНО',
|
|
51
|
-
displayOptions: { show: { showStamp: [true] } }
|
|
52
|
-
},
|
|
53
|
-
// Контент
|
|
54
|
-
{
|
|
55
|
-
displayName: 'Текст документа (HTML)',
|
|
56
|
-
name: 'content',
|
|
57
|
-
type: 'string',
|
|
58
|
-
typeOptions: { rows: 5 },
|
|
59
|
-
default: 'Настоящим подтверждаем выполнение обязательств по договору...',
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
displayName: 'Данные таблицы (JSON)',
|
|
63
|
-
name: 'details',
|
|
64
|
-
type: 'json',
|
|
65
|
-
default: '[{"Услуга": "Разработка ПО", "Кол-во": 1, "Сумма": "50000"}]',
|
|
66
|
-
},
|
|
46
|
+
{ displayName: 'Текст на штампе', name: 'status', type: 'string', default: 'ОПЛАЧЕНО', displayOptions: { show: { showStamp: [true] } } },
|
|
67
47
|
],
|
|
68
48
|
};
|
|
69
49
|
}
|
|
@@ -72,28 +52,24 @@ class ContractGenerator {
|
|
|
72
52
|
const returnData = [];
|
|
73
53
|
for (let i = 0; i < items.length; i++) {
|
|
74
54
|
const templateType = this.getNodeParameter('templateType', i);
|
|
75
|
-
const presetTheme = this.getNodeParameter('presetTheme', i);
|
|
76
55
|
const titles = { invoice: 'СЧЕТ НА ОПЛАТУ', contract: 'ДОГОВОР ПОСТАВКИ', act: 'АКТ ВЫПОЛНЕННЫХ РАБОТ' };
|
|
77
56
|
const settings = {
|
|
78
|
-
presetTheme,
|
|
57
|
+
presetTheme: this.getNodeParameter('presetTheme', i),
|
|
79
58
|
headerColor: this.getNodeParameter('headerColor', i),
|
|
80
59
|
};
|
|
81
60
|
const data = {
|
|
82
61
|
title: titles[templateType] || 'ДОКУМЕНТ',
|
|
83
62
|
companyName: this.getNodeParameter('companyName', i),
|
|
84
63
|
clientName: this.getNodeParameter('clientName', i),
|
|
64
|
+
content: this.getNodeParameter('content', i),
|
|
65
|
+
details: this.getNodeParameter('details', i),
|
|
85
66
|
showStamp: this.getNodeParameter('showStamp', i),
|
|
86
67
|
status: this.getNodeParameter('status', i, ''),
|
|
87
|
-
content: this.getNodeParameter('content', i, ''),
|
|
88
|
-
details: this.getNodeParameter('details', i, '[]'),
|
|
89
68
|
};
|
|
90
69
|
try {
|
|
91
70
|
const buffer = await DocumentEngine_1.DocumentEngine.createPDF(data, settings);
|
|
92
71
|
const binaryData = await this.helpers.prepareBinaryData(buffer, `${templateType}.pdf`, 'application/pdf');
|
|
93
|
-
returnData.push({
|
|
94
|
-
json: { success: true, theme: presetTheme },
|
|
95
|
-
binary: { data: binaryData }
|
|
96
|
-
});
|
|
72
|
+
returnData.push({ json: { success: true }, binary: { data: binaryData } });
|
|
97
73
|
}
|
|
98
74
|
catch (error) {
|
|
99
75
|
returnData.push({ json: { success: false, error: error.message } });
|
package/dist/DocumentEngine.d.ts
CHANGED
package/dist/DocumentEngine.js
CHANGED
|
@@ -38,57 +38,74 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.DocumentEngine = void 0;
|
|
40
40
|
const handlebars = __importStar(require("handlebars"));
|
|
41
|
-
const
|
|
41
|
+
const puppeteer = __importStar(require("puppeteer-core"));
|
|
42
42
|
const crypto_1 = __importDefault(require("crypto"));
|
|
43
|
+
const fs_1 = __importDefault(require("fs"));
|
|
43
44
|
class DocumentEngine {
|
|
44
|
-
//
|
|
45
|
+
// Метод для поиска пути к браузеру
|
|
46
|
+
static getExecutablePath() {
|
|
47
|
+
if (process.env.CHROME_BIN)
|
|
48
|
+
return process.env.CHROME_BIN;
|
|
49
|
+
const paths = [];
|
|
50
|
+
if (process.platform === 'win32') {
|
|
51
|
+
paths.push('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe');
|
|
52
|
+
}
|
|
53
|
+
else if (process.platform === 'darwin') {
|
|
54
|
+
paths.push('/Applications/Google Chrome.app/Contents/MacOS/Google Chrome');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
paths.push('/usr/bin/chromium-browser', '/usr/bin/chromium', '/usr/bin/google-chrome');
|
|
58
|
+
}
|
|
59
|
+
for (const path of paths) {
|
|
60
|
+
if (fs_1.default.existsSync(path))
|
|
61
|
+
return path;
|
|
62
|
+
}
|
|
63
|
+
throw new Error('Браузер не найден. Установите Chromium или укажите путь в переменной CHROME_BIN. ' +
|
|
64
|
+
'Если вы в Docker, добавьте установку chromium в Dockerfile.');
|
|
65
|
+
}
|
|
45
66
|
static getStyles(theme, accentColor) {
|
|
46
67
|
const baseStyles = `
|
|
47
68
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
|
|
48
|
-
body { font-family: 'Inter', sans-serif; margin: 0; padding:
|
|
69
|
+
body { font-family: 'Inter', sans-serif; margin: 0; padding: 0; color: #2d3436; }
|
|
70
|
+
.page { padding: 40px; position: relative; min-height: 100vh; box-sizing: border-box; }
|
|
49
71
|
.header { display: flex; justify-content: space-between; margin-bottom: 50px; }
|
|
50
72
|
.stamp {
|
|
51
|
-
position: absolute; right:
|
|
73
|
+
position: absolute; right: 50px; bottom: 150px;
|
|
52
74
|
border: 4px double ${accentColor}; color: ${accentColor};
|
|
53
|
-
padding:
|
|
54
|
-
font-weight: bold; font-size: 24px; opacity: 0.
|
|
75
|
+
padding: 10px 20px; transform: rotate(-15deg);
|
|
76
|
+
font-weight: bold; font-size: 24px; opacity: 0.6;
|
|
55
77
|
text-transform: uppercase; z-index: 100;
|
|
56
|
-
background: rgba(255,255,255,0.8);
|
|
57
78
|
}
|
|
58
|
-
table { width: 100%; border-collapse: collapse; margin:
|
|
59
|
-
th { text-align: left; padding: 12px; background: #f8f9fa; border-bottom: 2px solid ${accentColor}; }
|
|
60
|
-
td { padding: 12px; border-bottom: 1px solid #eee; }
|
|
61
|
-
.footer { position:
|
|
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; }
|
|
62
83
|
`;
|
|
63
84
|
const themes = {
|
|
64
85
|
modern: `
|
|
65
86
|
${baseStyles}
|
|
66
|
-
.company-name { font-size:
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
.card { padding: 20px; background: #fcfcfc; border-left: 5px solid ${accentColor}; }
|
|
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; }
|
|
70
90
|
`,
|
|
71
91
|
corporate: `
|
|
72
92
|
${baseStyles}
|
|
73
|
-
|
|
74
|
-
.header { flex-direction: column;
|
|
75
|
-
.
|
|
76
|
-
.card { margin: 20px 0; border: 1px solid #ddd; padding: 15px; }
|
|
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; }
|
|
77
96
|
`,
|
|
78
97
|
minimal: `
|
|
79
98
|
${baseStyles}
|
|
80
|
-
.company-name { font-size: 20px;
|
|
81
|
-
|
|
82
|
-
|
|
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; }
|
|
83
102
|
`
|
|
84
103
|
};
|
|
85
104
|
return themes[theme] || themes.modern;
|
|
86
105
|
}
|
|
87
|
-
// 2. Главный метод генерации
|
|
88
106
|
static async createPDF(data, settings) {
|
|
89
107
|
const theme = settings.presetTheme || 'modern';
|
|
90
108
|
const accent = settings.headerColor || '#1a2b3c';
|
|
91
|
-
const docId = crypto_1.default.randomBytes(4).toString('hex').toUpperCase();
|
|
92
109
|
const htmlLayout = `
|
|
93
110
|
<!DOCTYPE html>
|
|
94
111
|
<html>
|
|
@@ -96,67 +113,72 @@ class DocumentEngine {
|
|
|
96
113
|
<style>${this.getStyles(theme, accent)}</style>
|
|
97
114
|
</head>
|
|
98
115
|
<body>
|
|
99
|
-
<div class="
|
|
100
|
-
<div>
|
|
101
|
-
<div
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<div style="
|
|
106
|
-
|
|
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>
|
|
107
126
|
</div>
|
|
108
|
-
</div>
|
|
109
127
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
128
|
+
{{#if showStamp}}
|
|
129
|
+
<div class="stamp">{{status}}</div>
|
|
130
|
+
{{/if}}
|
|
113
131
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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>
|
|
122
141
|
</div>
|
|
123
|
-
</div>
|
|
124
142
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
{{#if details.length}}
|
|
129
|
-
<table>
|
|
130
|
-
<thead>
|
|
131
|
-
<tr>
|
|
132
|
-
{{#each tableHeaders}}<th>{{this}}</th>{{/each}}
|
|
133
|
-
</tr>
|
|
134
|
-
</thead>
|
|
135
|
-
<tbody>
|
|
136
|
-
{{#each details}}
|
|
137
|
-
<tr>
|
|
138
|
-
{{#each this}}<td>{{this}}</td>{{/each}}
|
|
139
|
-
</tr>
|
|
140
|
-
{{/each}}
|
|
141
|
-
</tbody>
|
|
142
|
-
</table>
|
|
143
|
-
{{/if}}
|
|
144
|
-
</div>
|
|
143
|
+
<div style="margin-top: 30px; min-height: 200px;">
|
|
144
|
+
{{{content}}}
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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>
|
|
150
172
|
|
|
151
|
-
|
|
152
|
-
|
|
173
|
+
<div class="footer">
|
|
174
|
+
Документ ID: {{docId}} | Сформировано автоматически | Страница 1 из 1
|
|
175
|
+
</div>
|
|
153
176
|
</div>
|
|
154
177
|
</body>
|
|
155
178
|
</html>
|
|
156
179
|
`;
|
|
157
|
-
const secureHash = crypto_1.default.createHash('sha256').update(docId).digest('hex').substring(0, 16);
|
|
158
180
|
const template = handlebars.compile(htmlLayout);
|
|
159
|
-
|
|
181
|
+
const docId = crypto_1.default.randomBytes(3).toString('hex').toUpperCase();
|
|
160
182
|
let details = [];
|
|
161
183
|
try {
|
|
162
184
|
details = typeof data.details === 'string' ? JSON.parse(data.details) : data.details;
|
|
@@ -167,26 +189,27 @@ class DocumentEngine {
|
|
|
167
189
|
const finalHtml = template({
|
|
168
190
|
...data,
|
|
169
191
|
docId,
|
|
170
|
-
secureHash,
|
|
171
192
|
date: new Date().toLocaleDateString('ru-RU'),
|
|
172
193
|
details: Array.isArray(details) ? details.map(Object.values) : [],
|
|
173
194
|
tableHeaders: Array.isArray(details) && details.length > 0 ? Object.keys(details[0]) : []
|
|
174
195
|
});
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
args: ['--no-sandbox', '--disable-setuid-sandbox'],
|
|
178
|
-
|
|
179
|
-
executablePath: process.env.CHROME_BIN || undefined
|
|
180
|
-
});
|
|
181
|
-
const page = await browser.newPage();
|
|
182
|
-
await page.setContent(finalHtml, { waitUntil: 'networkidle0' });
|
|
183
|
-
const buffer = await page.pdf({
|
|
184
|
-
format: 'A4',
|
|
185
|
-
printBackground: true,
|
|
186
|
-
margin: { top: '0px', right: '0px', bottom: '0px', left: '0px' }
|
|
196
|
+
const browser = await puppeteer.launch({
|
|
197
|
+
executablePath: this.getExecutablePath(),
|
|
198
|
+
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
|
|
199
|
+
headless: true
|
|
187
200
|
});
|
|
188
|
-
|
|
189
|
-
|
|
201
|
+
try {
|
|
202
|
+
const page = await browser.newPage();
|
|
203
|
+
await page.setContent(finalHtml, { waitUntil: 'networkidle0' });
|
|
204
|
+
const buffer = await page.pdf({
|
|
205
|
+
format: 'A4',
|
|
206
|
+
printBackground: true,
|
|
207
|
+
});
|
|
208
|
+
return Buffer.from(buffer);
|
|
209
|
+
}
|
|
210
|
+
finally {
|
|
211
|
+
await browser.close();
|
|
212
|
+
}
|
|
190
213
|
}
|
|
191
214
|
}
|
|
192
215
|
exports.DocumentEngine = DocumentEngine;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-contract-lite",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"n8n": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"handlebars": "^4.7.8",
|
|
29
29
|
"mustache": "^4.2.0",
|
|
30
30
|
"pdfkit": "^0.13.0",
|
|
31
|
-
"puppeteer": "^24.35.0"
|
|
31
|
+
"puppeteer-core": "^24.35.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^20.19.30",
|