n8n-nodes-contract-lite 2.0.0 → 2.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/package.json +7 -4
- package/dist/src/ContractGenerator.d.ts +0 -5
- package/dist/src/ContractGenerator.js +0 -77
- package/index.ts +0 -3
- package/scripts/copy-fonts.js +0 -16
- package/src/ContractGenerator.node.ts +0 -103
- package/src/DocumentEngine.ts +0 -141
- package/src/Roboto-Regular.ttf +0 -0
- package/src/types.ts +0 -8
- package/tsconfig.json +0 -12
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-contract-lite",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Lightweight contract PDF generator for n8n",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
7
10
|
"scripts": {
|
|
8
|
-
"build": "tsc
|
|
9
|
-
"
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
10
13
|
},
|
|
11
14
|
"keywords": [
|
|
12
15
|
"n8n-node",
|
|
@@ -32,4 +35,4 @@
|
|
|
32
35
|
"n8n-workflow": "^1.120.6",
|
|
33
36
|
"typescript": "^5.9.3"
|
|
34
37
|
}
|
|
35
|
-
}
|
|
38
|
+
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { INodeType, INodeTypeDescription, IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
|
2
|
-
export declare class ContractGenerator implements INodeType {
|
|
3
|
-
description: INodeTypeDescription;
|
|
4
|
-
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ContractGenerator = void 0;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const pdfkit_1 = __importDefault(require("pdfkit"));
|
|
9
|
-
class ContractGenerator {
|
|
10
|
-
constructor() {
|
|
11
|
-
this.description = {
|
|
12
|
-
displayName: 'Contract Generator Lite',
|
|
13
|
-
name: 'contractGeneratorLite',
|
|
14
|
-
group: ['transform'],
|
|
15
|
-
version: 1,
|
|
16
|
-
description: 'Generates a professional contract PDF (lightweight)',
|
|
17
|
-
defaults: { name: 'Contract Generator Lite' },
|
|
18
|
-
inputs: ['main'],
|
|
19
|
-
outputs: ['main'],
|
|
20
|
-
properties: [
|
|
21
|
-
{ displayName: 'Contract Data', name: 'contractData', type: 'json', default: {}, description: 'Contract variables' },
|
|
22
|
-
{ displayName: 'Seal Image', name: 'sealImage', type: 'string', default: '', description: 'Path to seal PNG/JPG' },
|
|
23
|
-
{ displayName: 'Signature Image', name: 'signatureImage', type: 'string', default: '', description: 'Path to signature PNG/JPG' },
|
|
24
|
-
],
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
async execute() {
|
|
28
|
-
const items = this.getInputData();
|
|
29
|
-
const returnData = [];
|
|
30
|
-
for (let i = 0; i < items.length; i++) {
|
|
31
|
-
const data = this.getNodeParameter('contractData', i);
|
|
32
|
-
const sealImage = this.getNodeParameter('sealImage', i);
|
|
33
|
-
const signatureImage = this.getNodeParameter('signatureImage', i);
|
|
34
|
-
['company_name', 'client_name', 'contract_number', 'date'].forEach(field => {
|
|
35
|
-
if (!data[field])
|
|
36
|
-
throw new Error(`Missing field: ${field}`);
|
|
37
|
-
});
|
|
38
|
-
const doc = new pdfkit_1.default({ size: 'A4', margin: 50 });
|
|
39
|
-
const buffers = [];
|
|
40
|
-
doc.on('data', buffers.push.bind(buffers));
|
|
41
|
-
doc.on('end', () => { });
|
|
42
|
-
// --- HEADER ---
|
|
43
|
-
doc.rect(0, 0, 595.28, 70).fill('#007BFF'); // голубой хедер
|
|
44
|
-
doc.fillColor('white').fontSize(26).text('KNK GROUP', 50, 25, { align: 'left' });
|
|
45
|
-
doc.moveDown(4);
|
|
46
|
-
doc.fillColor('black').fontSize(12);
|
|
47
|
-
// --- CONTRACT INFO ---
|
|
48
|
-
doc.text(`Договор № ${data.contract_number}`, { continued: true }).text(` от ${data.date}`);
|
|
49
|
-
doc.moveDown();
|
|
50
|
-
// --- PARTIES ---
|
|
51
|
-
doc.font('Helvetica-Bold').text('Исполнитель: ', { continued: true }).font('Helvetica').text(`${data.company_name}, ИНН ${data.inn || ''}`);
|
|
52
|
-
doc.font('Helvetica-Bold').text('Заказчик: ', { continued: true }).font('Helvetica').text(`${data.client_name}`);
|
|
53
|
-
doc.moveDown();
|
|
54
|
-
// --- SUBJECT ---
|
|
55
|
-
doc.font('Helvetica-Bold').text('Предмет договора:');
|
|
56
|
-
doc.font('Helvetica').text(data.subject || 'Оказание услуг по автоматизации бизнес-процессов', { indent: 20 });
|
|
57
|
-
doc.moveDown();
|
|
58
|
-
if (data.VAT) {
|
|
59
|
-
doc.font('Helvetica-Bold').text('НДС: ', { continued: true }).font('Helvetica').text(`включён в сумму ${data.amount || ''} руб.`);
|
|
60
|
-
doc.moveDown();
|
|
61
|
-
}
|
|
62
|
-
// --- SIGNATURES ---
|
|
63
|
-
const startY = doc.y + 30;
|
|
64
|
-
if (fs_1.default.existsSync(signatureImage)) {
|
|
65
|
-
doc.image(signatureImage, 50, startY, { width: 150 });
|
|
66
|
-
}
|
|
67
|
-
if (fs_1.default.existsSync(sealImage)) {
|
|
68
|
-
doc.image(sealImage, 400, startY, { width: 150 });
|
|
69
|
-
}
|
|
70
|
-
doc.end();
|
|
71
|
-
const pdfBuffer = Buffer.concat(buffers);
|
|
72
|
-
returnData.push({ json: { pdf: pdfBuffer.toString('base64') } });
|
|
73
|
-
}
|
|
74
|
-
return [returnData];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
exports.ContractGenerator = ContractGenerator;
|
package/index.ts
DELETED
package/scripts/copy-fonts.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const srcDir = path.join(__dirname, '../src');
|
|
5
|
-
const distDir = path.join(__dirname, '../dist/src');
|
|
6
|
-
|
|
7
|
-
if (!fs.existsSync(distDir)) {
|
|
8
|
-
fs.mkdirSync(distDir, { recursive: true });
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
fs.readdirSync(srcDir).forEach(file => {
|
|
12
|
-
if (file.endsWith('.ttf')) {
|
|
13
|
-
fs.copyFileSync(path.join(srcDir, file), path.join(distDir, file));
|
|
14
|
-
console.log(`Copied ${file} to dist/src/`);
|
|
15
|
-
}
|
|
16
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
-
import { DocumentEngine } from './DocumentEngine';
|
|
3
|
-
|
|
4
|
-
export class ContractGenerator implements INodeType {
|
|
5
|
-
description: INodeTypeDescription = {
|
|
6
|
-
displayName: 'Document Generator Pro (RU)',
|
|
7
|
-
name: 'documentGeneratorPro',
|
|
8
|
-
icon: 'fa:file-signature',
|
|
9
|
-
group: ['transform'],
|
|
10
|
-
version: 1,
|
|
11
|
-
description: 'Премиальный генератор документов с защитой',
|
|
12
|
-
defaults: { name: 'Генератор документов' },
|
|
13
|
-
inputs: ['main'],
|
|
14
|
-
outputs: ['main'],
|
|
15
|
-
properties: [
|
|
16
|
-
{
|
|
17
|
-
displayName: 'Тип шаблона',
|
|
18
|
-
name: 'template',
|
|
19
|
-
type: 'options',
|
|
20
|
-
options: [
|
|
21
|
-
{ name: 'Счет (Invoice)', value: 'invoice' },
|
|
22
|
-
{ name: 'Договор (Contract)', value: 'contract' },
|
|
23
|
-
{ name: 'Накладная (Waybill)', value: 'waybill' },
|
|
24
|
-
{ name: 'Акт (Act)', value: 'act' },
|
|
25
|
-
],
|
|
26
|
-
default: 'invoice',
|
|
27
|
-
},
|
|
28
|
-
{ displayName: 'Название компании', name: 'companyName', type: 'string', default: 'ООО КНК ГРУПП' },
|
|
29
|
-
{ displayName: 'Имя клиента', name: 'clientName', type: 'string', default: 'Иван Иванов' },
|
|
30
|
-
|
|
31
|
-
// Настройки штампа
|
|
32
|
-
{ displayName: 'Показать штамп', name: 'showStamp', type: 'boolean', default: true },
|
|
33
|
-
{
|
|
34
|
-
displayName: 'Текст штампа',
|
|
35
|
-
name: 'status',
|
|
36
|
-
type: 'string',
|
|
37
|
-
default: 'ОПЛАЧЕНО',
|
|
38
|
-
displayOptions: { show: { showStamp: [true] } }
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
// Контент
|
|
42
|
-
{
|
|
43
|
-
displayName: 'Текст документа',
|
|
44
|
-
name: 'content',
|
|
45
|
-
type: 'string',
|
|
46
|
-
typeOptions: { rows: 5 },
|
|
47
|
-
default: 'Настоящий документ подтверждает...',
|
|
48
|
-
displayOptions: { show: { template: ['contract', 'act'] } }
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
displayName: 'Данные таблицы (JSON)',
|
|
52
|
-
name: 'details',
|
|
53
|
-
type: 'json',
|
|
54
|
-
default: '[]',
|
|
55
|
-
description: 'Пример: [{"item": "Услуга", "qty": 1, "price": 100, "total": 100}]',
|
|
56
|
-
displayOptions: { hide: { template: ['contract'] } }
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
// Стили
|
|
60
|
-
{ displayName: 'Цвет бренда', name: 'headerColor', type: 'color', default: '#1a2b3c' },
|
|
61
|
-
],
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
65
|
-
const items = this.getInputData();
|
|
66
|
-
const returnData: INodeExecutionData[] = [];
|
|
67
|
-
|
|
68
|
-
for (let i = 0; i < items.length; i++) {
|
|
69
|
-
const template = this.getNodeParameter('template', i) as string;
|
|
70
|
-
|
|
71
|
-
// Вызываем статичный метод через имя класса
|
|
72
|
-
const title = ContractGenerator.getTitle(template);
|
|
73
|
-
|
|
74
|
-
const data = {
|
|
75
|
-
companyName: this.getNodeParameter('companyName', i),
|
|
76
|
-
clientName: this.getNodeParameter('clientName', i),
|
|
77
|
-
showStamp: this.getNodeParameter('showStamp', i),
|
|
78
|
-
status: this.getNodeParameter('showStamp', i) ? this.getNodeParameter('status', i) : '',
|
|
79
|
-
content: this.getNodeParameter('content', i, ''),
|
|
80
|
-
details: this.getNodeParameter('details', i, '[]'),
|
|
81
|
-
headerColor: this.getNodeParameter('headerColor', i),
|
|
82
|
-
title: title,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const buffer = await DocumentEngine.createPDF(data, template);
|
|
86
|
-
const binaryData = await this.helpers.prepareBinaryData(buffer, `${template}_${Date.now()}.pdf`);
|
|
87
|
-
|
|
88
|
-
returnData.push({ json: { success: true, timestamp: new Date() }, binary: { data: binaryData } });
|
|
89
|
-
}
|
|
90
|
-
return [returnData];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Добавили static
|
|
94
|
-
private static getTitle(template: string): string {
|
|
95
|
-
const titles: any = {
|
|
96
|
-
invoice: 'Счет на оплату',
|
|
97
|
-
contract: 'Договор поставки',
|
|
98
|
-
act: 'Акт выполненных работ',
|
|
99
|
-
waybill: 'Товарная накладная'
|
|
100
|
-
};
|
|
101
|
-
return titles[template] || 'Документ';
|
|
102
|
-
}
|
|
103
|
-
}
|
package/src/DocumentEngine.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import PDFDocument from 'pdfkit';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import crypto from 'crypto';
|
|
5
|
-
|
|
6
|
-
export class DocumentEngine {
|
|
7
|
-
static async createPDF(data: any, template: string): Promise<Buffer> {
|
|
8
|
-
return new Promise((resolve, reject) => {
|
|
9
|
-
const doc = new PDFDocument({ size: 'A4', margin: 40, bufferPages: true });
|
|
10
|
-
const chunks: Buffer[] = [];
|
|
11
|
-
doc.on('data', chunk => chunks.push(chunk));
|
|
12
|
-
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
|
13
|
-
doc.on('error', reject);
|
|
14
|
-
|
|
15
|
-
const fontPath = path.join(__dirname, 'Roboto-Regular.ttf');
|
|
16
|
-
if (fs.existsSync(fontPath)) doc.registerFont('Main', fontPath);
|
|
17
|
-
doc.font('Main');
|
|
18
|
-
|
|
19
|
-
const accent = data.headerColor || '#1a2b3c';
|
|
20
|
-
|
|
21
|
-
// 1. Фоновый паттерн (Фишка: микро-сетка)
|
|
22
|
-
this.drawPattern(doc);
|
|
23
|
-
|
|
24
|
-
// 2. Премиум Шапка
|
|
25
|
-
this.drawHeader(doc, data, accent);
|
|
26
|
-
|
|
27
|
-
// 3. Инфо-карточки
|
|
28
|
-
this.drawCards(doc, data, accent);
|
|
29
|
-
|
|
30
|
-
// 4. Штамп
|
|
31
|
-
if (data.showStamp && data.status) {
|
|
32
|
-
this.drawStamp(doc, data.status, accent);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// 5. Контент
|
|
36
|
-
let details = typeof data.details === 'string' ? JSON.parse(data.details || '[]') : data.details;
|
|
37
|
-
if (!Array.isArray(details)) details = [];
|
|
38
|
-
|
|
39
|
-
doc.y = 280;
|
|
40
|
-
switch (template) {
|
|
41
|
-
case 'invoice':
|
|
42
|
-
this.drawTable(doc, ['Наименование', 'Кол-во', 'Цена', 'Сумма'], details, accent);
|
|
43
|
-
break;
|
|
44
|
-
case 'contract':
|
|
45
|
-
doc.fillColor(accent).fontSize(14).text('ПРЕДМЕТ ДОГОВОРА').moveDown();
|
|
46
|
-
doc.fillColor('#444').fontSize(11).text(data.content, { align: 'justify', lineGap: 4 });
|
|
47
|
-
break;
|
|
48
|
-
case 'act':
|
|
49
|
-
doc.fillColor('#333').fontSize(11).text(data.content).moveDown();
|
|
50
|
-
this.drawTable(doc, ['Услуга', 'Период', 'Статус', 'Итого'], details, accent);
|
|
51
|
-
break;
|
|
52
|
-
case 'waybill':
|
|
53
|
-
this.drawTable(doc, ['Товар', 'Артикул', 'Ед.изм', 'Кол-во'], details, accent);
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// 6. Подписи и Футер
|
|
58
|
-
this.drawSignatures(doc, data, accent);
|
|
59
|
-
this.drawFooter(doc, accent);
|
|
60
|
-
|
|
61
|
-
doc.end();
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private static drawPattern(doc: any) {
|
|
66
|
-
doc.save().strokeColor('#000').lineWidth(0.1).strokeOpacity(0.05);
|
|
67
|
-
for(let i=0; i<842; i+=25) {
|
|
68
|
-
doc.moveTo(0, i).lineTo(595, i).stroke();
|
|
69
|
-
}
|
|
70
|
-
doc.restore();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private static drawHeader(doc: any, data: any, color: string) {
|
|
74
|
-
doc.rect(0, 0, 15, 140).fill(color); // Стильная полоса слева
|
|
75
|
-
doc.rect(40, 40, 520, 80).fillOpacity(0.03).fill(color).fillOpacity(1);
|
|
76
|
-
|
|
77
|
-
doc.fillColor(color).fontSize(22).text(data.companyName.toUpperCase(), 60, 60);
|
|
78
|
-
doc.fillColor('#666').fontSize(10).text(data.title, 60, 88);
|
|
79
|
-
|
|
80
|
-
const docId = crypto.randomBytes(4).toString('hex').toUpperCase();
|
|
81
|
-
doc.fillColor(color).fontSize(9).text('ID ДОКУМЕНТА', 400, 60, { align: 'right', width: 140 });
|
|
82
|
-
doc.fontSize(12).text(docId, 400, 72, { align: 'right', width: 140 });
|
|
83
|
-
doc.fontSize(9).fillColor('#999').text(new Date().toLocaleDateString('ru-RU'), 400, 88, { align: 'right', width: 140 });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private static drawCards(doc: any, data: any, color: string) {
|
|
87
|
-
const card = (x: number, y: number, label: string, text: string) => {
|
|
88
|
-
doc.rect(x, y, 250, 50).fill('#fcfcfc').strokeColor('#eee').lineWidth(1).stroke();
|
|
89
|
-
doc.fillColor(color).fontSize(7).text(label, x + 10, y + 10);
|
|
90
|
-
doc.fillColor('#333').fontSize(10).text(text, x + 10, y + 25);
|
|
91
|
-
};
|
|
92
|
-
card(40, 160, 'ОТПРАВИТЕЛЬ', data.companyName);
|
|
93
|
-
card(310, 160, 'ПОЛУЧАТЕЛЬ', data.clientName);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private static drawStamp(doc: any, text: string, color: string) {
|
|
97
|
-
doc.save();
|
|
98
|
-
doc.rotate(-15, { origin: [500, 250] });
|
|
99
|
-
doc.rect(430, 220, 120, 35).lineWidth(2).strokeColor(color).stroke();
|
|
100
|
-
doc.fillColor(color).fontSize(14).text(text.toUpperCase(), 430, 232, { width: 120, align: 'center' });
|
|
101
|
-
doc.restore();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
private static drawTable(doc: any, headers: string[], rows: any[], color: string) {
|
|
105
|
-
let y = doc.y + 20;
|
|
106
|
-
doc.rect(40, y, 520, 22).fill(color);
|
|
107
|
-
doc.fillColor('#fff').fontSize(8);
|
|
108
|
-
headers.forEach((h, i) => doc.text(h, 50 + (i * 130), y + 7));
|
|
109
|
-
|
|
110
|
-
y += 30;
|
|
111
|
-
doc.fillColor('#333').fontSize(9);
|
|
112
|
-
rows.forEach((row: any) => {
|
|
113
|
-
const vals = Object.values(row);
|
|
114
|
-
vals.forEach((v: any, i) => doc.text(String(v), 50 + (i * 130), y));
|
|
115
|
-
y += 20;
|
|
116
|
-
doc.moveTo(40, y - 5).lineTo(560, y - 5).strokeColor('#eee').stroke();
|
|
117
|
-
if (y > 700) { doc.addPage(); y = 50; }
|
|
118
|
-
});
|
|
119
|
-
doc.y = y;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
private static drawSignatures(doc: any, data: any, color: string) {
|
|
123
|
-
const y = 680;
|
|
124
|
-
doc.moveTo(40, y).lineTo(560, y).strokeColor('#eee').stroke();
|
|
125
|
-
doc.fontSize(8).fillColor('#999').text('М.П. ПОДПИСЬ ОТПРАВИТЕЛЯ', 40, y + 15);
|
|
126
|
-
doc.text('ПОДПИСЬ ПОЛУЧАТЕЛЯ', 310, y + 15);
|
|
127
|
-
|
|
128
|
-
doc.fillColor('#333').fontSize(10).text(data.companyName, 40, y + 45);
|
|
129
|
-
doc.text(data.clientName, 310, y + 45);
|
|
130
|
-
doc.moveTo(40, y + 40).lineTo(200, y + 40).strokeColor('#ccc').stroke();
|
|
131
|
-
doc.moveTo(310, y + 40).lineTo(470, y + 40).stroke();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private static drawFooter(doc: any, color: string) {
|
|
135
|
-
const y = 790;
|
|
136
|
-
const hash = crypto.randomBytes(16).toString('hex');
|
|
137
|
-
doc.fontSize(6).fillColor('#ccc').text(`SECURE HASH: ${hash}`, 40, y);
|
|
138
|
-
doc.fontSize(8).fillColor('#999').text('Документ сформирован автоматически и защищен цифровым идентификатором.', 40, y + 10);
|
|
139
|
-
doc.rect(530, y - 5, 30, 30).fill(color);
|
|
140
|
-
}
|
|
141
|
-
}
|
package/src/Roboto-Regular.ttf
DELETED
|
Binary file
|
package/src/types.ts
DELETED
package/tsconfig.json
DELETED