Files
mkd/backend/balanceSheet76Processor.js

158 lines
5.8 KiB
JavaScript
Raw Normal View History

2026-02-04 00:17:04 +05:00
const csv = require('csv-parser');
const XLSX = require('xlsx');
const fs = require('fs');
/**
* Процессор для обработки ОСВ по счёту 76.06 (лицевые счета жителей)
*/
class BalanceSheet76Processor {
constructor() {}
parseAmount(value) {
if (!value) return 0;
const str = String(value).replace(/\s/g, '').replace(',', '.');
const num = parseFloat(str);
return isNaN(num) ? 0 : num;
}
_isDataRow(rowArray) {
if (!rowArray || rowArray.length < 5) return false;
const firstCell = String(rowArray[0] || '').trim();
const col1 = this.parseAmount(rowArray[1]);
const col3 = this.parseAmount(rowArray[3]);
const col4 = this.parseAmount(rowArray[4]);
return firstCell.length >= 1 && (col1 > 0 || col3 > 0 || col4 > 0);
}
/**
* Извлечь номер л/с из колонки 0 (например "00-000000001, ФИО (л/с)" -> "00-000000001")
*/
extractAccountLs(firstCell) {
const s = String(firstCell || '').trim();
const comma = s.indexOf(',');
if (comma > 0) {
const prefix = s.substring(0, comma).trim();
if (/^[\d\-]+$/.test(prefix) || /^[\d\-]+\s*$/.test(prefix)) return prefix;
}
return null;
}
/**
* Парсинг CSV ОСВ 76 (UTF-8, BOM, автоопределение начала данных)
*/
async parseBalanceSheet76CSV(filePath) {
return new Promise((resolve, reject) => {
const allRows = [];
let currentRowIndex = 0;
let dataStartRowIndex = -1;
fs.createReadStream(filePath, { encoding: 'utf8' })
.pipe(csv({ separator: ';', headers: false }))
.on('data', (row) => {
const rowArray = Object.values(row);
if (currentRowIndex === 0 && rowArray[0] !== undefined) {
rowArray[0] = String(rowArray[0]).replace(/^\uFEFF/, '');
}
allRows.push({ index: currentRowIndex, data: rowArray });
currentRowIndex++;
})
.on('end', () => {
for (let i = 0; i < allRows.length; i++) {
const rowArray = allRows[i].data;
const firstCell = String(rowArray[0] || '').trim();
if (firstCell.includes('Обороты за период') || firstCell.includes('Контрагенты') || firstCell.includes('Счет,')) {
dataStartRowIndex = i + 1;
break;
}
if (this._isDataRow(rowArray)) {
dataStartRowIndex = i;
break;
}
}
if (dataStartRowIndex < 0) dataStartRowIndex = 9;
const rows = allRows.filter(r => r.index >= dataStartRowIndex);
resolve(rows);
})
.on('error', reject);
});
}
async parseBalanceSheet76XLSX(filePath) {
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: '', raw: false });
const rows = [];
for (let i = 9; i < rawData.length; i++) {
rows.push({ index: i, data: rawData[i] || [] });
}
return rows;
}
/**
* Парсинг ведомости 76 и возврат массива строк по лицевым счетам
*/
async parseBalanceSheet76(filePath, fileType) {
const rows = fileType === 'CSV'
? await this.parseBalanceSheet76CSV(filePath)
: await this.parseBalanceSheet76XLSX(filePath);
const result = [];
let rowIndex = 0;
for (const row of rows) {
const rowData = row.data;
if (!rowData || rowData.length === 0) continue;
const firstCell = String(rowData[0] || '').trim();
if (!firstCell) continue;
if (firstCell === '76.06' || firstCell === 'Итого') continue;
const saldoStartDebet = this.parseAmount(rowData[1]);
const turnoverDebet = this.parseAmount(rowData[3]);
const turnoverCredit = this.parseAmount(rowData[4]);
const saldoEndDebet = this.parseAmount(rowData[5]);
const saldoEndCredit = this.parseAmount(rowData[6]);
result.push({
row_index: rowIndex,
account_label: firstCell,
account_ls: this.extractAccountLs(firstCell),
saldo_start_debet: saldoStartDebet,
turnover_debet: turnoverDebet,
turnover_credit: turnoverCredit,
saldo_end_debet: saldoEndDebet,
saldo_end_credit: saldoEndCredit
});
rowIndex++;
}
console.log(`[BalanceSheet76Processor] Распознано строк (лицевых счетов): ${result.length}`);
return result;
}
extractPeriodFromFilename(filename) {
const yearMatch = filename.match(/20\d{2}/);
const year = yearMatch ? parseInt(yearMatch[0]) : new Date().getFullYear();
const monthMatch = filename.match(/(\d{1,2})[\.\/](\d{4})|(\d{4})[\.\/](\d{1,2})|Январь|январь/i);
if (monthMatch) {
const monthNames = { январ: 1, феврал: 2, март: 3, апрел: 4, май: 5, июн: 6, июл: 7, август: 8, сентябр: 9, октябр: 10, ноябр: 11, декабр: 12 };
const lower = filename.toLowerCase();
for (const [key, m] of Object.entries(monthNames)) {
if (lower.includes(key)) {
const startDate = new Date(year, m - 1, 1);
const endDate = new Date(year, m, 0);
return {
start: `${year}-${String(m).padStart(2, '0')}-01`,
end: `${year}-${String(m).padStart(2, '0')}-${String(endDate.getDate()).padStart(2, '0')}`,
type: 'month'
};
}
}
}
return { start: `${year}-01-01`, end: `${year}-12-31`, type: 'year' };
}
}
module.exports = BalanceSheet76Processor;