vv-iiko-pp-parser 1.0.6
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/CHANGELOG.md +151 -0
- package/README.md +582 -0
- package/dist/clients/clients.fetcher.d.ts +102 -0
- package/dist/clients/clients.fetcher.d.ts.map +1 -0
- package/dist/clients/clients.fetcher.js +196 -0
- package/dist/clients/clients.fetcher.js.map +1 -0
- package/dist/clients/clients.parser.d.ts +144 -0
- package/dist/clients/clients.parser.d.ts.map +1 -0
- package/dist/clients/clients.parser.js +473 -0
- package/dist/clients/clients.parser.js.map +1 -0
- package/dist/clients/clients.service.d.ts +90 -0
- package/dist/clients/clients.service.d.ts.map +1 -0
- package/dist/clients/clients.service.js +277 -0
- package/dist/clients/clients.service.js.map +1 -0
- package/dist/clients/clients.types.d.ts +127 -0
- package/dist/clients/clients.types.d.ts.map +1 -0
- package/dist/clients/clients.types.js +6 -0
- package/dist/clients/clients.types.js.map +1 -0
- package/dist/clients/index.d.ts +5 -0
- package/dist/clients/index.d.ts.map +1 -0
- package/dist/clients/index.js +25 -0
- package/dist/clients/index.js.map +1 -0
- package/dist/core/auth/iiko-auth.service.d.ts +52 -0
- package/dist/core/auth/iiko-auth.service.d.ts.map +1 -0
- package/dist/core/auth/iiko-auth.service.js +302 -0
- package/dist/core/auth/iiko-auth.service.js.map +1 -0
- package/dist/core/http/iiko-http.client.d.ts +13 -0
- package/dist/core/http/iiko-http.client.d.ts.map +1 -0
- package/dist/core/http/iiko-http.client.js +55 -0
- package/dist/core/http/iiko-http.client.js.map +1 -0
- package/dist/core/iiko-parser.config.d.ts +28 -0
- package/dist/core/iiko-parser.config.d.ts.map +1 -0
- package/dist/core/iiko-parser.config.js +76 -0
- package/dist/core/iiko-parser.config.js.map +1 -0
- package/dist/core/parser/iiko-parser.service.d.ts +15 -0
- package/dist/core/parser/iiko-parser.service.d.ts.map +1 -0
- package/dist/core/parser/iiko-parser.service.js +156 -0
- package/dist/core/parser/iiko-parser.service.js.map +1 -0
- package/dist/core/session/iiko-session.service.d.ts +68 -0
- package/dist/core/session/iiko-session.service.d.ts.map +1 -0
- package/dist/core/session/iiko-session.service.js +209 -0
- package/dist/core/session/iiko-session.service.js.map +1 -0
- package/dist/iiko-parser.d.ts +149 -0
- package/dist/iiko-parser.d.ts.map +1 -0
- package/dist/iiko-parser.js +209 -0
- package/dist/iiko-parser.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/company-profile.interface.d.ts +12 -0
- package/dist/interfaces/company-profile.interface.d.ts.map +1 -0
- package/dist/interfaces/company-profile.interface.js +3 -0
- package/dist/interfaces/company-profile.interface.js.map +1 -0
- package/dist/invoices/index.d.ts +6 -0
- package/dist/invoices/index.d.ts.map +1 -0
- package/dist/invoices/index.js +22 -0
- package/dist/invoices/index.js.map +1 -0
- package/dist/invoices/invoices.fetcher.d.ts +49 -0
- package/dist/invoices/invoices.fetcher.d.ts.map +1 -0
- package/dist/invoices/invoices.fetcher.js +141 -0
- package/dist/invoices/invoices.fetcher.js.map +1 -0
- package/dist/invoices/invoices.filters.d.ts +40 -0
- package/dist/invoices/invoices.filters.d.ts.map +1 -0
- package/dist/invoices/invoices.filters.js +81 -0
- package/dist/invoices/invoices.filters.js.map +1 -0
- package/dist/invoices/invoices.parser.d.ts +101 -0
- package/dist/invoices/invoices.parser.d.ts.map +1 -0
- package/dist/invoices/invoices.parser.js +540 -0
- package/dist/invoices/invoices.parser.js.map +1 -0
- package/dist/invoices/invoices.service.d.ts +82 -0
- package/dist/invoices/invoices.service.d.ts.map +1 -0
- package/dist/invoices/invoices.service.js +361 -0
- package/dist/invoices/invoices.service.js.map +1 -0
- package/dist/invoices/invoices.storage.service.d.ts +30 -0
- package/dist/invoices/invoices.storage.service.d.ts.map +1 -0
- package/dist/invoices/invoices.storage.service.js +135 -0
- package/dist/invoices/invoices.storage.service.js.map +1 -0
- package/dist/invoices/invoices.types.d.ts +94 -0
- package/dist/invoices/invoices.types.d.ts.map +1 -0
- package/dist/invoices/invoices.types.js +3 -0
- package/dist/invoices/invoices.types.js.map +1 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as cheerio from 'cheerio';
|
|
2
|
+
import { InvoiceTableData, InvoiceListItem, InvoiceDetails } from './invoices.types';
|
|
3
|
+
import { InvoiceData, InvoiceItem as PublicInvoiceItem, Subscription } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Сервис для парсинга HTML страниц iiko Partner Portal.
|
|
6
|
+
*
|
|
7
|
+
* Использует библиотеку cheerio для извлечения данных из HTML:
|
|
8
|
+
* - Таблица инвойсов из AJAX endpoint
|
|
9
|
+
* - Детали инвойса из модального окна (items, subscriptions)
|
|
10
|
+
*
|
|
11
|
+
* Выполняет нормализацию данных:
|
|
12
|
+
* - Удаление пробелов и символов валюты
|
|
13
|
+
* - Конвертация строковых чисел в тип number
|
|
14
|
+
* - Извлечение только цифр из tax_id
|
|
15
|
+
*/
|
|
16
|
+
export declare class InvoicesParser {
|
|
17
|
+
/**
|
|
18
|
+
* Извлекает только цифры из строки.
|
|
19
|
+
*
|
|
20
|
+
* Используется для нормализации tax_id, удаляя пробелы и другие символы.
|
|
21
|
+
*
|
|
22
|
+
* @param text - Исходная строка (например: "305 467 088")
|
|
23
|
+
* @returns Строка только с цифрами (например: "305467088")
|
|
24
|
+
*/
|
|
25
|
+
private extractNumbers;
|
|
26
|
+
/**
|
|
27
|
+
* Извлекает число из строки с валютой и другими символами.
|
|
28
|
+
*
|
|
29
|
+
* Удаляет символы валюты ($, ₽), пробелы и другие нечисловые символы.
|
|
30
|
+
* Конвертирует результат в число.
|
|
31
|
+
*
|
|
32
|
+
* @param text - Строка с числом и символами (например: "243 $", "1,234.56")
|
|
33
|
+
* @returns Число без символов (например: 243, 1234.56)
|
|
34
|
+
*/
|
|
35
|
+
private extractAmount;
|
|
36
|
+
/**
|
|
37
|
+
* Парсит одну строку таблицы инвойсов в нормализованную структуру.
|
|
38
|
+
*
|
|
39
|
+
* Извлекает данные из HTML колонок таблицы по их позиции (nth-child).
|
|
40
|
+
* Выполняет нормализацию для tax_id (только цифры) и amount (число).
|
|
41
|
+
*
|
|
42
|
+
* @param $ - Cheerio API для работы с HTML
|
|
43
|
+
* @param row - HTML элемент строки таблицы (<tr>)
|
|
44
|
+
* @returns Объект InvoiceData или null если строка невалидна
|
|
45
|
+
*/
|
|
46
|
+
parseInvoiceRow($: cheerio.CheerioAPI, row: any): InvoiceData | null;
|
|
47
|
+
/**
|
|
48
|
+
* Парсит HTML модального окна с деталями инвойса.
|
|
49
|
+
*
|
|
50
|
+
* Извлекает две таблицы из модального окна:
|
|
51
|
+
* - #tabInvoiceInfo — элементы инвойса (items)
|
|
52
|
+
* - #tabSubscriptionInfo — подписки (subscriptions)
|
|
53
|
+
*
|
|
54
|
+
* Все числовые поля нормализуются (цена, количество, скидка, сумма).
|
|
55
|
+
*
|
|
56
|
+
* @param html - HTML строка модального окна invoice-info.html
|
|
57
|
+
* @returns Объект с массивами items и subscriptions
|
|
58
|
+
*/
|
|
59
|
+
parseInvoiceModal(html: string): {
|
|
60
|
+
items: PublicInvoiceItem[];
|
|
61
|
+
subscriptions: Subscription[];
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Парсит HTML таблицу инвойсов и возвращает нормализованные данные.
|
|
65
|
+
*
|
|
66
|
+
* Основной метод для парсинга страницы со списком инвойсов.
|
|
67
|
+
* Проходит по всем строкам таблицы (<tr>) и для каждой вызывает parseInvoiceRow().
|
|
68
|
+
*
|
|
69
|
+
* Массивы items и subscriptions остаются пустыми — они заполняются
|
|
70
|
+
* при вызове enrichInvoicesWithDetails() из InvoicesService.
|
|
71
|
+
*
|
|
72
|
+
* @param html - HTML строка с таблицей инвойсов из AJAX endpoint
|
|
73
|
+
* @returns Массив объектов InvoiceData с заполненными основными полями
|
|
74
|
+
*/
|
|
75
|
+
parseInvoicesTableNormalized(html: string): InvoiceData[];
|
|
76
|
+
/**
|
|
77
|
+
* Парсит HTML таблицу инвойсов с помощью cheerio (старый метод для обратной совместимости)
|
|
78
|
+
*/
|
|
79
|
+
parseInvoicesTable(html: string): InvoiceTableData;
|
|
80
|
+
/**
|
|
81
|
+
* Парсит список инвойсов и извлекает данные + ID для модалки
|
|
82
|
+
*/
|
|
83
|
+
parseInvoicesList(html: string): InvoiceListItem[];
|
|
84
|
+
/**
|
|
85
|
+
* Парсит детальную информацию из модального окна
|
|
86
|
+
*/
|
|
87
|
+
parseInvoiceDetails(html: string): InvoiceDetails;
|
|
88
|
+
/**
|
|
89
|
+
* Парсит модальное окно "Invoice Information" и извлекает items
|
|
90
|
+
*/
|
|
91
|
+
parseInvoiceItems(html: string): PublicInvoiceItem[];
|
|
92
|
+
/**
|
|
93
|
+
* Парсит модальное окно "Subscription Info" и извлекает subscriptions
|
|
94
|
+
*/
|
|
95
|
+
parseSubscriptions(html: string): Subscription[];
|
|
96
|
+
/**
|
|
97
|
+
* Выводит таблицу инвойсов в читаемом формате
|
|
98
|
+
*/
|
|
99
|
+
logInvoicesTable(data: InvoiceTableData): void;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=invoices.parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoices.parser.d.ts","sourceRoot":"","sources":["../../src/invoices/invoices.parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,EAEL,gBAAgB,EAChB,eAAe,EACf,cAAc,EAEf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,WAAW,IAAI,iBAAiB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEvF;;;;;;;;;;;GAWG;AACH,qBAAa,cAAc;IAEzB;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAItB;;;;;;;;OAQG;IACH,OAAO,CAAC,aAAa;IAKrB;;;;;;;;;OASG;IACH,eAAe,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,GAAG,WAAW,GAAG,IAAI;IA2DpE;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG;QAC/B,KAAK,EAAE,iBAAiB,EAAE,CAAC;QAC3B,aAAa,EAAE,YAAY,EAAE,CAAC;KAC/B;IA4ED;;;;;;;;;;;OAWG;IACH,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE;IAuBzD;;OAEG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB;IAqDlD;;OAEG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,EAAE;IAuDlD;;OAEG;IACH,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc;IAyGjD;;OAEG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE;IA8CpD;;OAEG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE;IAmDhD;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI;CAwB/C"}
|
|
@@ -0,0 +1,540 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.InvoicesParser = void 0;
|
|
37
|
+
const cheerio = __importStar(require("cheerio"));
|
|
38
|
+
/**
|
|
39
|
+
* Сервис для парсинга HTML страниц iiko Partner Portal.
|
|
40
|
+
*
|
|
41
|
+
* Использует библиотеку cheerio для извлечения данных из HTML:
|
|
42
|
+
* - Таблица инвойсов из AJAX endpoint
|
|
43
|
+
* - Детали инвойса из модального окна (items, subscriptions)
|
|
44
|
+
*
|
|
45
|
+
* Выполняет нормализацию данных:
|
|
46
|
+
* - Удаление пробелов и символов валюты
|
|
47
|
+
* - Конвертация строковых чисел в тип number
|
|
48
|
+
* - Извлечение только цифр из tax_id
|
|
49
|
+
*/
|
|
50
|
+
class InvoicesParser {
|
|
51
|
+
/**
|
|
52
|
+
* Извлекает только цифры из строки.
|
|
53
|
+
*
|
|
54
|
+
* Используется для нормализации tax_id, удаляя пробелы и другие символы.
|
|
55
|
+
*
|
|
56
|
+
* @param text - Исходная строка (например: "305 467 088")
|
|
57
|
+
* @returns Строка только с цифрами (например: "305467088")
|
|
58
|
+
*/
|
|
59
|
+
extractNumbers(text) {
|
|
60
|
+
return text.replace(/[^\d]/g, '');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Извлекает число из строки с валютой и другими символами.
|
|
64
|
+
*
|
|
65
|
+
* Удаляет символы валюты ($, ₽), пробелы и другие нечисловые символы.
|
|
66
|
+
* Конвертирует результат в число.
|
|
67
|
+
*
|
|
68
|
+
* @param text - Строка с числом и символами (например: "243 $", "1,234.56")
|
|
69
|
+
* @returns Число без символов (например: 243, 1234.56)
|
|
70
|
+
*/
|
|
71
|
+
extractAmount(text) {
|
|
72
|
+
const cleaned = text.replace(/[^\d.,]/g, '').replace(',', '.');
|
|
73
|
+
return parseFloat(cleaned) || 0;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Парсит одну строку таблицы инвойсов в нормализованную структуру.
|
|
77
|
+
*
|
|
78
|
+
* Извлекает данные из HTML колонок таблицы по их позиции (nth-child).
|
|
79
|
+
* Выполняет нормализацию для tax_id (только цифры) и amount (число).
|
|
80
|
+
*
|
|
81
|
+
* @param $ - Cheerio API для работы с HTML
|
|
82
|
+
* @param row - HTML элемент строки таблицы (<tr>)
|
|
83
|
+
* @returns Объект InvoiceData или null если строка невалидна
|
|
84
|
+
*/
|
|
85
|
+
parseInvoiceRow($, row) {
|
|
86
|
+
try {
|
|
87
|
+
const $row = $(row);
|
|
88
|
+
// 1. Invoice Number - первая ссылка
|
|
89
|
+
const invoiceNumber = $row.find('td:nth-child(1) a').text().trim();
|
|
90
|
+
// 2. Invoice Date - 2-я колонка
|
|
91
|
+
const invoiceDate = $row.find('td:nth-child(2)').text().trim();
|
|
92
|
+
// 3. Due Date - 3-я колонка
|
|
93
|
+
const dueDate = $row.find('td:nth-child(3)').text().trim();
|
|
94
|
+
// 4. Client Name - ссылка в 5-й колонке
|
|
95
|
+
const clientName = $row.find('td:nth-child(5) a').text().trim();
|
|
96
|
+
// 5. Tax ID - извлекаем только цифры из текста "Company tax id..."
|
|
97
|
+
const taxIdText = $row.find('td:nth-child(6) p:first-child').text().trim();
|
|
98
|
+
const taxId = this.extractNumbers(taxIdText);
|
|
99
|
+
// 6. Company Name - второй <p> в 6-й колонке
|
|
100
|
+
const companyName = $row.find('td:nth-child(6) p:nth-child(2)').text().trim();
|
|
101
|
+
// 7. Status - 8-я колонка
|
|
102
|
+
const status = $row.find('td:nth-child(8)').text().trim();
|
|
103
|
+
// 8. Amount - последняя колонка, только цифры
|
|
104
|
+
const amountText = $row.find('td:last-child').text().trim();
|
|
105
|
+
const amount = this.extractAmount(amountText);
|
|
106
|
+
// 9. Details URL - ссылка на модальное окно с деталями
|
|
107
|
+
// Ищем ссылку с классом invoice-info-link во всей строке
|
|
108
|
+
const detailsLink = $row.find('a.invoice-info-link');
|
|
109
|
+
const detailsUrl = detailsLink.attr('href') || '';
|
|
110
|
+
// Проверяем что есть хотя бы номер инвойса
|
|
111
|
+
if (!invoiceNumber) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
invoiceNumber,
|
|
116
|
+
invoiceDate,
|
|
117
|
+
dueDate,
|
|
118
|
+
clientName,
|
|
119
|
+
companyName,
|
|
120
|
+
taxId,
|
|
121
|
+
status,
|
|
122
|
+
amount,
|
|
123
|
+
detailsUrl,
|
|
124
|
+
items: [], // Заполнится из модалки
|
|
125
|
+
subscriptions: [] // Заполнится из модалки
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error(`Ошибка парсинга строки инвойса:`, error);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Парсит HTML модального окна с деталями инвойса.
|
|
135
|
+
*
|
|
136
|
+
* Извлекает две таблицы из модального окна:
|
|
137
|
+
* - #tabInvoiceInfo — элементы инвойса (items)
|
|
138
|
+
* - #tabSubscriptionInfo — подписки (subscriptions)
|
|
139
|
+
*
|
|
140
|
+
* Все числовые поля нормализуются (цена, количество, скидка, сумма).
|
|
141
|
+
*
|
|
142
|
+
* @param html - HTML строка модального окна invoice-info.html
|
|
143
|
+
* @returns Объект с массивами items и subscriptions
|
|
144
|
+
*/
|
|
145
|
+
parseInvoiceModal(html) {
|
|
146
|
+
try {
|
|
147
|
+
const $ = cheerio.load(html);
|
|
148
|
+
const items = [];
|
|
149
|
+
const subscriptions = [];
|
|
150
|
+
// Парсим Invoice Information (items)
|
|
151
|
+
$('#tabInvoiceInfo table.table-striped tbody tr').each((index, row) => {
|
|
152
|
+
const $row = $(row);
|
|
153
|
+
// 2-й td - productName
|
|
154
|
+
const productName = $row.find('td:nth-child(2)').text().trim();
|
|
155
|
+
// 3-й td - price (только число)
|
|
156
|
+
const priceText = $row.find('td:nth-child(3)').text().trim();
|
|
157
|
+
const price = this.extractAmount(priceText);
|
|
158
|
+
// 4-й td - quantity
|
|
159
|
+
const quantityText = $row.find('td:nth-child(4)').text().trim();
|
|
160
|
+
const quantity = parseFloat(quantityText) || 0;
|
|
161
|
+
// 6-й td - discount (только число)
|
|
162
|
+
const discountText = $row.find('td:nth-child(6)').text().trim();
|
|
163
|
+
const discount = this.extractAmount(discountText);
|
|
164
|
+
// 7-й td - amount (только число)
|
|
165
|
+
const amountText = $row.find('td:nth-child(7)').text().trim();
|
|
166
|
+
const amount = this.extractAmount(amountText);
|
|
167
|
+
if (productName) {
|
|
168
|
+
items.push({
|
|
169
|
+
productName,
|
|
170
|
+
price,
|
|
171
|
+
quantity,
|
|
172
|
+
discount,
|
|
173
|
+
amount,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
// Парсим Subscription Info (subscriptions)
|
|
178
|
+
$('#tabSubscriptionInfo table.table-striped tbody tr').each((index, row) => {
|
|
179
|
+
const $row = $(row);
|
|
180
|
+
// 1-й td - productName
|
|
181
|
+
const productName = $row.find('td:nth-child(1)').text().trim();
|
|
182
|
+
// 2-й td - quantity
|
|
183
|
+
const quantityText = $row.find('td:nth-child(2)').text().trim();
|
|
184
|
+
const quantity = parseFloat(quantityText) || 0;
|
|
185
|
+
// 3-й td - price (число)
|
|
186
|
+
const priceText = $row.find('td:nth-child(3)').text().trim();
|
|
187
|
+
const price = this.extractAmount(priceText);
|
|
188
|
+
// 4-й td - amount (число)
|
|
189
|
+
const amountText = $row.find('td:nth-child(4)').text().trim();
|
|
190
|
+
const amount = this.extractAmount(amountText);
|
|
191
|
+
if (productName) {
|
|
192
|
+
subscriptions.push({
|
|
193
|
+
productName,
|
|
194
|
+
quantity,
|
|
195
|
+
price,
|
|
196
|
+
amount,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
return { items, subscriptions };
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
console.error(`❌ Ошибка при парсинге модального окна: ${error.message}`);
|
|
204
|
+
return { items: [], subscriptions: [] };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Парсит HTML таблицу инвойсов и возвращает нормализованные данные.
|
|
209
|
+
*
|
|
210
|
+
* Основной метод для парсинга страницы со списком инвойсов.
|
|
211
|
+
* Проходит по всем строкам таблицы (<tr>) и для каждой вызывает parseInvoiceRow().
|
|
212
|
+
*
|
|
213
|
+
* Массивы items и subscriptions остаются пустыми — они заполняются
|
|
214
|
+
* при вызове enrichInvoicesWithDetails() из InvoicesService.
|
|
215
|
+
*
|
|
216
|
+
* @param html - HTML строка с таблицей инвойсов из AJAX endpoint
|
|
217
|
+
* @returns Массив объектов InvoiceData с заполненными основными полями
|
|
218
|
+
*/
|
|
219
|
+
parseInvoicesTableNormalized(html) {
|
|
220
|
+
try {
|
|
221
|
+
console.log('🔍 Начинаем парсинг HTML таблицы инвойсов...');
|
|
222
|
+
const $ = cheerio.load(html);
|
|
223
|
+
const invoices = [];
|
|
224
|
+
// Парсим каждую строку таблицы
|
|
225
|
+
$('table tbody tr').each((index, row) => {
|
|
226
|
+
const invoice = this.parseInvoiceRow($, row);
|
|
227
|
+
if (invoice) {
|
|
228
|
+
invoices.push(invoice);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
console.log(`✅ Распарсено инвойсов: ${invoices.length}`);
|
|
232
|
+
return invoices;
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
console.error(`❌ Ошибка при парсинге таблицы: ${error.message}`);
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Парсит HTML таблицу инвойсов с помощью cheerio (старый метод для обратной совместимости)
|
|
241
|
+
*/
|
|
242
|
+
parseInvoicesTable(html) {
|
|
243
|
+
try {
|
|
244
|
+
console.log('🔍 Начинаем парсинг HTML таблицы...');
|
|
245
|
+
const $ = cheerio.load(html);
|
|
246
|
+
// Проверяем наличие таблицы
|
|
247
|
+
const table = $('table');
|
|
248
|
+
if (table.length === 0) {
|
|
249
|
+
throw new Error('HTML не содержит <table>');
|
|
250
|
+
}
|
|
251
|
+
// Извлекаем строки из tbody
|
|
252
|
+
const tbody = $('table tbody');
|
|
253
|
+
if (tbody.length === 0) {
|
|
254
|
+
console.warn('⚠️ Таблица не содержит <tbody>');
|
|
255
|
+
}
|
|
256
|
+
const rows = [];
|
|
257
|
+
// Парсим каждую строку
|
|
258
|
+
$('table tbody tr').each((index, row) => {
|
|
259
|
+
const columns = [];
|
|
260
|
+
$(row)
|
|
261
|
+
.find('td')
|
|
262
|
+
.each((colIndex, cell) => {
|
|
263
|
+
const text = $(cell).text().trim();
|
|
264
|
+
columns.push(text);
|
|
265
|
+
});
|
|
266
|
+
// Игнорируем пустые строки
|
|
267
|
+
if (columns.length > 0 && columns.some((col) => col !== '')) {
|
|
268
|
+
rows.push({ columns });
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
if (rows.length === 0) {
|
|
272
|
+
console.debug('⚠️ Таблица не содержит данных (tbody пустой)');
|
|
273
|
+
}
|
|
274
|
+
console.log(`✅ Распарсено строк: ${rows.length}`);
|
|
275
|
+
return {
|
|
276
|
+
rows,
|
|
277
|
+
totalRows: rows.length,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
console.error(`❌ Ошибка при парсинге таблицы: ${error.message}`);
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Парсит список инвойсов и извлекает данные + ID для модалки
|
|
287
|
+
*/
|
|
288
|
+
parseInvoicesList(html) {
|
|
289
|
+
try {
|
|
290
|
+
const $ = cheerio.load(html);
|
|
291
|
+
const invoices = [];
|
|
292
|
+
$('table tbody tr').each((index, row) => {
|
|
293
|
+
const $row = $(row);
|
|
294
|
+
const columns = [];
|
|
295
|
+
$row.find('td').each((colIndex, cell) => {
|
|
296
|
+
const text = $(cell).text().trim();
|
|
297
|
+
columns.push(text);
|
|
298
|
+
});
|
|
299
|
+
// Игнорируем пустые строки
|
|
300
|
+
if (columns.length === 0 || columns.every((col) => col === '')) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
// Извлекаем client_id и invoice_id из href модального окна
|
|
304
|
+
const modalLink = $row.find('a[href*="invoice-info.html"]');
|
|
305
|
+
let clientId = '';
|
|
306
|
+
let invoiceId = '';
|
|
307
|
+
if (modalLink.length > 0) {
|
|
308
|
+
const href = modalLink.attr('href') || '';
|
|
309
|
+
// Парсим URL параметры
|
|
310
|
+
const urlParams = new URLSearchParams(href.split('?')[1] || '');
|
|
311
|
+
clientId = urlParams.get('client_id') || '';
|
|
312
|
+
invoiceId = urlParams.get('invoice_id') || '';
|
|
313
|
+
}
|
|
314
|
+
// Предполагаемая структура колонок (может отличаться, нужно адаптировать)
|
|
315
|
+
// Обычно: [Номер, Дата, Срок, Статус, Сумма, ...]
|
|
316
|
+
const invoice = {
|
|
317
|
+
invoiceNumber: columns[0] || '',
|
|
318
|
+
invoiceDate: columns[1] || '',
|
|
319
|
+
dueDate: columns[2] || '',
|
|
320
|
+
status: columns[3] || '',
|
|
321
|
+
amount: columns[4] || '',
|
|
322
|
+
clientId,
|
|
323
|
+
invoiceId,
|
|
324
|
+
};
|
|
325
|
+
invoices.push(invoice);
|
|
326
|
+
});
|
|
327
|
+
return invoices;
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
console.error(`❌ Ошибка при парсинге списка инвойсов: ${error.message}`);
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Парсит детальную информацию из модального окна
|
|
336
|
+
*/
|
|
337
|
+
parseInvoiceDetails(html) {
|
|
338
|
+
try {
|
|
339
|
+
const $ = cheerio.load(html);
|
|
340
|
+
// Извлекаем информацию о плательщике и юр. лице
|
|
341
|
+
let payer = '';
|
|
342
|
+
let legalEntity = '';
|
|
343
|
+
let paymentDate;
|
|
344
|
+
// Ищем таблицу с информацией об инвойсе
|
|
345
|
+
$('table').each((index, table) => {
|
|
346
|
+
const $table = $(table);
|
|
347
|
+
$table.find('tr').each((rowIndex, row) => {
|
|
348
|
+
const $row = $(row);
|
|
349
|
+
const label = $row.find('td').first().text().trim();
|
|
350
|
+
const value = $row.find('td').last().text().trim();
|
|
351
|
+
if (label.toLowerCase().includes('payer') || label.includes('Плательщик')) {
|
|
352
|
+
payer = value;
|
|
353
|
+
}
|
|
354
|
+
else if (label.toLowerCase().includes('legal entity') || label.includes('Юридическое лицо')) {
|
|
355
|
+
legalEntity = value;
|
|
356
|
+
}
|
|
357
|
+
else if (label.toLowerCase().includes('payment date') || label.includes('Дата оплаты')) {
|
|
358
|
+
paymentDate = value || undefined;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
// Извлекаем items (позиции инвойса)
|
|
363
|
+
const items = [];
|
|
364
|
+
// Ищем таблицу с items (обычно имеет заголовки: Name, Quantity, Price, Amount)
|
|
365
|
+
$('table').each((index, table) => {
|
|
366
|
+
const $table = $(table);
|
|
367
|
+
const headers = [];
|
|
368
|
+
// Проверяем заголовки
|
|
369
|
+
$table.find('thead tr th').each((i, th) => {
|
|
370
|
+
headers.push($(th).text().trim().toLowerCase());
|
|
371
|
+
});
|
|
372
|
+
// Если есть заголовки похожие на items таблицу
|
|
373
|
+
if (headers.some((h) => h.includes('name') || h.includes('product')) &&
|
|
374
|
+
headers.some((h) => h.includes('quantity') || h.includes('qty'))) {
|
|
375
|
+
$table.find('tbody tr').each((rowIndex, row) => {
|
|
376
|
+
const $row = $(row);
|
|
377
|
+
const cells = [];
|
|
378
|
+
$row.find('td').each((cellIndex, cell) => {
|
|
379
|
+
cells.push($(cell).text().trim());
|
|
380
|
+
});
|
|
381
|
+
if (cells.length >= 3) {
|
|
382
|
+
items.push({
|
|
383
|
+
name: cells[0] || '',
|
|
384
|
+
quantity: cells[1] || '',
|
|
385
|
+
price: cells[2] || '',
|
|
386
|
+
amount: cells[3] || cells[2] || '',
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
// Извлекаем total (обычно внизу таблицы или в отдельном блоке)
|
|
393
|
+
let total = '';
|
|
394
|
+
// Ищем элементы с классами или текстом "Total", "Итого"
|
|
395
|
+
$('*').each((index, element) => {
|
|
396
|
+
const $el = $(element);
|
|
397
|
+
const text = $el.text().trim();
|
|
398
|
+
if ((text.toLowerCase().includes('total:') || text.includes('Итого:')) &&
|
|
399
|
+
text.length < 100) {
|
|
400
|
+
// Извлекаем числовое значение
|
|
401
|
+
const match = text.match(/[\d.,\s]+(?:₽|\$|€|USD|RUB)/);
|
|
402
|
+
if (match) {
|
|
403
|
+
total = match[0].trim();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
// Fallback: берем последнюю ячейку из items таблицы
|
|
408
|
+
if (!total && items.length > 0) {
|
|
409
|
+
const lastItem = items[items.length - 1];
|
|
410
|
+
total = lastItem.amount;
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
payer,
|
|
414
|
+
legalEntity,
|
|
415
|
+
paymentDate,
|
|
416
|
+
items,
|
|
417
|
+
total,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
console.error(`❌ Ошибка при парсинге деталей инвойса: ${error.message}`);
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Парсит модальное окно "Invoice Information" и извлекает items
|
|
427
|
+
*/
|
|
428
|
+
parseInvoiceItems(html) {
|
|
429
|
+
try {
|
|
430
|
+
const $ = cheerio.load(html);
|
|
431
|
+
const items = [];
|
|
432
|
+
// Ищем таблицу с items
|
|
433
|
+
$('table').each((tableIndex, table) => {
|
|
434
|
+
const $table = $(table);
|
|
435
|
+
// Проверяем заголовки (Product, Price, Quantity, Discount, Amount)
|
|
436
|
+
const hasProductHeader = $table.find('th').text().toLowerCase().includes('product');
|
|
437
|
+
const hasAmountHeader = $table.find('th').text().toLowerCase().includes('amount');
|
|
438
|
+
if (hasProductHeader && hasAmountHeader) {
|
|
439
|
+
// Парсим строки tbody
|
|
440
|
+
$table.find('tbody tr').each((rowIndex, row) => {
|
|
441
|
+
const $row = $(row);
|
|
442
|
+
const cells = $row.find('td');
|
|
443
|
+
if (cells.length >= 5) {
|
|
444
|
+
// Пропускаем первую колонку с #
|
|
445
|
+
const productName = $(cells[1]).text().trim();
|
|
446
|
+
const priceText = $(cells[2]).text().trim();
|
|
447
|
+
const quantityText = $(cells[3]).text().trim();
|
|
448
|
+
const discountText = $(cells[5]).text().trim();
|
|
449
|
+
const amountText = $(cells[6]).text().trim();
|
|
450
|
+
items.push({
|
|
451
|
+
productName,
|
|
452
|
+
price: this.extractAmount(priceText),
|
|
453
|
+
quantity: parseInt(quantityText) || 0,
|
|
454
|
+
discount: this.extractAmount(discountText),
|
|
455
|
+
amount: this.extractAmount(amountText)
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
return items;
|
|
462
|
+
}
|
|
463
|
+
catch (error) {
|
|
464
|
+
console.error(`Ошибка парсинга invoice items:`, error);
|
|
465
|
+
return [];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Парсит модальное окно "Subscription Info" и извлекает subscriptions
|
|
470
|
+
*/
|
|
471
|
+
parseSubscriptions(html) {
|
|
472
|
+
try {
|
|
473
|
+
const $ = cheerio.load(html);
|
|
474
|
+
const subscriptions = [];
|
|
475
|
+
// Ищем таблицу с subscription info
|
|
476
|
+
$('table').each((tableIndex, table) => {
|
|
477
|
+
const $table = $(table);
|
|
478
|
+
// Проверяем заголовки (Product, Quantity, Price, Amount)
|
|
479
|
+
const headers = $table.find('th').map((i, th) => $(th).text().toLowerCase()).get();
|
|
480
|
+
const hasSubscriptionHeaders = headers.some(h => h.includes('product')) &&
|
|
481
|
+
headers.some(h => h.includes('quantity')) &&
|
|
482
|
+
headers.length === 4; // Subscription таблица имеет 4 колонки
|
|
483
|
+
if (hasSubscriptionHeaders) {
|
|
484
|
+
// Парсим строки (могут быть без tbody)
|
|
485
|
+
$table.find('tr').each((rowIndex, row) => {
|
|
486
|
+
const $row = $(row);
|
|
487
|
+
// Пропускаем заголовок
|
|
488
|
+
if ($row.find('th').length > 0) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
const cells = $row.find('td');
|
|
492
|
+
if (cells.length >= 4) {
|
|
493
|
+
const productName = $(cells[0]).text().trim();
|
|
494
|
+
const quantityText = $(cells[1]).text().trim();
|
|
495
|
+
const priceText = $(cells[2]).text().trim();
|
|
496
|
+
const amountText = $(cells[3]).text().trim();
|
|
497
|
+
subscriptions.push({
|
|
498
|
+
productName,
|
|
499
|
+
quantity: parseInt(quantityText) || 0,
|
|
500
|
+
price: this.extractAmount(priceText),
|
|
501
|
+
amount: this.extractAmount(amountText)
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
return subscriptions;
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
console.error(`Ошибка парсинга subscriptions:`, error);
|
|
511
|
+
return [];
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Выводит таблицу инвойсов в читаемом формате
|
|
516
|
+
*/
|
|
517
|
+
logInvoicesTable(data) {
|
|
518
|
+
const lines = [];
|
|
519
|
+
lines.push('');
|
|
520
|
+
lines.push('==== INVOICES TABLE ====');
|
|
521
|
+
lines.push(`Total rows: ${data.totalRows}`);
|
|
522
|
+
lines.push('');
|
|
523
|
+
if (data.totalRows === 0) {
|
|
524
|
+
lines.push('(No invoices found)');
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
data.rows.forEach((row, index) => {
|
|
528
|
+
const rowNumber = index + 1;
|
|
529
|
+
const rowData = row.columns.join(' | ');
|
|
530
|
+
lines.push(`[${rowNumber}] ${rowData}`);
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
lines.push('');
|
|
534
|
+
lines.push('==== END ====');
|
|
535
|
+
lines.push('');
|
|
536
|
+
console.log(lines.join('\n'));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
exports.InvoicesParser = InvoicesParser;
|
|
540
|
+
//# sourceMappingURL=invoices.parser.js.map
|