n8n-nodes-contract-lite 4.0.3 → 4.0.4
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/ContractGenerator.node.js +37 -40
- package/dist/DocumentEngine.d.ts +3 -2
- package/dist/DocumentEngine.js +146 -112
- package/package.json +1 -1
|
@@ -5,12 +5,12 @@ const DocumentEngine_1 = require("./DocumentEngine");
|
|
|
5
5
|
class ContractGenerator {
|
|
6
6
|
constructor() {
|
|
7
7
|
this.description = {
|
|
8
|
-
displayName: 'Document Generator Elite',
|
|
8
|
+
displayName: 'Document Generator Pro (Elite)',
|
|
9
9
|
name: 'documentGeneratorElite',
|
|
10
10
|
icon: 'fa:file-signature',
|
|
11
11
|
group: ['transform'],
|
|
12
12
|
version: 1,
|
|
13
|
-
description: '
|
|
13
|
+
description: 'Премиальная генерация документов с исправленной логикой сторон',
|
|
14
14
|
defaults: { name: 'Генератор документов' },
|
|
15
15
|
inputs: ['main'],
|
|
16
16
|
outputs: ['main'],
|
|
@@ -22,55 +22,55 @@ class ContractGenerator {
|
|
|
22
22
|
options: [
|
|
23
23
|
{ name: 'Счет на оплату', value: 'invoice' },
|
|
24
24
|
{ name: 'Акт выполненных работ', value: 'act' },
|
|
25
|
-
{ name: 'Договор
|
|
26
|
-
{ name: 'Счет-оферта', value: 'offer' },
|
|
25
|
+
{ name: 'Договор', value: 'contract' },
|
|
27
26
|
],
|
|
28
27
|
default: 'invoice',
|
|
29
28
|
},
|
|
30
29
|
{
|
|
31
|
-
displayName: '
|
|
30
|
+
displayName: 'Тема оформления',
|
|
32
31
|
name: 'presetTheme',
|
|
33
32
|
type: 'options',
|
|
34
33
|
options: [
|
|
35
|
-
{ name: '
|
|
36
|
-
{ name: '
|
|
37
|
-
{ name: 'Minimal (
|
|
34
|
+
{ name: 'Modern (Чистый синий)', value: 'modern' },
|
|
35
|
+
{ name: 'Accent (С боковой полосой)', value: 'accent' },
|
|
36
|
+
{ name: 'Minimal (Классика)', value: 'minimal' },
|
|
38
37
|
],
|
|
39
38
|
default: 'modern',
|
|
40
39
|
},
|
|
41
|
-
{ displayName: 'Цвет
|
|
40
|
+
{ displayName: 'Цвет бренда', name: 'headerColor', type: 'color', default: '#2563eb' },
|
|
42
41
|
{
|
|
43
|
-
displayName: '
|
|
44
|
-
name: '
|
|
42
|
+
displayName: 'Данные ПОСТАВЩИКА (Ваши)',
|
|
43
|
+
name: 'supplierData',
|
|
45
44
|
type: 'collection',
|
|
46
|
-
placeholder: '
|
|
45
|
+
placeholder: 'Ваши реквизиты',
|
|
47
46
|
default: {},
|
|
48
47
|
options: [
|
|
49
|
-
{ displayName: '
|
|
50
|
-
{ displayName: '
|
|
51
|
-
{ displayName: '
|
|
52
|
-
{ displayName: '
|
|
48
|
+
{ displayName: 'Название компании', name: 'companyName', type: 'string', default: 'ООО КНК ГРУПП' },
|
|
49
|
+
{ displayName: 'ИНН', name: 'inn', type: 'string', default: '7701234567' },
|
|
50
|
+
{ displayName: 'КПП', name: 'kpp', type: 'string', default: '770101001' },
|
|
51
|
+
{ displayName: 'Банк', name: 'bankName', type: 'string', default: 'ПАО СБЕРБАНК' },
|
|
52
|
+
{ displayName: 'БИК', name: 'bik', type: 'string', default: '044525225' },
|
|
53
|
+
{ displayName: 'Расчетный счет', name: 'account', type: 'string', default: '40702810000000000000' },
|
|
54
|
+
{ displayName: 'Корр. счет', name: 'corrAccount', type: 'string', default: '30101810400000000225' },
|
|
53
55
|
]
|
|
54
56
|
},
|
|
55
57
|
{
|
|
56
|
-
displayName: '
|
|
57
|
-
name: '
|
|
58
|
+
displayName: 'Данные ПОКУПАТЕЛЯ (Клиент)',
|
|
59
|
+
name: 'clientData',
|
|
58
60
|
type: 'collection',
|
|
59
|
-
placeholder: '
|
|
61
|
+
placeholder: 'Реквизиты клиента',
|
|
60
62
|
default: {},
|
|
61
63
|
options: [
|
|
62
|
-
{ displayName: 'Название
|
|
63
|
-
{ displayName: '
|
|
64
|
-
{ displayName: 'Расчетный счет', name: 'account', type: 'string', default: '40702810000000000000' },
|
|
65
|
-
{ displayName: 'Корр. счет', name: 'corrAccount', type: 'string', default: '30101810400000000225' },
|
|
66
|
-
{ displayName: 'ИНН', name: 'inn', type: 'string', default: '7701234567' },
|
|
67
|
-
{ displayName: 'КПП', name: 'kpp', type: 'string', default: '770101001' },
|
|
64
|
+
{ displayName: 'ФИО / Название', name: 'clientName', type: 'string', default: 'ИП Иванов Иван' },
|
|
65
|
+
{ displayName: 'Адрес', name: 'clientAddress', type: 'string', default: 'г. Москва, ул. Ленина 1' },
|
|
68
66
|
]
|
|
69
67
|
},
|
|
70
|
-
{ displayName: '
|
|
71
|
-
{ displayName: '
|
|
72
|
-
{ displayName: '
|
|
73
|
-
{ displayName: 'Текст
|
|
68
|
+
{ displayName: 'Номер документа', name: 'docNumber', type: 'string', default: '101' },
|
|
69
|
+
{ displayName: 'Дата', name: 'date', type: 'string', default: new Date().toLocaleDateString('ru-RU') },
|
|
70
|
+
{ displayName: 'Таблица товаров (JSON)', name: 'details', type: 'json', default: '[{"№": 1, "Товар": "Услуги консалтинга", "Кол": 1, "Цена": 15000, "Сумма": 15000}]' },
|
|
71
|
+
{ displayName: 'Доп. условия (Текст)', name: 'content', type: 'string', typeOptions: { rows: 3 }, default: 'Оплата счета означает согласие с условиями.' },
|
|
72
|
+
{ displayName: 'Показать штамп', name: 'showStamp', type: 'boolean', default: true },
|
|
73
|
+
{ displayName: 'Текст штампа', name: 'status', type: 'string', default: 'ОПЛАЧЕНО', displayOptions: { show: { showStamp: [true] } } },
|
|
74
74
|
],
|
|
75
75
|
};
|
|
76
76
|
}
|
|
@@ -79,20 +79,17 @@ class ContractGenerator {
|
|
|
79
79
|
const returnData = [];
|
|
80
80
|
for (let i = 0; i < items.length; i++) {
|
|
81
81
|
const templateType = this.getNodeParameter('templateType', i);
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const titles = {
|
|
85
|
-
invoice: 'СЧЕТ НА ОПЛАТУ',
|
|
86
|
-
act: 'АКТ ВЫПОЛНЕННЫХ РАБОТ',
|
|
87
|
-
contract: 'ДОГОВОР',
|
|
88
|
-
offer: 'СЧЕТ-ОФЕРТА'
|
|
89
|
-
};
|
|
82
|
+
const supplier = this.getNodeParameter('supplierData', i);
|
|
83
|
+
const client = this.getNodeParameter('clientData', i);
|
|
84
|
+
const titles = { invoice: 'СЧЕТ НА ОПЛАТУ', act: 'АКТ ПРИЕМКИ-СДАЧИ', contract: 'ДОГОВОР' };
|
|
90
85
|
const data = {
|
|
91
|
-
...
|
|
92
|
-
...
|
|
86
|
+
...supplier,
|
|
87
|
+
...client,
|
|
93
88
|
title: titles[templateType],
|
|
94
|
-
|
|
89
|
+
docNumber: this.getNodeParameter('docNumber', i),
|
|
90
|
+
date: this.getNodeParameter('date', i),
|
|
95
91
|
details: this.getNodeParameter('details', i),
|
|
92
|
+
content: this.getNodeParameter('content', i),
|
|
96
93
|
showStamp: this.getNodeParameter('showStamp', i),
|
|
97
94
|
status: this.getNodeParameter('status', i, 'ОПЛАЧЕНО'),
|
|
98
95
|
};
|
package/dist/DocumentEngine.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export declare class DocumentEngine {
|
|
2
2
|
static createPDF(data: any, settings: any): Promise<Buffer>;
|
|
3
|
-
private static
|
|
4
|
-
private static
|
|
3
|
+
private static drawTemplateDecor;
|
|
4
|
+
private static drawBankGrid;
|
|
5
5
|
private static drawParties;
|
|
6
6
|
private static drawTable;
|
|
7
7
|
private static drawTotals;
|
|
8
8
|
private static drawSignatures;
|
|
9
|
+
private static drawRealisticStamp;
|
|
9
10
|
}
|
package/dist/DocumentEngine.js
CHANGED
|
@@ -6,157 +6,191 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.DocumentEngine = void 0;
|
|
7
7
|
const pdfkit_1 = __importDefault(require("pdfkit"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const fs_1 = __importDefault(require("fs"));
|
|
10
9
|
class DocumentEngine {
|
|
11
10
|
static async createPDF(data, settings) {
|
|
12
11
|
return new Promise((resolve, reject) => {
|
|
13
|
-
const doc = new pdfkit_1.default({
|
|
12
|
+
const doc = new pdfkit_1.default({
|
|
13
|
+
size: 'A4',
|
|
14
|
+
margin: 0, // Управляем полями вручную для точности дизайна
|
|
15
|
+
bufferPages: true
|
|
16
|
+
});
|
|
14
17
|
const chunks = [];
|
|
15
18
|
doc.on('data', chunk => chunks.push(chunk));
|
|
16
19
|
doc.on('end', () => resolve(Buffer.concat(chunks)));
|
|
17
20
|
doc.on('error', reject);
|
|
18
|
-
// РЕГИСТРАЦИЯ ШРИФТОВ (Решает проблему иероглифов)
|
|
19
21
|
const fontPath = path_1.default.join(__dirname, 'Roboto-Regular.ttf');
|
|
20
22
|
const fontBoldPath = path_1.default.join(__dirname, 'Roboto-Bold.ttf');
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
doc.registerFont('Bold', fontBoldPath);
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
console.warn("Шрифт Roboto не найден по пути:", fontPath);
|
|
27
|
-
}
|
|
28
|
-
const accent = settings.headerColor || '#000000';
|
|
29
|
-
// ПРИМЕНЯЕМ ШРИФТ СРАЗУ
|
|
23
|
+
doc.registerFont('Main', fontPath);
|
|
24
|
+
doc.registerFont('Bold', fontBoldPath);
|
|
30
25
|
doc.font('Main');
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
doc.font('Bold').fontSize(18).fillColor('#000').text(`${data.title} № ${data.docNumber || 'б/н'} от ${data.date}`, { align: 'left' });
|
|
40
|
-
doc.moveDown(1);
|
|
41
|
-
doc.rect(doc.x, doc.y, 515, 2).fill(accent);
|
|
42
|
-
doc.moveDown(1);
|
|
43
|
-
// 4. СТОРОНЫ (ОТПРАВИТЕЛЬ / ПОЛУЧАТЕЛЬ)
|
|
44
|
-
this.drawParties(doc, data);
|
|
45
|
-
// 5. ТАБЛИЦА С ДАННЫМИ
|
|
46
|
-
let details = [];
|
|
47
|
-
try {
|
|
48
|
-
details = typeof data.details === 'string' ? JSON.parse(data.details) : data.details;
|
|
49
|
-
}
|
|
50
|
-
catch (e) {
|
|
51
|
-
details = [];
|
|
52
|
-
}
|
|
53
|
-
if (Array.isArray(details) && details.length > 0) {
|
|
54
|
-
this.drawTable(doc, details, accent, settings.presetTheme);
|
|
26
|
+
const accent = settings.headerColor || '#2563eb';
|
|
27
|
+
const theme = settings.presetTheme || 'modern';
|
|
28
|
+
// 1. ФОН И СЕТКА
|
|
29
|
+
this.drawTemplateDecor(doc, theme, accent);
|
|
30
|
+
// 2. ШАПКА: БАНКОВСКИЕ РЕКВИЗИТЫ (в стиле 1С, но чище)
|
|
31
|
+
let currentY = 50;
|
|
32
|
+
if (settings.templateType !== 'contract') {
|
|
33
|
+
currentY = this.drawBankGrid(doc, data, 50, 40);
|
|
55
34
|
}
|
|
35
|
+
// 3. ЗАГОЛОВОК
|
|
36
|
+
doc.y = currentY + 30;
|
|
37
|
+
doc.x = 40;
|
|
38
|
+
doc.font('Bold').fontSize(20).fillColor('#111827').text(`${data.title} № ${data.docNumber}`, { continued: true });
|
|
39
|
+
doc.font('Main').fontSize(14).fillColor('#6b7280').text(` от ${data.date}`);
|
|
40
|
+
doc.moveDown(0.5);
|
|
41
|
+
doc.rect(40, doc.y, 515, 1.5).fill(accent);
|
|
42
|
+
doc.moveDown(1.5);
|
|
43
|
+
// 4. СТОРОНЫ (БЛОКИ ПОСТАВЩИК / ПОКУПАТЕЛЬ)
|
|
44
|
+
this.drawParties(doc, data, accent);
|
|
45
|
+
// 5. ТАБЛИЦА ТОВАРОВ
|
|
46
|
+
this.drawTable(doc, data, accent);
|
|
56
47
|
// 6. ИТОГИ
|
|
57
|
-
this.drawTotals(doc,
|
|
58
|
-
// 7.
|
|
48
|
+
this.drawTotals(doc, data);
|
|
49
|
+
// 7. УСЛОВИЯ
|
|
59
50
|
if (data.content) {
|
|
60
51
|
doc.moveDown(2);
|
|
61
|
-
doc.
|
|
62
|
-
doc.font('
|
|
52
|
+
doc.x = 40;
|
|
53
|
+
doc.font('Bold').fontSize(9).fillColor(accent).text('ДОПОЛНИТЕЛЬНЫЕ УСЛОВИЯ');
|
|
54
|
+
doc.font('Main').fontSize(9).fillColor('#374151').text(data.content, { width: 400, lineGap: 2 });
|
|
63
55
|
}
|
|
64
|
-
// 8.
|
|
56
|
+
// 8. ПОДПИСИ И ШТАМП
|
|
65
57
|
this.drawSignatures(doc, data, accent);
|
|
66
58
|
doc.end();
|
|
67
59
|
});
|
|
68
60
|
}
|
|
69
|
-
static
|
|
70
|
-
if (theme === '
|
|
71
|
-
doc.rect(0, 0,
|
|
61
|
+
static drawTemplateDecor(doc, theme, accent) {
|
|
62
|
+
if (theme === 'modern') {
|
|
63
|
+
doc.rect(0, 0, 595, 5).fill(accent);
|
|
72
64
|
}
|
|
73
|
-
else if (theme === '
|
|
74
|
-
doc.rect(
|
|
65
|
+
else if (theme === 'accent') {
|
|
66
|
+
doc.rect(0, 0, 30, 842).fill(accent);
|
|
75
67
|
}
|
|
76
68
|
}
|
|
77
|
-
static
|
|
78
|
-
doc.font('Main').fontSize(
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
doc.
|
|
83
|
-
|
|
84
|
-
doc.
|
|
85
|
-
doc.
|
|
86
|
-
doc.
|
|
87
|
-
doc.
|
|
88
|
-
|
|
89
|
-
doc.
|
|
90
|
-
doc.font('
|
|
91
|
-
doc.
|
|
92
|
-
doc.text(
|
|
93
|
-
doc.text('Сч. №', 305,
|
|
94
|
-
doc.font('Bold').text(data.
|
|
95
|
-
doc.
|
|
69
|
+
static drawBankGrid(doc, data, x, y) {
|
|
70
|
+
doc.font('Main').fontSize(7).fillColor('#9ca3af');
|
|
71
|
+
const width = 515;
|
|
72
|
+
const rowH = 15;
|
|
73
|
+
// Рисуем рамку сетки
|
|
74
|
+
doc.rect(x, y, width, 55).strokeColor('#e5e7eb').lineWidth(0.5).stroke();
|
|
75
|
+
// Линии
|
|
76
|
+
doc.moveTo(x, y + 35).lineTo(x + width, y + 35).stroke(); // Горизонтальная
|
|
77
|
+
doc.moveTo(x + 300, y).lineTo(x + 300, y + 55).stroke(); // Вертикальная
|
|
78
|
+
doc.moveTo(x + 300, y + rowH).lineTo(x + width, y + rowH).stroke();
|
|
79
|
+
doc.moveTo(x + 300, y + rowH * 2).lineTo(x + width, y + rowH * 2).stroke();
|
|
80
|
+
// Текст
|
|
81
|
+
doc.fillColor('#6b7280').text('БАНК ПОЛУЧАТЕЛЯ', x + 5, y + 5);
|
|
82
|
+
doc.font('Bold').fillColor('#111827').fontSize(9).text(data.bankName || 'ПАО СБЕРБАНК', x + 5, y + 15);
|
|
83
|
+
doc.font('Main').fillColor('#6b7280').fontSize(7).text('БИК', x + 305, y + 4);
|
|
84
|
+
doc.font('Bold').fillColor('#111827').fontSize(9).text(data.bik || '044525225', x + 340, y + 4);
|
|
85
|
+
doc.font('Main').fillColor('#6b7280').text('Сч. №', x + 305, y + 19);
|
|
86
|
+
doc.font('Bold').text(data.corrAccount || '30101810400000000225', x + 340, y + 19);
|
|
87
|
+
doc.font('Main').fillColor('#6b7280').text('ИНН ' + data.inn, x + 5, y + 38);
|
|
88
|
+
doc.text('КПП ' + data.kpp, x + 120, y + 38);
|
|
89
|
+
doc.text('Сч. №', x + 305, y + 38);
|
|
90
|
+
doc.font('Bold').text(data.account || '40702810000000001234', x + 340, y + 38);
|
|
91
|
+
doc.font('Main').fillColor('#6b7280').text('ПОЛУЧАТЕЛЬ', x + 5, y + 46);
|
|
92
|
+
doc.font('Bold').fillColor('#111827').text(data.companyName, x + 70, y + 46);
|
|
93
|
+
return y + 55;
|
|
96
94
|
}
|
|
97
|
-
static drawParties(doc, data) {
|
|
95
|
+
static drawParties(doc, data, accent) {
|
|
98
96
|
const y = doc.y;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
doc.font('Bold').fillColor(
|
|
102
|
-
doc.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
doc.font('Bold').fillColor(
|
|
106
|
-
doc.
|
|
97
|
+
const width = 250;
|
|
98
|
+
// Поставщик
|
|
99
|
+
doc.font('Bold').fontSize(8).fillColor(accent).text('ПОСТАВЩИК (ИСПОЛНИТЕЛЬ)', 40, y);
|
|
100
|
+
doc.font('Bold').fontSize(10).fillColor('#111827').text(data.companyName, 40, y + 12, { width });
|
|
101
|
+
doc.font('Main').fontSize(8).fillColor('#6b7280').text(`ИНН: ${data.inn}, ${data.address || 'Москва, Россия'}`, 40, y + 25, { width });
|
|
102
|
+
// Покупатель
|
|
103
|
+
doc.font('Bold').fontSize(8).fillColor(accent).text('ПОКУПАТЕЛЬ (ЗАКАЗЧИК)', 305, y);
|
|
104
|
+
doc.font('Bold').fontSize(10).fillColor('#111827').text(data.clientName, 305, y + 12, { width });
|
|
105
|
+
doc.font('Main').fontSize(8).fillColor('#6b7280').text('Юридический адрес клиента', 305, y + 25, { width });
|
|
106
|
+
doc.y = y + 50;
|
|
107
107
|
}
|
|
108
|
-
static drawTable(doc,
|
|
109
|
-
let
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
static drawTable(doc, data, accent) {
|
|
109
|
+
let details = [];
|
|
110
|
+
try {
|
|
111
|
+
details = typeof data.details === 'string' ? JSON.parse(data.details) : data.details;
|
|
112
|
+
}
|
|
113
|
+
catch (e) { }
|
|
114
|
+
let y = doc.y + 10;
|
|
115
|
+
const x = 40;
|
|
116
|
+
const colWidths = [30, 280, 50, 75, 80]; // №, Товар, Кол-во, Цена, Сумма
|
|
117
|
+
const headers = ['№', 'НАИМЕНОВАНИЕ ТОВАРОВ / УСЛУГ', 'КОЛ-ВО', 'ЦЕНА', 'СУММА'];
|
|
118
|
+
// Заголовок таблицы
|
|
119
|
+
doc.rect(x, y, 515, 20).fill(accent);
|
|
120
|
+
doc.font('Bold').fontSize(7).fillColor('#ffffff');
|
|
121
|
+
let currentX = x;
|
|
116
122
|
headers.forEach((h, i) => {
|
|
117
|
-
doc.text(h
|
|
123
|
+
doc.text(h, currentX, y + 7, { width: colWidths[i], align: i > 1 ? 'right' : 'center' });
|
|
124
|
+
currentX += colWidths[i];
|
|
118
125
|
});
|
|
126
|
+
// Строки
|
|
119
127
|
y += 20;
|
|
120
|
-
doc.font('Main').fontSize(8).fillColor('#
|
|
121
|
-
|
|
122
|
-
const
|
|
123
|
-
const rowHeight =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
+
doc.font('Main').fontSize(8).fillColor('#374151');
|
|
129
|
+
details.forEach((row, i) => {
|
|
130
|
+
const rowValues = Object.values(row);
|
|
131
|
+
const rowHeight = 22;
|
|
132
|
+
// Зебра
|
|
133
|
+
if (i % 2 === 0) {
|
|
134
|
+
doc.rect(x, y, 515, rowHeight).fill('#f9fafb');
|
|
135
|
+
}
|
|
136
|
+
doc.fillColor('#374151');
|
|
137
|
+
let itemX = x;
|
|
138
|
+
rowValues.forEach((val, j) => {
|
|
139
|
+
const align = j > 1 ? 'right' : (j === 0 ? 'center' : 'left');
|
|
140
|
+
const padding = j === 1 ? 10 : 0;
|
|
141
|
+
doc.text(String(val), itemX + padding, y + 7, { width: colWidths[j] - padding * 2, align });
|
|
142
|
+
itemX += colWidths[j];
|
|
128
143
|
});
|
|
129
144
|
y += rowHeight;
|
|
130
|
-
doc.moveTo(
|
|
145
|
+
doc.moveTo(x, y).lineTo(x + 515, y).strokeColor('#f3f4f6').lineWidth(0.5).stroke();
|
|
131
146
|
});
|
|
132
147
|
doc.y = y;
|
|
133
148
|
}
|
|
134
|
-
static drawTotals(doc,
|
|
135
|
-
|
|
149
|
+
static drawTotals(doc, data) {
|
|
150
|
+
let details = [];
|
|
151
|
+
try {
|
|
152
|
+
details = typeof data.details === 'string' ? JSON.parse(data.details) : data.details;
|
|
153
|
+
}
|
|
154
|
+
catch (e) { }
|
|
136
155
|
let total = 0;
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
156
|
+
details.forEach((item) => {
|
|
157
|
+
const values = Object.values(item);
|
|
158
|
+
const sum = parseFloat(String(values[values.length - 1]).replace(/\s/g, ''));
|
|
159
|
+
total += isNaN(sum) ? 0 : sum;
|
|
140
160
|
});
|
|
141
|
-
doc.
|
|
142
|
-
doc.
|
|
143
|
-
doc.font('Main').fontSize(
|
|
144
|
-
doc.
|
|
161
|
+
const y = doc.y + 15;
|
|
162
|
+
doc.x = 350;
|
|
163
|
+
doc.font('Main').fontSize(9).fillColor('#6b7280').text('Итого:', { continued: true });
|
|
164
|
+
doc.font('Bold').fillColor('#111827').text(` ${total.toLocaleString('ru-RU')} руб.`, { align: 'right', width: 200 });
|
|
165
|
+
doc.moveDown(0.3);
|
|
166
|
+
doc.font('Main').fontSize(10).fillColor('#6b7280').text('Всего к оплате:', { continued: true });
|
|
167
|
+
doc.font('Bold').fontSize(12).fillColor('#111827').text(` ${total.toLocaleString('ru-RU')} руб.`, { align: 'right', width: 200 });
|
|
145
168
|
}
|
|
146
169
|
static drawSignatures(doc, data, accent) {
|
|
147
|
-
const y =
|
|
148
|
-
doc.
|
|
149
|
-
doc.
|
|
150
|
-
doc.text('
|
|
170
|
+
const y = 720;
|
|
171
|
+
doc.rect(40, y - 20, 515, 0.5).fill('#e5e7eb');
|
|
172
|
+
doc.font('Bold').fontSize(8).fillColor('#111827');
|
|
173
|
+
doc.text('РУКОВОДИТЕЛЬ', 40, y);
|
|
174
|
+
doc.text('БУХГАЛТЕР', 305, y);
|
|
175
|
+
doc.moveTo(40, y + 25).lineTo(200, y + 25).strokeColor('#9ca3af').stroke();
|
|
176
|
+
doc.moveTo(305, y + 25).lineTo(465, y + 25).stroke();
|
|
177
|
+
doc.font('Main').fontSize(7).fillColor('#9ca3af').text('(Подпись / Расшифровка)', 40, y + 30);
|
|
178
|
+
doc.text('(Подпись / Расшифровка)', 305, y + 30);
|
|
151
179
|
if (data.showStamp) {
|
|
152
|
-
|
|
153
|
-
doc.save();
|
|
154
|
-
doc.rotate(-10, { origin: [150, y + 20] });
|
|
155
|
-
doc.rect(80, y - 10, 120, 50).lineWidth(2).strokeColor(accent).strokeOpacity(0.5).stroke();
|
|
156
|
-
doc.fillColor(accent).fillOpacity(0.5).fontSize(12).text(data.status, 80, y + 5, { width: 120, align: 'center' });
|
|
157
|
-
doc.fontSize(7).text(data.companyName, 80, y + 25, { width: 120, align: 'center' });
|
|
158
|
-
doc.restore();
|
|
180
|
+
this.drawRealisticStamp(doc, 130, y - 30, data.status, accent, data.companyName);
|
|
159
181
|
}
|
|
160
182
|
}
|
|
183
|
+
static drawRealisticStamp(doc, x, y, text, color, company) {
|
|
184
|
+
doc.save();
|
|
185
|
+
doc.rotate(-12, { origin: [x + 60, y + 30] });
|
|
186
|
+
// Внешняя рамка
|
|
187
|
+
doc.rect(x, y, 130, 60).lineWidth(1.5).strokeColor(color).strokeOpacity(0.4).stroke();
|
|
188
|
+
// Внутренняя рамка
|
|
189
|
+
doc.rect(x + 3, y + 3, 124, 54).lineWidth(0.5).stroke();
|
|
190
|
+
doc.fillColor(color).fillOpacity(0.5);
|
|
191
|
+
doc.font('Bold').fontSize(14).text(text, x, y + 15, { width: 130, align: 'center' });
|
|
192
|
+
doc.font('Main').fontSize(7).text(company, x, y + 35, { width: 130, align: 'center' });
|
|
193
|
+
doc.restore();
|
|
194
|
+
}
|
|
161
195
|
}
|
|
162
196
|
exports.DocumentEngine = DocumentEngine;
|