n8n-nodes-contract-generate 1.0.0
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/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/src/ContractGenerator.d.ts +5 -0
- package/dist/src/ContractGenerator.js +77 -0
- package/dist/src/ContractGenerator.node.d.ts +5 -0
- package/dist/src/ContractGenerator.node.js +95 -0
- package/dist/src/DocumentEngine.d.ts +12 -0
- package/dist/src/DocumentEngine.js +116 -0
- package/dist/src/types.d.ts +11 -0
- package/dist/src/types.js +2 -0
- package/index.ts +3 -0
- package/package.json +35 -0
- package/src/ContractGenerator.node.ts +95 -0
- package/src/DocumentEngine.ts +111 -0
- package/src/types.ts +8 -0
- package/tsconfig.json +12 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContractGenerator = void 0;
|
|
4
|
+
const ContractGenerator_node_1 = require("./src/ContractGenerator.node");
|
|
5
|
+
Object.defineProperty(exports, "ContractGenerator", { enumerable: true, get: function () { return ContractGenerator_node_1.ContractGenerator; } });
|
|
@@ -0,0 +1,5 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class ContractGenerator implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContractGenerator = void 0;
|
|
4
|
+
const DocumentEngine_1 = require("./DocumentEngine");
|
|
5
|
+
class ContractGenerator {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.description = {
|
|
8
|
+
displayName: 'Document Generator Pro',
|
|
9
|
+
name: 'documentGeneratorPro',
|
|
10
|
+
icon: 'fa:file-invoice',
|
|
11
|
+
group: ['transform'],
|
|
12
|
+
version: 1,
|
|
13
|
+
description: 'Генерация PDF по шаблонам: Счета, Договоры, Акты',
|
|
14
|
+
defaults: { name: 'Document Generator' },
|
|
15
|
+
inputs: ['main'],
|
|
16
|
+
outputs: ['main'],
|
|
17
|
+
properties: [
|
|
18
|
+
{
|
|
19
|
+
displayName: 'Template Type',
|
|
20
|
+
name: 'template',
|
|
21
|
+
type: 'options',
|
|
22
|
+
options: [
|
|
23
|
+
{ name: 'Счет (Invoice)', value: 'invoice' },
|
|
24
|
+
{ name: 'Договор (Contract)', value: 'contract' },
|
|
25
|
+
{ name: 'Накладная (Waybill)', value: 'waybill' },
|
|
26
|
+
{ name: 'Отчет (Report)', value: 'report' },
|
|
27
|
+
{ name: 'Акт (Act)', value: 'act' },
|
|
28
|
+
],
|
|
29
|
+
default: 'contract',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
displayName: 'Company Name',
|
|
33
|
+
name: 'companyName',
|
|
34
|
+
type: 'string',
|
|
35
|
+
default: 'My Company',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
displayName: 'Client Name',
|
|
39
|
+
name: 'clientName',
|
|
40
|
+
type: 'string',
|
|
41
|
+
default: '',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
displayName: 'Document Title',
|
|
45
|
+
name: 'title',
|
|
46
|
+
type: 'string',
|
|
47
|
+
default: 'Document #1',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
displayName: 'Main Content (AI Text)',
|
|
51
|
+
name: 'content',
|
|
52
|
+
type: 'string',
|
|
53
|
+
typeOptions: { rows: 5 },
|
|
54
|
+
default: '',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
displayName: 'Table Details (JSON Array)',
|
|
58
|
+
name: 'details',
|
|
59
|
+
type: 'json',
|
|
60
|
+
default: '[]',
|
|
61
|
+
description: 'Пример для счета: [{"item":"Услуга", "qty":1, "price":100, "sum":100}]',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
displayName: 'Header Color',
|
|
65
|
+
name: 'headerColor',
|
|
66
|
+
type: 'color',
|
|
67
|
+
default: '#2c3e50',
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async execute() {
|
|
73
|
+
const items = this.getInputData();
|
|
74
|
+
const returnData = [];
|
|
75
|
+
for (let i = 0; i < items.length; i++) {
|
|
76
|
+
const template = this.getNodeParameter('template', i);
|
|
77
|
+
const data = {
|
|
78
|
+
companyName: this.getNodeParameter('companyName', i),
|
|
79
|
+
clientName: this.getNodeParameter('clientName', i),
|
|
80
|
+
title: this.getNodeParameter('title', i),
|
|
81
|
+
content: this.getNodeParameter('content', i),
|
|
82
|
+
details: this.getNodeParameter('details', i),
|
|
83
|
+
headerColor: this.getNodeParameter('headerColor', i),
|
|
84
|
+
};
|
|
85
|
+
const buffer = await DocumentEngine_1.DocumentEngine.createPDF(data, template);
|
|
86
|
+
const binaryData = await this.helpers.prepareBinaryData(buffer, `${template}.pdf`, 'application/pdf');
|
|
87
|
+
returnData.push({
|
|
88
|
+
json: { success: true, template },
|
|
89
|
+
binary: { data: binaryData }
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
return [returnData];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.ContractGenerator = ContractGenerator;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class DocumentEngine {
|
|
2
|
+
static createPDF(data: any, template: string): Promise<Buffer>;
|
|
3
|
+
private static drawHeader;
|
|
4
|
+
private static drawInvoice;
|
|
5
|
+
private static drawContract;
|
|
6
|
+
private static drawWaybill;
|
|
7
|
+
private static drawReport;
|
|
8
|
+
private static drawAct;
|
|
9
|
+
private static drawGeneric;
|
|
10
|
+
private static drawTable;
|
|
11
|
+
private static drawFooter;
|
|
12
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
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.DocumentEngine = void 0;
|
|
7
|
+
const pdfkit_1 = __importDefault(require("pdfkit"));
|
|
8
|
+
class DocumentEngine {
|
|
9
|
+
static async createPDF(data, template) {
|
|
10
|
+
return new Promise((resolve) => {
|
|
11
|
+
const doc = new pdfkit_1.default({ size: 'A4', margin: 50 });
|
|
12
|
+
const chunks = [];
|
|
13
|
+
doc.on('data', chunk => chunks.push(chunk));
|
|
14
|
+
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
|
15
|
+
// --- КОРРЕКТИРОВКА ДАННЫХ ---
|
|
16
|
+
let details = data.details;
|
|
17
|
+
if (typeof details === 'string') {
|
|
18
|
+
try {
|
|
19
|
+
details = JSON.parse(details);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
details = [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!Array.isArray(details))
|
|
26
|
+
details = [];
|
|
27
|
+
// --- ОБЩИЙ ХЕДЕР ---
|
|
28
|
+
this.drawHeader(doc, data);
|
|
29
|
+
// --- ВЫБОР ШАБЛОНА ---
|
|
30
|
+
switch (template) {
|
|
31
|
+
case 'invoice':
|
|
32
|
+
this.drawInvoice(doc, data, details);
|
|
33
|
+
break;
|
|
34
|
+
case 'contract':
|
|
35
|
+
this.drawContract(doc, data);
|
|
36
|
+
break;
|
|
37
|
+
case 'waybill':
|
|
38
|
+
this.drawWaybill(doc, data, details);
|
|
39
|
+
break;
|
|
40
|
+
case 'report':
|
|
41
|
+
this.drawReport(doc, data, details);
|
|
42
|
+
break;
|
|
43
|
+
case 'act':
|
|
44
|
+
this.drawAct(doc, data, details);
|
|
45
|
+
break;
|
|
46
|
+
default: this.drawGeneric(doc, data);
|
|
47
|
+
}
|
|
48
|
+
// --- ОБЩИЙ ФУТЕР ---
|
|
49
|
+
this.drawFooter(doc, data);
|
|
50
|
+
doc.end();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
static drawHeader(doc, data) {
|
|
54
|
+
doc.rect(0, 0, 600, 80).fill(data.headerColor || '#2c3e50');
|
|
55
|
+
doc.fillColor('#ffffff').fontSize(20).text(data.companyName, 50, 30);
|
|
56
|
+
doc.fontSize(10).text(data.title || 'ДОКУМЕНТ', 50, 55, { characterSpacing: 1 });
|
|
57
|
+
doc.fillColor('#000000').moveDown(4);
|
|
58
|
+
}
|
|
59
|
+
// 1. ШАБЛОН: СЧЕТ (INVOICE) - Фокус на таблице и сумме
|
|
60
|
+
static drawInvoice(doc, data, details) {
|
|
61
|
+
doc.fontSize(16).text('СЧЕТ НА ОПЛАТУ', { align: 'center' }).moveDown();
|
|
62
|
+
this.drawTable(doc, ['Наименование', 'Кол-во', 'Цена', 'Сумма'], details);
|
|
63
|
+
const total = details.reduce((sum, item) => sum + (Number(item.sum) || 0), 0);
|
|
64
|
+
doc.moveDown().fontSize(12).font('Helvetica-Bold').text(`ИТОГО К ОПЛАТЕ: ${total} руб.`, { align: 'right' });
|
|
65
|
+
}
|
|
66
|
+
// 2. ШАБЛОН: ДОГОВОР (CONTRACT) - Фокус на тексте (ИИ)
|
|
67
|
+
static drawContract(doc, data) {
|
|
68
|
+
doc.fontSize(14).text('ДОГОВОР ОКАЗАНИЯ УСЛУГ', { align: 'center' }).moveDown();
|
|
69
|
+
doc.fontSize(11).font('Helvetica').text(data.content, { align: 'justify', lineGap: 4 });
|
|
70
|
+
}
|
|
71
|
+
// 3. ШАБЛОН: НАКЛАДНАЯ (WAYBILL) - Фокус на отправителе/получателе
|
|
72
|
+
static drawWaybill(doc, data, details) {
|
|
73
|
+
doc.fontSize(14).text('ТОВАРНАЯ НАКЛАДНАЯ', { underline: true }).moveDown();
|
|
74
|
+
doc.fontSize(10).text(`ОТПРАВИТЕЛЬ: ${data.companyName}`);
|
|
75
|
+
doc.text(`ПОЛУЧАТЕЛЬ: ${data.clientName || '____________________'}`).moveDown();
|
|
76
|
+
this.drawTable(doc, ['Товар', 'Ед.изм', 'Кол-во', 'Вес'], details);
|
|
77
|
+
}
|
|
78
|
+
// 4. ШАБЛОН: ОТЧЕТ (REPORT) - Акцент на аналитике
|
|
79
|
+
static drawReport(doc, data, details) {
|
|
80
|
+
doc.fontSize(16).fillColor('#2c3e50').text('АНАЛИТИЧЕСКИЙ ОТЧЕТ').moveDown();
|
|
81
|
+
doc.fillColor('#333').fontSize(11).text(data.content).moveDown();
|
|
82
|
+
details.forEach(item => {
|
|
83
|
+
doc.font('Helvetica-Bold').text(`${item.label}: `, { continued: true })
|
|
84
|
+
.font('Helvetica').text(item.value);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// 5. ШАБЛОН: АКТ (ACT) - Официальное подтверждение
|
|
88
|
+
static drawAct(doc, data, details) {
|
|
89
|
+
doc.fontSize(14).text('АКТ ВЫПОЛНЕННЫХ РАБОТ', { align: 'center' }).moveDown();
|
|
90
|
+
doc.fontSize(10).text(`Мы, нижеподписавшиеся, составили настоящий акт о том, что услуги выполнены полностью...`).moveDown();
|
|
91
|
+
this.drawTable(doc, ['Услуга', 'Срок', 'Результат', 'Статус'], details);
|
|
92
|
+
}
|
|
93
|
+
static drawGeneric(doc, data) {
|
|
94
|
+
doc.text(data.content || 'Пустой документ');
|
|
95
|
+
}
|
|
96
|
+
static drawTable(doc, headers, rows) {
|
|
97
|
+
const startX = 50;
|
|
98
|
+
let currentY = doc.y;
|
|
99
|
+
// Заголовок таблицы
|
|
100
|
+
doc.font('Helvetica-Bold').fontSize(10);
|
|
101
|
+
headers.forEach((h, i) => doc.text(h, startX + (i * 120), currentY));
|
|
102
|
+
doc.moveTo(startX, currentY + 15).lineTo(550, currentY + 15).stroke();
|
|
103
|
+
// Строки
|
|
104
|
+
doc.font('Helvetica').moveDown(1.5);
|
|
105
|
+
rows.forEach(row => {
|
|
106
|
+
const rowValues = Object.values(row);
|
|
107
|
+
const y = doc.y;
|
|
108
|
+
rowValues.forEach((val, i) => doc.text(String(val), startX + (i * 120), y));
|
|
109
|
+
doc.moveDown();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
static drawFooter(doc, data) {
|
|
113
|
+
doc.fontSize(8).fillColor('#999').text('Сгенерировано автоматически системой n8n', 50, 780, { align: 'center' });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.DocumentEngine = DocumentEngine;
|
package/index.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-contract-generate",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight contract PDF generator for n8n",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"prepare": "npm run build"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"n8n-node",
|
|
13
|
+
"contract",
|
|
14
|
+
"pdf",
|
|
15
|
+
"lightweight"
|
|
16
|
+
],
|
|
17
|
+
"author": "KNK-GROUP",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"docx": "^9.5.1",
|
|
21
|
+
"mustache": "^4.2.0",
|
|
22
|
+
"pdfkit": "^0.13.0"
|
|
23
|
+
},
|
|
24
|
+
"n8n": {
|
|
25
|
+
"nodes": [
|
|
26
|
+
"dist/src/ContractGenerator.node.js"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.19.30",
|
|
31
|
+
"@types/pdfkit": "^0.17.4",
|
|
32
|
+
"n8n-workflow": "^1.120.6",
|
|
33
|
+
"typescript": "^5.9.3"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
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',
|
|
7
|
+
name: 'documentGeneratorPro',
|
|
8
|
+
icon: 'fa:file-invoice',
|
|
9
|
+
group: ['transform'],
|
|
10
|
+
version: 1,
|
|
11
|
+
description: 'Генерация PDF по шаблонам: Счета, Договоры, Акты',
|
|
12
|
+
defaults: { name: 'Document Generator' },
|
|
13
|
+
inputs: ['main'],
|
|
14
|
+
outputs: ['main'],
|
|
15
|
+
properties: [
|
|
16
|
+
{
|
|
17
|
+
displayName: 'Template Type',
|
|
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: 'Отчет (Report)', value: 'report' },
|
|
25
|
+
{ name: 'Акт (Act)', value: 'act' },
|
|
26
|
+
],
|
|
27
|
+
default: 'contract',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
displayName: 'Company Name',
|
|
31
|
+
name: 'companyName',
|
|
32
|
+
type: 'string',
|
|
33
|
+
default: 'My Company',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
displayName: 'Client Name',
|
|
37
|
+
name: 'clientName',
|
|
38
|
+
type: 'string',
|
|
39
|
+
default: '',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
displayName: 'Document Title',
|
|
43
|
+
name: 'title',
|
|
44
|
+
type: 'string',
|
|
45
|
+
default: 'Document #1',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
displayName: 'Main Content (AI Text)',
|
|
49
|
+
name: 'content',
|
|
50
|
+
type: 'string',
|
|
51
|
+
typeOptions: { rows: 5 },
|
|
52
|
+
default: '',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
displayName: 'Table Details (JSON Array)',
|
|
56
|
+
name: 'details',
|
|
57
|
+
type: 'json',
|
|
58
|
+
default: '[]',
|
|
59
|
+
description: 'Пример для счета: [{"item":"Услуга", "qty":1, "price":100, "sum":100}]',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
displayName: 'Header Color',
|
|
63
|
+
name: 'headerColor',
|
|
64
|
+
type: 'color',
|
|
65
|
+
default: '#2c3e50',
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
71
|
+
const items = this.getInputData();
|
|
72
|
+
const returnData: INodeExecutionData[] = [];
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < items.length; i++) {
|
|
75
|
+
const template = this.getNodeParameter('template', i) as string;
|
|
76
|
+
const data = {
|
|
77
|
+
companyName: this.getNodeParameter('companyName', i),
|
|
78
|
+
clientName: this.getNodeParameter('clientName', i),
|
|
79
|
+
title: this.getNodeParameter('title', i),
|
|
80
|
+
content: this.getNodeParameter('content', i),
|
|
81
|
+
details: this.getNodeParameter('details', i),
|
|
82
|
+
headerColor: this.getNodeParameter('headerColor', i),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const buffer = await DocumentEngine.createPDF(data, template);
|
|
86
|
+
const binaryData = await this.helpers.prepareBinaryData(buffer, `${template}.pdf`, 'application/pdf');
|
|
87
|
+
|
|
88
|
+
returnData.push({
|
|
89
|
+
json: { success: true, template },
|
|
90
|
+
binary: { data: binaryData }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return [returnData];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import PDFDocument from 'pdfkit';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
|
|
4
|
+
export class DocumentEngine {
|
|
5
|
+
static async createPDF(data: any, template: string): Promise<Buffer> {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const doc = new PDFDocument({ size: 'A4', margin: 50 });
|
|
8
|
+
const chunks: Buffer[] = [];
|
|
9
|
+
doc.on('data', chunk => chunks.push(chunk));
|
|
10
|
+
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
|
11
|
+
|
|
12
|
+
// --- КОРРЕКТИРОВКА ДАННЫХ ---
|
|
13
|
+
let details = data.details;
|
|
14
|
+
if (typeof details === 'string') {
|
|
15
|
+
try { details = JSON.parse(details); } catch { details = []; }
|
|
16
|
+
}
|
|
17
|
+
if (!Array.isArray(details)) details = [];
|
|
18
|
+
|
|
19
|
+
// --- ОБЩИЙ ХЕДЕР ---
|
|
20
|
+
this.drawHeader(doc, data);
|
|
21
|
+
|
|
22
|
+
// --- ВЫБОР ШАБЛОНА ---
|
|
23
|
+
switch (template) {
|
|
24
|
+
case 'invoice': this.drawInvoice(doc, data, details); break;
|
|
25
|
+
case 'contract': this.drawContract(doc, data); break;
|
|
26
|
+
case 'waybill': this.drawWaybill(doc, data, details); break;
|
|
27
|
+
case 'report': this.drawReport(doc, data, details); break;
|
|
28
|
+
case 'act': this.drawAct(doc, data, details); break;
|
|
29
|
+
default: this.drawGeneric(doc, data);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// --- ОБЩИЙ ФУТЕР ---
|
|
33
|
+
this.drawFooter(doc, data);
|
|
34
|
+
|
|
35
|
+
doc.end();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private static drawHeader(doc: any, data: any) {
|
|
40
|
+
doc.rect(0, 0, 600, 80).fill(data.headerColor || '#2c3e50');
|
|
41
|
+
doc.fillColor('#ffffff').fontSize(20).text(data.companyName, 50, 30);
|
|
42
|
+
doc.fontSize(10).text(data.title || 'ДОКУМЕНТ', 50, 55, { characterSpacing: 1 });
|
|
43
|
+
doc.fillColor('#000000').moveDown(4);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 1. ШАБЛОН: СЧЕТ (INVOICE) - Фокус на таблице и сумме
|
|
47
|
+
private static drawInvoice(doc: any, data: any, details: any[]) {
|
|
48
|
+
doc.fontSize(16).text('СЧЕТ НА ОПЛАТУ', { align: 'center' }).moveDown();
|
|
49
|
+
this.drawTable(doc, ['Наименование', 'Кол-во', 'Цена', 'Сумма'], details);
|
|
50
|
+
const total = details.reduce((sum, item) => sum + (Number(item.sum) || 0), 0);
|
|
51
|
+
doc.moveDown().fontSize(12).font('Helvetica-Bold').text(`ИТОГО К ОПЛАТЕ: ${total} руб.`, { align: 'right' });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 2. ШАБЛОН: ДОГОВОР (CONTRACT) - Фокус на тексте (ИИ)
|
|
55
|
+
private static drawContract(doc: any, data: any) {
|
|
56
|
+
doc.fontSize(14).text('ДОГОВОР ОКАЗАНИЯ УСЛУГ', { align: 'center' }).moveDown();
|
|
57
|
+
doc.fontSize(11).font('Helvetica').text(data.content, { align: 'justify', lineGap: 4 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 3. ШАБЛОН: НАКЛАДНАЯ (WAYBILL) - Фокус на отправителе/получателе
|
|
61
|
+
private static drawWaybill(doc: any, data: any, details: any[]) {
|
|
62
|
+
doc.fontSize(14).text('ТОВАРНАЯ НАКЛАДНАЯ', { underline: true }).moveDown();
|
|
63
|
+
doc.fontSize(10).text(`ОТПРАВИТЕЛЬ: ${data.companyName}`);
|
|
64
|
+
doc.text(`ПОЛУЧАТЕЛЬ: ${data.clientName || '____________________'}`).moveDown();
|
|
65
|
+
this.drawTable(doc, ['Товар', 'Ед.изм', 'Кол-во', 'Вес'], details);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 4. ШАБЛОН: ОТЧЕТ (REPORT) - Акцент на аналитике
|
|
69
|
+
private static drawReport(doc: any, data: any, details: any[]) {
|
|
70
|
+
doc.fontSize(16).fillColor('#2c3e50').text('АНАЛИТИЧЕСКИЙ ОТЧЕТ').moveDown();
|
|
71
|
+
doc.fillColor('#333').fontSize(11).text(data.content).moveDown();
|
|
72
|
+
details.forEach(item => {
|
|
73
|
+
doc.font('Helvetica-Bold').text(`${item.label}: `, { continued: true })
|
|
74
|
+
.font('Helvetica').text(item.value);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 5. ШАБЛОН: АКТ (ACT) - Официальное подтверждение
|
|
79
|
+
private static drawAct(doc: any, data: any, details: any[]) {
|
|
80
|
+
doc.fontSize(14).text('АКТ ВЫПОЛНЕННЫХ РАБОТ', { align: 'center' }).moveDown();
|
|
81
|
+
doc.fontSize(10).text(`Мы, нижеподписавшиеся, составили настоящий акт о том, что услуги выполнены полностью...`).moveDown();
|
|
82
|
+
this.drawTable(doc, ['Услуга', 'Срок', 'Результат', 'Статус'], details);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private static drawGeneric(doc: any, data: any) {
|
|
86
|
+
doc.text(data.content || 'Пустой документ');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private static drawTable(doc: any, headers: string[], rows: any[]) {
|
|
90
|
+
const startX = 50;
|
|
91
|
+
let currentY = doc.y;
|
|
92
|
+
|
|
93
|
+
// Заголовок таблицы
|
|
94
|
+
doc.font('Helvetica-Bold').fontSize(10);
|
|
95
|
+
headers.forEach((h, i) => doc.text(h, startX + (i * 120), currentY));
|
|
96
|
+
doc.moveTo(startX, currentY + 15).lineTo(550, currentY + 15).stroke();
|
|
97
|
+
|
|
98
|
+
// Строки
|
|
99
|
+
doc.font('Helvetica').moveDown(1.5);
|
|
100
|
+
rows.forEach(row => {
|
|
101
|
+
const rowValues = Object.values(row);
|
|
102
|
+
const y = doc.y;
|
|
103
|
+
rowValues.forEach((val: any, i) => doc.text(String(val), startX + (i * 120), y));
|
|
104
|
+
doc.moveDown();
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private static drawFooter(doc: any, data: any) {
|
|
109
|
+
doc.fontSize(8).fillColor('#999').text('Сгенерировано автоматически системой n8n', 50, 780, { align: 'center' });
|
|
110
|
+
}
|
|
111
|
+
}
|
package/src/types.ts
ADDED
package/tsconfig.json
ADDED