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.
@@ -5,12 +5,12 @@ const DocumentEngine_1 = require("./DocumentEngine");
5
5
  class ContractGenerator {
6
6
  constructor() {
7
7
  this.description = {
8
- displayName: 'Document Generator Pro (HTML)',
9
- name: 'documentGeneratorProHtml',
10
- icon: 'fa:file-invoice',
8
+ displayName: 'Document Generator Lite (RU)',
9
+ name: 'documentGeneratorLite',
10
+ icon: 'fa:file-pdf',
11
11
  group: ['transform'],
12
12
  version: 1,
13
- description: 'Генерация PDF через профессиональные HTML-шаблоны',
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 (Светлый, современный)', value: 'modern' },
35
- { name: 'Corporate (Строгий, ГОСТ-style)', value: '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: '#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"}]' },
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: 'Текст на штампе', name: 'status', type: 'string', default: 'ОПЛАЧЕНО', displayOptions: { show: { showStamp: [true] } } },
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');
@@ -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
  }
@@ -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 handlebars = __importStar(require("handlebars"));
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
- 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');
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
- for (const path of paths) {
60
- if (fs_1.default.existsSync(path))
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 getStyles(theme, accentColor) {
67
- const baseStyles = `
68
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
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; }
71
- .header { display: flex; justify-content: space-between; margin-bottom: 50px; }
72
- .stamp {
73
- position: absolute; right: 50px; bottom: 150px;
74
- border: 4px double ${accentColor}; color: ${accentColor};
75
- padding: 10px 20px; transform: rotate(-15deg);
76
- font-weight: bold; font-size: 24px; opacity: 0.6;
77
- text-transform: uppercase; z-index: 100;
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
- return themes[theme] || themes.modern;
80
+ drawCard(50, 100, 'ОТПРАВИТЕЛЬ', data.companyName);
81
+ drawCard(305, 100, 'ПОЛУЧАТЕЛЬ', data.clientName);
105
82
  }
106
- static async createPDF(data, settings) {
107
- const theme = settings.presetTheme || 'modern';
108
- const accent = settings.headerColor || '#1a2b3c';
109
- const htmlLayout = `
110
- <!DOCTYPE html>
111
- <html>
112
- <head>
113
- <style>${this.getStyles(theme, accent)}</style>
114
- </head>
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
- 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,
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
- return Buffer.from(buffer);
209
- }
210
- finally {
211
- await browser.close();
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-contract-lite",
3
- "version": "4.0.1",
3
+ "version": "4.0.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "n8n": {