let mainData = null; let templateData = null; let mainFileName = ''; let templateFileName = ''; let mappedResult = null; // Проверка загрузки XLSX function getXLSX() { if (window.XLSX) return window.XLSX; alert('Библиотека для работы с Excel ещё не загрузилась. Подождите секунду...'); return null; } // Обработка выбора файла function handleFileSelect(event, type) { const XLSX = getXLSX(); if (!XLSX) return; const file = event.target.files[0]; if (!file) return; console.log('Файл выбран:', type, file.name, file.size); const zone = document.getElementById(type + '-upload'); const info = document.getElementById(type + '-file-info'); zone.classList.add('has-file'); info.textContent = file.name + ' (' + formatFileSize(file.size) + ')'; if (type === 'main') { mainFileName = file.name; readExcel(file, (data) => { console.log('Основной файл прочитан, строк:', data.length); mainData = data; checkFilesReady(); }, (error) => { console.error('Ошибка чтения основного файла:', error); alert('Ошибка чтения файла: ' + error.message); }); } else { templateFileName = file.name; readExcel(file, (data) => { console.log('Шаблон прочитан, строк:', data.length); templateData = data; checkFilesReady(); }, (error) => { console.error('Ошибка чтения шаблона:', error); alert('Ошибка чтения файла: ' + error.message); }); } } function formatFileSize(bytes) { if (bytes < 1024) return bytes + ' Б'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' КБ'; return (bytes / (1024 * 1024)).toFixed(1) + ' МБ'; } function readExcel(file, callback, errorCallback) { const XLSX = window.XLSX; if (!XLSX) { if (errorCallback) errorCallback(new Error('Библиотека XLSX не загружена')); return; } console.log('Чтение файла:', file.name); const reader = new FileReader(); reader.onload = (e) => { try { const data = new Uint8Array(e.target.result); console.log('Файл загружен в память, байт:', data.length); const workbook = XLSX.read(data, { type: 'array' }); console.log('Workbook прочитан, листов:', workbook.SheetNames.length); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const json = XLSX.utils.sheet_to_json(worksheet); console.log('Данные конвертированы в JSON, строк:', json.length); callback(json); } catch (error) { console.error('Ошибка при чтении Excel:', error); if (errorCallback) errorCallback(error); else alert('Ошибка при чтении файла: ' + error.message); } }; reader.onerror = () => { const error = new Error('Не удалось прочитать файл'); if (errorCallback) errorCallback(error); else alert(error.message); }; reader.readAsArrayBuffer(file); } function checkFilesReady() { const btn = document.getElementById('btn-mapping'); if (mainData && mainData.length > 0 && templateData && templateData.length > 0) { btn.disabled = false; } } function goToMapping() { if (!mainData || !templateData) return; document.getElementById('upload-section').classList.add('hidden'); document.getElementById('mapping-section').classList.remove('hidden'); document.getElementById('step1').classList.remove('active'); document.getElementById('step1').classList.add('completed'); document.getElementById('step2').classList.add('active'); populateFieldSelectors(); } function goBackToUpload() { document.getElementById('upload-section').classList.remove('hidden'); document.getElementById('mapping-section').classList.add('hidden'); document.getElementById('step1').classList.add('active'); document.getElementById('step2').classList.remove('active', 'completed'); } function populateFieldSelectors() { const mainFields = Object.keys(mainData[0] || {}); const templateFields = Object.keys(templateData[0] || {}); const mainSelect = document.getElementById('main-key-field'); const templateSelect = document.getElementById('template-key-field'); mainSelect.innerHTML = ''; templateSelect.innerHTML = ''; // Поиск полей с номером телефона и лицевым счетом const phoneFields = ['телефон', 'phone', 'tel', 'номер', 'number', 'лицевой', 'account', 'лс', 'флс']; phoneFields.forEach(field => { const mainMatch = mainFields.find(f => f.toLowerCase().includes(field)); const templateMatch = templateFields.find(f => f.toLowerCase().includes(field)); if (mainMatch && !mainSelect.querySelector(`[value="${mainMatch}"]`)) { mainSelect.add(new Option(mainMatch, mainMatch)); } if (templateMatch && !templateSelect.querySelector(`[value="${templateMatch}"]`)) { templateSelect.add(new Option(templateMatch, templateMatch)); } }); // Добавляем все остальные поля mainFields.forEach(field => { if (!mainSelect.querySelector(`[value="${field}"]`)) { mainSelect.add(new Option(field, field)); } }); templateFields.forEach(field => { if (!templateSelect.querySelector(`[value="${field}"]`)) { templateSelect.add(new Option(field, field)); } }); // Заполняем чекбоксы полей для заполнения const fieldsContainer = document.getElementById('fields-to-fill'); fieldsContainer.innerHTML = ''; templateFields.forEach(field => { const div = document.createElement('div'); div.style.cssText = 'display: inline-flex; align-items: center; gap: var(--kt-ai-space-2); margin-right: var(--kt-ai-space-3); margin-bottom: var(--kt-ai-space-2);'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = 'field-' + field; checkbox.value = field; checkbox.checked = true; // По умолчанию все поля выбраны checkbox.onchange = processMapping; const label = document.createElement('label'); label.htmlFor = 'field-' + field; label.textContent = field; label.style.fontSize = 'var(--kt-ai-text-sm)'; div.appendChild(checkbox); div.appendChild(label); fieldsContainer.appendChild(div); }); processMapping(); } function processMapping() { const mainKeyField = document.getElementById('main-key-field').value; const templateKeyField = document.getElementById('template-key-field').value; if (!mainKeyField || !templateKeyField) return; // Собираем поля для заполнения const fieldsToFill = []; document.querySelectorAll('#fields-to-fill input[type="checkbox"]:checked').forEach(cb => { fieldsToFill.push(cb.value); }); // Создаём карту соответствий const mainMap = new Map(); mainData.forEach(row => { const keyValue = normalizeValue(row[mainKeyField]); if (keyValue) { mainMap.set(keyValue, row); } }); // Сопоставляем данные let matched = 0; let notMatched = 0; let filled = 0; const previewRows = templateData.slice(0, 10).map((templateRow, index) => { const keyValue = normalizeValue(templateRow[templateKeyField]); const mainRow = mainMap.get(keyValue); if (mainRow) { matched++; // Заполняем пустые поля fieldsToFill.forEach(field => { if (!templateRow[field] && mainRow[field]) { filled++; templateRow[field] = mainRow[field]; } }); } else { notMatched++; } return { templateRow, mainRow, matched: !!mainRow }; }); // Статистика const total = templateData.length; const matchPercent = total > 0 ? Math.round((matched / total) * 100) : 0; document.getElementById('mapping-stats').innerHTML = `
${total}
Всего записей в шаблоне
${matched}
Сопоставлено (${matchPercent}%)
${notMatched}
Не найдено совпадений
${filled}
Полей заполнено (в предпросмотре)
`; // Предпросмотр const table = document.getElementById('mapping-preview'); const allFields = new Set([...Object.keys(templateData[0] || {}), ...fieldsToFill]); let headerHtml = ''; allFields.forEach(field => { headerHtml += '' + escapeHtml(field) + ''; }); headerHtml += 'Статус'; let bodyHtml = ''; previewRows.forEach(row => { bodyHtml += ''; allFields.forEach(field => { const value = row.templateRow[field] !== undefined ? row.templateRow[field] : ''; const fromMain = row.mainRow && row.mainRow[field] && !row.templateRow[field]; const cellStyle = fromMain ? 'background: var(--kt-ai-success-bg); font-weight: 600;' : ''; bodyHtml += '' + escapeHtml(value) + ''; }); bodyHtml += '' + (row.matched ? '✓' : '✗') + ''; }); bodyHtml += ''; table.innerHTML = headerHtml + bodyHtml; // Сохраняем результат для дальнейшей обработки mappedResult = { templateData, fieldsToFill, mainMap, templateKeyField }; document.getElementById('btn-process').disabled = matched === 0; } function processFiles() { if (!mappedResult) return; const { templateData, fieldsToFill, mainMap, templateKeyField } = mappedResult; // Полная обработка всех данных let totalFilled = 0; const resultData = templateData.map(row => { const keyValue = normalizeValue(row[templateKeyField]); const mainRow = mainMap.get(keyValue); if (mainRow) { fieldsToFill.forEach(field => { if (!row[field] && mainRow[field]) { row[field] = mainRow[field]; totalFilled++; } }); } return row; }); // Создаём Excel файл const worksheet = XLSX.utils.json_to_sheet(resultData); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, 'Результат'); // Генерируем имя файла const baseName = templateFileName.replace(/\.(xlsx|xls|csv)$/i, ''); const outputFileName = baseName + '_заполненный.xlsx'; // Создаём ссылку для скачивания const wbout = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' }); const blob = new Blob([wbout], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }); const url = URL.createObjectURL(blob); const downloadLink = document.getElementById('download-link'); downloadLink.href = url; downloadLink.download = outputFileName; // Показываем результат document.getElementById('mapping-section').classList.add('hidden'); document.getElementById('result-section').classList.remove('hidden'); document.getElementById('step2').classList.remove('active'); document.getElementById('step2').classList.add('completed'); document.getElementById('step3').classList.add('active'); // Итоговая статистика const matchedCount = resultData.filter(row => { const keyValue = normalizeValue(row[templateKeyField]); return mainMap.has(keyValue); }).length; document.getElementById('result-stats').innerHTML = `
${templateData.length}
Всего записей обработано
${matchedCount}
Найдено совпадений
${totalFilled}
Полей заполнено
${outputFileName}
Файл готов
`; } function normalizeValue(value) { if (value === null || value === undefined) return ''; const str = String(value).trim(); // Удаляем все нецифровые символы для номеров и счетов return str.replace(/\D/g, ''); } function escapeHtml(text) { if (text === null || text === undefined) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }