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;