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

158 lines
5.8 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 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;