n8n-nodes-contract-lite 3.0.1 → 3.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.
@@ -2,5 +2,4 @@ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription
2
2
  export declare class ContractGenerator implements INodeType {
3
3
  description: INodeTypeDescription;
4
4
  execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
- private static getTitle;
6
5
  }
@@ -5,31 +5,43 @@ const DocumentEngine_1 = require("./DocumentEngine");
5
5
  class ContractGenerator {
6
6
  constructor() {
7
7
  this.description = {
8
- displayName: 'Document Generator Pro (RU)',
9
- name: 'documentGeneratorPro',
10
- icon: 'fa:file-signature',
8
+ displayName: 'Document Generator Pro (HTML)',
9
+ name: 'documentGeneratorProHtml',
10
+ icon: 'fa:file-pdf',
11
11
  group: ['transform'],
12
12
  version: 1,
13
- description: 'Премиальный генератор документов с защитой',
13
+ description: 'Генерация документов через HTML-шаблоны',
14
14
  defaults: { name: 'Генератор документов' },
15
15
  inputs: ['main'],
16
16
  outputs: ['main'],
17
17
  properties: [
18
18
  {
19
- displayName: 'Тип шаблона',
20
- name: 'template',
19
+ displayName: 'Тип документа',
20
+ name: 'templateType',
21
21
  type: 'options',
22
22
  options: [
23
23
  { name: 'Счет (Invoice)', value: 'invoice' },
24
24
  { name: 'Договор (Contract)', value: 'contract' },
25
- { name: 'Накладная (Waybill)', value: 'waybill' },
26
25
  { name: 'Акт (Act)', value: 'act' },
27
26
  ],
28
27
  default: 'invoice',
29
28
  },
30
- { displayName: 'Название компании', name: 'companyName', type: 'string', default: 'ООО КНК ГРУПП' },
29
+ {
30
+ displayName: 'Дизайн шаблона',
31
+ name: 'presetTheme',
32
+ type: 'options',
33
+ options: [
34
+ { name: 'Modern (Цветной акцент)', value: 'modern' },
35
+ { name: 'Corporate (Строгий)', value: 'corporate' },
36
+ { name: 'Minimal (Чистый)', value: 'minimal' },
37
+ ],
38
+ default: 'modern',
39
+ },
40
+ { displayName: 'Цвет бренда', name: 'headerColor', type: 'color', default: '#2563eb' },
41
+ // Данные
42
+ { displayName: 'Название компании', name: 'companyName', type: 'string', default: 'ООО Инновация' },
31
43
  { displayName: 'Имя клиента', name: 'clientName', type: 'string', default: 'Иван Иванов' },
32
- // Настройки штампа
44
+ // Штамп
33
45
  { displayName: 'Показать штамп', name: 'showStamp', type: 'boolean', default: true },
34
46
  {
35
47
  displayName: 'Текст штампа',
@@ -40,23 +52,18 @@ class ContractGenerator {
40
52
  },
41
53
  // Контент
42
54
  {
43
- displayName: 'Текст документа',
55
+ displayName: 'Текст документа (HTML)',
44
56
  name: 'content',
45
57
  type: 'string',
46
58
  typeOptions: { rows: 5 },
47
- default: 'Настоящий документ подтверждает...',
48
- displayOptions: { show: { template: ['contract', 'act'] } }
59
+ default: 'Настоящим подтверждаем выполнение обязательств по договору...',
49
60
  },
50
61
  {
51
62
  displayName: 'Данные таблицы (JSON)',
52
63
  name: 'details',
53
64
  type: 'json',
54
- default: '[]',
55
- description: 'Пример: [{"item": "Услуга", "qty": 1, "price": 100, "total": 100}]',
56
- displayOptions: { hide: { template: ['contract'] } }
65
+ default: '[{"Услуга": "Разработка ПО", "Кол-во": 1, "Сумма": "50000"}]',
57
66
  },
58
- // Стили
59
- { displayName: 'Цвет бренда', name: 'headerColor', type: 'color', default: '#1a2b3c' },
60
67
  ],
61
68
  };
62
69
  }
@@ -64,34 +71,35 @@ class ContractGenerator {
64
71
  const items = this.getInputData();
65
72
  const returnData = [];
66
73
  for (let i = 0; i < items.length; i++) {
67
- const template = this.getNodeParameter('template', i);
68
- // Вызываем статичный метод через имя класса
69
- const title = ContractGenerator.getTitle(template);
74
+ const templateType = this.getNodeParameter('templateType', i);
75
+ const presetTheme = this.getNodeParameter('presetTheme', i);
76
+ const titles = { invoice: 'СЧЕТ НА ОПЛАТУ', contract: 'ДОГОВОР ПОСТАВКИ', act: 'АКТ ВЫПОЛНЕННЫХ РАБОТ' };
77
+ const settings = {
78
+ presetTheme,
79
+ headerColor: this.getNodeParameter('headerColor', i),
80
+ };
70
81
  const data = {
82
+ title: titles[templateType] || 'ДОКУМЕНТ',
71
83
  companyName: this.getNodeParameter('companyName', i),
72
84
  clientName: this.getNodeParameter('clientName', i),
73
85
  showStamp: this.getNodeParameter('showStamp', i),
74
- status: this.getNodeParameter('showStamp', i) ? this.getNodeParameter('status', i) : '',
86
+ status: this.getNodeParameter('status', i, ''),
75
87
  content: this.getNodeParameter('content', i, ''),
76
88
  details: this.getNodeParameter('details', i, '[]'),
77
- headerColor: this.getNodeParameter('headerColor', i),
78
- title: title,
79
89
  };
80
- const buffer = await DocumentEngine_1.DocumentEngine.createPDF(data, template);
81
- const binaryData = await this.helpers.prepareBinaryData(buffer, `${template}_${Date.now()}.pdf`);
82
- returnData.push({ json: { success: true, timestamp: new Date() }, binary: { data: binaryData } });
90
+ try {
91
+ const buffer = await DocumentEngine_1.DocumentEngine.createPDF(data, settings);
92
+ 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
+ });
97
+ }
98
+ catch (error) {
99
+ returnData.push({ json: { success: false, error: error.message } });
100
+ }
83
101
  }
84
102
  return [returnData];
85
103
  }
86
- // Добавили static
87
- static getTitle(template) {
88
- const titles = {
89
- invoice: 'Счет на оплату',
90
- contract: 'Договор поставки',
91
- act: 'Акт выполненных работ',
92
- waybill: 'Товарная накладная'
93
- };
94
- return titles[template] || 'Документ';
95
- }
96
104
  }
97
105
  exports.ContractGenerator = ContractGenerator;
@@ -1,10 +1,4 @@
1
1
  export declare class DocumentEngine {
2
- static createPDF(data: any, template: string): Promise<Buffer>;
3
- private static drawPattern;
4
- private static drawHeader;
5
- private static drawCards;
6
- private static drawStamp;
7
- private static drawTable;
8
- private static drawSignatures;
9
- private static drawFooter;
2
+ private static getStyles;
3
+ static createPDF(data: any, settings: any): Promise<Buffer>;
10
4
  }
@@ -1,131 +1,192 @@
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
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.DocumentEngine = void 0;
7
- const pdfkit_1 = __importDefault(require("pdfkit"));
8
- const path_1 = __importDefault(require("path"));
9
- const fs_1 = __importDefault(require("fs"));
40
+ const handlebars = __importStar(require("handlebars"));
41
+ const puppeteer_1 = __importDefault(require("puppeteer"));
10
42
  const crypto_1 = __importDefault(require("crypto"));
11
43
  class DocumentEngine {
12
- static async createPDF(data, template) {
13
- return new Promise((resolve, reject) => {
14
- const doc = new pdfkit_1.default({ size: 'A4', margin: 40, bufferPages: true });
15
- const chunks = [];
16
- doc.on('data', chunk => chunks.push(chunk));
17
- doc.on('end', () => resolve(Buffer.concat(chunks)));
18
- doc.on('error', reject);
19
- const fontPath = path_1.default.join(__dirname, 'Roboto-Regular.ttf');
20
- if (fs_1.default.existsSync(fontPath))
21
- doc.registerFont('Main', fontPath);
22
- doc.font('Main');
23
- const accent = data.headerColor || '#1a2b3c';
24
- // 1. Фоновый паттерн (Фишка: микро-сетка)
25
- this.drawPattern(doc);
26
- // 2. Премиум Шапка
27
- this.drawHeader(doc, data, accent);
28
- // 3. Инфо-карточки
29
- this.drawCards(doc, data, accent);
30
- // 4. Штамп
31
- if (data.showStamp && data.status) {
32
- this.drawStamp(doc, data.status, accent);
33
- }
34
- // 5. Контент
35
- let details = typeof data.details === 'string' ? JSON.parse(data.details || '[]') : data.details;
36
- if (!Array.isArray(details))
37
- details = [];
38
- doc.y = 280;
39
- switch (template) {
40
- case 'invoice':
41
- this.drawTable(doc, ['Наименование', 'Кол-во', 'Цена', 'Сумма'], details, accent);
42
- break;
43
- case 'contract':
44
- doc.fillColor(accent).fontSize(14).text('ПРЕДМЕТ ДОГОВОРА').moveDown();
45
- doc.fillColor('#444').fontSize(11).text(data.content, { align: 'justify', lineGap: 4 });
46
- break;
47
- case 'act':
48
- doc.fillColor('#333').fontSize(11).text(data.content).moveDown();
49
- this.drawTable(doc, ['Услуга', 'Период', 'Статус', 'Итого'], details, accent);
50
- break;
51
- case 'waybill':
52
- this.drawTable(doc, ['Товар', 'Артикул', 'Ед.изм', 'Кол-во'], details, accent);
53
- break;
54
- }
55
- // 6. Подписи и Футер
56
- this.drawSignatures(doc, data, accent);
57
- this.drawFooter(doc, accent);
58
- doc.end();
59
- });
60
- }
61
- static drawPattern(doc) {
62
- doc.save().strokeColor('#000').lineWidth(0.1).strokeOpacity(0.05);
63
- for (let i = 0; i < 842; i += 25) {
64
- doc.moveTo(0, i).lineTo(595, i).stroke();
65
- }
66
- doc.restore();
67
- }
68
- static drawHeader(doc, data, color) {
69
- doc.rect(0, 0, 15, 140).fill(color); // Стильная полоса слева
70
- doc.rect(40, 40, 520, 80).fillOpacity(0.03).fill(color).fillOpacity(1);
71
- doc.fillColor(color).fontSize(22).text(data.companyName.toUpperCase(), 60, 60);
72
- doc.fillColor('#666').fontSize(10).text(data.title, 60, 88);
73
- const docId = crypto_1.default.randomBytes(4).toString('hex').toUpperCase();
74
- doc.fillColor(color).fontSize(9).text('ID ДОКУМЕНТА', 400, 60, { align: 'right', width: 140 });
75
- doc.fontSize(12).text(docId, 400, 72, { align: 'right', width: 140 });
76
- doc.fontSize(9).fillColor('#999').text(new Date().toLocaleDateString('ru-RU'), 400, 88, { align: 'right', width: 140 });
77
- }
78
- static drawCards(doc, data, color) {
79
- const card = (x, y, label, text) => {
80
- doc.rect(x, y, 250, 50).fill('#fcfcfc').strokeColor('#eee').lineWidth(1).stroke();
81
- doc.fillColor(color).fontSize(7).text(label, x + 10, y + 10);
82
- doc.fillColor('#333').fontSize(10).text(text, x + 10, y + 25);
44
+ // 1. Шаблоны стилей (CSS)
45
+ static getStyles(theme, accentColor) {
46
+ const baseStyles = `
47
+ @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; }
49
+ .header { display: flex; justify-content: space-between; margin-bottom: 50px; }
50
+ .stamp {
51
+ position: absolute; right: 80px; top: 450px;
52
+ border: 4px double ${accentColor}; color: ${accentColor};
53
+ padding: 15px 30px; transform: rotate(-15deg);
54
+ font-weight: bold; font-size: 24px; opacity: 0.8;
55
+ text-transform: uppercase; z-index: 100;
56
+ background: rgba(255,255,255,0.8);
57
+ }
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; }
62
+ `;
63
+ const themes = {
64
+ modern: `
65
+ ${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}; }
70
+ `,
71
+ corporate: `
72
+ ${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; }
77
+ `,
78
+ minimal: `
79
+ ${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; }
83
+ `
83
84
  };
84
- card(40, 160, 'ОТПРАВИТЕЛЬ', data.companyName);
85
- card(310, 160, 'ПОЛУЧАТЕЛЬ', data.clientName);
85
+ return themes[theme] || themes.modern;
86
86
  }
87
- static drawStamp(doc, text, color) {
88
- doc.save();
89
- doc.rotate(-15, { origin: [500, 250] });
90
- doc.rect(430, 220, 120, 35).lineWidth(2).strokeColor(color).stroke();
91
- doc.fillColor(color).fontSize(14).text(text.toUpperCase(), 430, 232, { width: 120, align: 'center' });
92
- doc.restore();
93
- }
94
- static drawTable(doc, headers, rows, color) {
95
- let y = doc.y + 20;
96
- doc.rect(40, y, 520, 22).fill(color);
97
- doc.fillColor('#fff').fontSize(8);
98
- headers.forEach((h, i) => doc.text(h, 50 + (i * 130), y + 7));
99
- y += 30;
100
- doc.fillColor('#333').fontSize(9);
101
- rows.forEach((row) => {
102
- const vals = Object.values(row);
103
- vals.forEach((v, i) => doc.text(String(v), 50 + (i * 130), y));
104
- y += 20;
105
- doc.moveTo(40, y - 5).lineTo(560, y - 5).strokeColor('#eee').stroke();
106
- if (y > 700) {
107
- doc.addPage();
108
- y = 50;
109
- }
87
+ // 2. Главный метод генерации
88
+ static async createPDF(data, settings) {
89
+ const theme = settings.presetTheme || 'modern';
90
+ const accent = settings.headerColor || '#1a2b3c';
91
+ const docId = crypto_1.default.randomBytes(4).toString('hex').toUpperCase();
92
+ const htmlLayout = `
93
+ <!DOCTYPE html>
94
+ <html>
95
+ <head>
96
+ <style>${this.getStyles(theme, accent)}</style>
97
+ </head>
98
+ <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>
107
+ </div>
108
+ </div>
109
+
110
+ {{#if showStamp}}
111
+ <div class="stamp">{{status}}</div>
112
+ {{/if}}
113
+
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>
122
+ </div>
123
+ </div>
124
+
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>
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>
150
+
151
+ <div class="footer">
152
+ ID Документа: {{docId}} | Сгенерировано системой n8n | Проверочный код: {{secureHash}}
153
+ </div>
154
+ </body>
155
+ </html>
156
+ `;
157
+ const secureHash = crypto_1.default.createHash('sha256').update(docId).digest('hex').substring(0, 16);
158
+ const template = handlebars.compile(htmlLayout);
159
+ // Подготовка данных для таблицы
160
+ let details = [];
161
+ try {
162
+ details = typeof data.details === 'string' ? JSON.parse(data.details) : data.details;
163
+ }
164
+ catch (e) {
165
+ details = [];
166
+ }
167
+ const finalHtml = template({
168
+ ...data,
169
+ docId,
170
+ secureHash,
171
+ date: new Date().toLocaleDateString('ru-RU'),
172
+ details: Array.isArray(details) ? details.map(Object.values) : [],
173
+ tableHeaders: Array.isArray(details) && details.length > 0 ? Object.keys(details[0]) : []
110
174
  });
111
- doc.y = y;
112
- }
113
- static drawSignatures(doc, data, color) {
114
- const y = 680;
115
- doc.moveTo(40, y).lineTo(560, y).strokeColor('#eee').stroke();
116
- doc.fontSize(8).fillColor('#999').text('М.П. ПОДПИСЬ ОТПРАВИТЕЛЯ', 40, y + 15);
117
- doc.text('ПОДПИСЬ ПОЛУЧАТЕЛЯ', 310, y + 15);
118
- doc.fillColor('#333').fontSize(10).text(data.companyName, 40, y + 45);
119
- doc.text(data.clientName, 310, y + 45);
120
- doc.moveTo(40, y + 40).lineTo(200, y + 40).strokeColor('#ccc').stroke();
121
- doc.moveTo(310, y + 40).lineTo(470, y + 40).stroke();
122
- }
123
- static drawFooter(doc, color) {
124
- const y = 790;
125
- const hash = crypto_1.default.randomBytes(16).toString('hex');
126
- doc.fontSize(6).fillColor('#ccc').text(`SECURE HASH: ${hash}`, 40, y);
127
- doc.fontSize(8).fillColor('#999').text('Документ сформирован автоматически и защищен цифровым идентификатором.', 40, y + 10);
128
- doc.rect(530, y - 5, 30, 30).fill(color);
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' }
187
+ });
188
+ await browser.close();
189
+ return Buffer.from(buffer);
129
190
  }
130
191
  }
131
192
  exports.DocumentEngine = DocumentEngine;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-contract-lite",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "n8n": {
@@ -25,8 +25,10 @@
25
25
  "license": "MIT",
26
26
  "dependencies": {
27
27
  "docx": "^9.5.1",
28
+ "handlebars": "^4.7.8",
28
29
  "mustache": "^4.2.0",
29
- "pdfkit": "^0.13.0"
30
+ "pdfkit": "^0.13.0",
31
+ "puppeteer": "^24.35.0"
30
32
  },
31
33
  "devDependencies": {
32
34
  "@types/node": "^20.19.30",
@@ -34,4 +36,4 @@
34
36
  "n8n-workflow": "^1.120.6",
35
37
  "typescript": "^5.9.3"
36
38
  }
37
- }
39
+ }