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.
@@ -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-pdf',
10
+ icon: 'fa:file-invoice',
11
11
  group: ['transform'],
12
12
  version: 1,
13
- description: 'Генерация документов через HTML-шаблоны',
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 (Цветной акцент)', value: 'modern' },
35
- { name: 'Corporate (Строгий)', value: 'corporate' },
36
- { name: 'Minimal (Чистый)', value: '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: '#2563eb' },
41
- // Данные
42
- { displayName: 'Название компании', name: 'companyName', type: 'string', default: 'ООО Инновация' },
43
- { displayName: 'Имя клиента', name: 'clientName', type: 'string', default: 'Иван Иванов' },
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 } });
@@ -1,4 +1,5 @@
1
1
  export declare class DocumentEngine {
2
+ private static getExecutablePath;
2
3
  private static getStyles;
3
4
  static createPDF(data: any, settings: any): Promise<Buffer>;
4
5
  }
@@ -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 puppeteer_1 = __importDefault(require("puppeteer"));
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
- // 1. Шаблоны стилей (CSS)
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: 40px; color: #2d3436; line-height: 1.5; }
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: 80px; top: 450px;
73
+ position: absolute; right: 50px; bottom: 150px;
52
74
  border: 4px double ${accentColor}; color: ${accentColor};
53
- padding: 15px 30px; transform: rotate(-15deg);
54
- font-weight: bold; font-size: 24px; opacity: 0.8;
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: 30px 0; }
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: fixed; bottom: 40px; left: 40px; right: 40px; font-size: 10px; color: #999; border-top: 1px solid #eee; pt: 10px; }
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: 28px; font-weight: 800; color: ${accentColor}; text-transform: uppercase; }
67
- .doc-title { font-size: 14px; color: #636e72; letter-spacing: 2px; }
68
- .card-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 40px; }
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
- body { padding: 60px; }
74
- .header { flex-direction: column; text-align: center; border-bottom: 3px solid #000; padding-bottom: 20px; }
75
- .company-name { font-size: 32px; font-family: serif; font-weight: bold; }
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; font-weight: 400; border-bottom: 1px solid #000; padding-bottom: 5px; }
81
- th { background: transparent; border-bottom: 1px solid #000; }
82
- .card { margin-bottom: 30px; }
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="header">
100
- <div>
101
- <div class="company-name">{{companyName}}</div>
102
- <div class="doc-title">{{title}}</div>
103
- </div>
104
- <div style="text-align: right">
105
- <div style="font-weight: bold">№ {{docId}}</div>
106
- <div>от {{date}}</div>
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
- {{#if showStamp}}
111
- <div class="stamp">{{status}}</div>
112
- {{/if}}
128
+ {{#if showStamp}}
129
+ <div class="stamp">{{status}}</div>
130
+ {{/if}}
113
131
 
114
- <div class="card-grid">
115
- <div class="card">
116
- <small style="color: ${accent}">ОТПРАВИТЕЛЬ</small>
117
- <div style="font-weight: 600">{{companyName}}</div>
118
- </div>
119
- <div class="card">
120
- <small style="color: ${accent}">ПОЛУЧАТЕЛЬ</small>
121
- <div style="font-weight: 600">{{clientName}}</div>
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
- <div class="main-content">
126
- {{#if content}}<p>{{{content}}}</p>{{/if}}
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
- <div style="margin-top: 60px; display: flex; justify-content: space-between;">
147
- <div style="width: 200px; border-top: 1px solid #000; pt: 10px;">Подпись компании</div>
148
- <div style="width: 200px; border-top: 1px solid #000; pt: 10px;">Подпись клиента</div>
149
- </div>
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
- <div class="footer">
152
- ID Документа: {{docId}} | Сгенерировано системой n8n | Проверочный код: {{secureHash}}
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
- // Запуск Puppeteer
176
- const browser = await puppeteer_1.default.launch({
177
- args: ['--no-sandbox', '--disable-setuid-sandbox'],
178
- // Если вы в Docker, путь может быть '/usr/bin/chromium-browser'
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
- await browser.close();
189
- return Buffer.from(buffer);
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.0.2",
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",