116 lines
4.4 KiB
JavaScript
Executable File
116 lines
4.4 KiB
JavaScript
Executable File
const csv = require('csv-parser');
|
||
const fs = require('fs');
|
||
|
||
/**
|
||
* Парсер отчёта по задолженности (CSV с разделителем ;).
|
||
* Строки данных: № п/п (0), Лицевой счёт (1), Ответственный (4), Объект учета (5), Количество месяцев (8), последняя колонка — Общая задолженность.
|
||
*/
|
||
class DebtorReportProcessor {
|
||
/**
|
||
* Нормализация числа из отчёта: пробелы убрать, запятую заменить на точку.
|
||
*/
|
||
parseAmount(str) {
|
||
if (str === undefined || str === null || str === '') return null;
|
||
const s = String(str).replace(/\s/g, '').replace(',', '.').trim();
|
||
if (s === '' || s === '-') return null;
|
||
const n = parseFloat(s);
|
||
return Number.isFinite(n) ? n : null;
|
||
}
|
||
|
||
/**
|
||
* Проверка: строка похожа на строку данных (колонка 0 — число, колонка 1 — лицевой счёт вида 00-000000001).
|
||
*/
|
||
isDataRow(cells) {
|
||
if (!cells || cells.length < 6) return false;
|
||
const col0 = String(cells[0] || '').trim();
|
||
const col1 = String(cells[1] || '').trim();
|
||
if (col1 === '' || col1.toLowerCase() === 'итого') return false;
|
||
const num0 = parseInt(col0, 10);
|
||
const isNum = col0 !== '' && !Number.isNaN(num0) && num0 >= 1;
|
||
const looksLikeAccount = /^[\d\-]+$/.test(col1) || /^\d{2}-\d{9}$/.test(col1.replace(/\s/g, ''));
|
||
return isNum && (looksLikeAccount || col1.length >= 5);
|
||
}
|
||
|
||
/**
|
||
* Парсинг одной строки данных в объект для БД.
|
||
*/
|
||
parseDataRow(cells, rowIndex) {
|
||
const account = String(cells[1] || '').trim();
|
||
const responsibleName = String(cells[4] || '').trim();
|
||
const objectAddress = String(cells[5] || '').trim();
|
||
const monthsRaw = String(cells[8] || '').trim();
|
||
const monthsDebt = monthsRaw === '' ? null : (parseInt(monthsRaw, 10) || null);
|
||
const lastCol = cells[cells.length - 1];
|
||
const totalDebt = this.parseAmount(lastCol);
|
||
return {
|
||
row_index: rowIndex,
|
||
account,
|
||
responsible_name: responsibleName,
|
||
object_address: objectAddress,
|
||
months_debt: monthsDebt,
|
||
total_debt: totalDebt != null ? totalDebt : 0,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Парсинг CSV-файла отчёта по задолженности.
|
||
* Возвращает массив объектов { row_index, account, responsible_name, object_address, months_debt, total_debt }.
|
||
*/
|
||
async parseDebtorReportCSV(filePath) {
|
||
return new Promise((resolve, reject) => {
|
||
const rows = [];
|
||
let rowIndex = 0;
|
||
|
||
fs.createReadStream(filePath, { encoding: 'utf8' })
|
||
.pipe(csv({ separator: ';', headers: false }))
|
||
.on('data', (row) => {
|
||
const cells = Object.values(row);
|
||
if (rowIndex === 0 && cells[0] !== undefined) {
|
||
const first = String(cells[0]).replace(/^\uFEFF/, '');
|
||
cells[0] = first;
|
||
}
|
||
if (this.isDataRow(cells)) {
|
||
rows.push(this.parseDataRow(cells, rowIndex));
|
||
}
|
||
rowIndex++;
|
||
})
|
||
.on('end', () => resolve(rows))
|
||
.on('error', reject);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Парсинг XLSX: чтение первого листа, поиск строк данных по тем же правилам.
|
||
*/
|
||
async parseDebtorReportXLSX(filePath) {
|
||
const XLSX = require('xlsx');
|
||
const workbook = XLSX.readFile(filePath);
|
||
const sheetName = workbook.SheetNames[0];
|
||
const worksheet = workbook.Sheets[sheetName];
|
||
const rawData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: '' });
|
||
const rows = [];
|
||
for (let i = 0; i < rawData.length; i++) {
|
||
const cells = rawData[i] || [];
|
||
if (this.isDataRow(cells)) {
|
||
rows.push(this.parseDataRow(cells, i));
|
||
}
|
||
}
|
||
return rows;
|
||
}
|
||
|
||
/**
|
||
* Единая точка входа: парсинг по типу файла.
|
||
*/
|
||
async parseDebtorReport(filePath, fileType) {
|
||
if (fileType === 'CSV') {
|
||
return this.parseDebtorReportCSV(filePath);
|
||
}
|
||
if (fileType === 'XLSX') {
|
||
return this.parseDebtorReportXLSX(filePath);
|
||
}
|
||
throw new Error('Поддерживаются только CSV и XLSX');
|
||
}
|
||
}
|
||
|
||
module.exports = DebtorReportProcessor;
|