Files
mkd/backend/debtorReportProcessor.js
2026-02-04 00:17:04 +05:00

116 lines
4.4 KiB
JavaScript
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;