Initial commit MKD fixes
This commit is contained in:
115
backend/debtorReportProcessor.js
Executable file
115
backend/debtorReportProcessor.js
Executable file
@@ -0,0 +1,115 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user