/** * utils.js — Вспомогательные утилиты * Чтение Excel, работа с CSV, скачивание файлов */ /** * Прочитать Excel-файл и вернуть массив листов * @param {File} file - файл .xlsx/.xls * @returns {Promise>} */ export async function readExcel(file) { const arr = await file.arrayBuffer(); const wb = XLSX.read(arr, { type: 'array', codepage: 1251 }); return wb.SheetNames.map(name => ({ name, data: XLSX.utils.sheet_to_json(wb.Sheets[name], { header: 1, defval: '' }) })); } /** * Проверить допустимость расширения файла * @param {File} file * @returns {boolean} */ export function validateFile(file) { const allowed = ['.xlsx', '.xls']; const ext = file.name.slice(file.name.lastIndexOf('.')).toLowerCase(); if (!allowed.includes(ext)) { alert(`❌ Недопустимый тип файла: ${ext}. Разрешены: .xlsx, .xls`); return false; } return true; } /** * Преобразовать массив объектов в CSV-строку * @param {Object[]} data * @param {string[]} headers * @returns {string} */ export function arrayToCsv(data, headers) { const rows = data.map(r => headers.map(h => `"${String(r[h] ?? '').replace(/"/g, '""')}"`).join(',') ); return '\uFEFF' + headers.join(',') + '\n' + rows.join('\n'); } /** * Скачать строку/Blob как файл * @param {string|Blob} content * @param {string} filename */ export function downloadFile(content, filename) { const blob = new Blob([content], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(() => URL.revokeObjectURL(url), 1000); } /** * Скачать Excel-файл (XLSX) из массива данных * @param {Object[]} data - массив строк * @param {string} filename */ export function downloadExcel(data, filename) { const wb = XLSX.utils.book_new(); const ws = XLSX.utils.json_to_sheet(data); // Авто-ширина колонок const colWidths = Object.keys(data[0] || {}).map(key => { const maxLen = data.reduce((max, row) => { const val = String(row[key] ?? ''); return Math.max(max, val.length); }, key.length); return { wch: Math.min(maxLen + 2, 40) }; }); ws['!cols'] = colWidths; XLSX.utils.book_append_sheet(wb, ws, 'Сверка'); const wbOut = XLSX.write(wb, { bookType: 'xlsx', type: 'array' }); downloadFile(wbOut, filename); } /** * Форматировать число в рубли (с пробелами как разделителями разрядов) * @param {number} num * @returns {string} */ export function formatMoney(num) { if (num === null || num === undefined || isNaN(num)) return '—'; return num.toLocaleString('ru-RU', { minimumFractionDigits: 0, maximumFractionDigits: 0 }); }