Initial commit MKD fixes

This commit is contained in:
Arsen
2026-02-04 00:17:04 +05:00
commit de94ad707b
312 changed files with 138754 additions and 0 deletions

115
backend/debtorReportProcessor.js Executable file
View 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;