Улучшение импорта клиентов: предобработка данных, умное слияние, прогресс-бар
- Добавлена предобработка email перед валидацией: * Исправление типичных опечаток (mail ru -> mail.ru, .ry -> .ru) * Удаление пробелов и двойных @@ * Умное добавление @ для популярных доменов * Исправление доменов без точки (gmail -> gmail.com) - Улучшена нормализация телефонов: * Умное добавление кода страны (+375, +7, +380) * Конверсия старого формата 8XXXXXXXXXX -> +7XXXXXXXXXX * Проверка длины номера (10-15 символов) * Поддержка локальных белорусских номеров (9 цифр) - Реализована идемпотентность импорта: * Notes не раздуваются при повторных импортах (метод _append_unique_note) * ContactChannel не дублируется для одного клиента * Проверка существования альтернативных контактов по customer+type+value - Добавлен прогресс-бар и защита от закрытия: * Визуальный прогресс-бар с анимацией и динамическим текстом * Блокировка формы во время импорта * Предупреждение браузера при попытке закрыть страницу - Создана команда clear_anatol_customers для тестирования - Добавлен тестовый файл test_customer_preprocess.csv с примерами исправляемых ошибок
This commit is contained in:
@@ -175,13 +175,30 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<button type="submit" class="btn btn-primary" id="submitBtn">
|
||||
<i class="bi bi-upload"></i> Импортировать
|
||||
</button>
|
||||
<a href="{% url 'customers:customer-list' %}" class="btn btn-outline-secondary">
|
||||
Отмена
|
||||
</a>
|
||||
</form>
|
||||
|
||||
<!-- Прогресс-бар (скрыт по умолчанию) -->
|
||||
<div id="progressContainer" class="mt-4 d-none">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<strong>Импорт в процессе...</strong>
|
||||
<span id="progressText" class="text-muted">Подготовка...</span>
|
||||
</div>
|
||||
<div class="progress" style="height: 25px;">
|
||||
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
|
||||
<span id="progressPercent">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted mt-2 mb-0">
|
||||
<small><i class="bi bi-exclamation-triangle text-warning"></i> Не закрывайте страницу до завершения импорта</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -308,6 +325,64 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
alert('Пожалуйста, выберите файл для импорта');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Показываем прогресс-бар и блокируем повторную отправку
|
||||
showProgressBar();
|
||||
});
|
||||
|
||||
// Показать прогресс-бар и защиту от закрытия
|
||||
function showProgressBar() {
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const progressContainer = document.getElementById('progressContainer');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressPercent = document.getElementById('progressPercent');
|
||||
const progressText = document.getElementById('progressText');
|
||||
|
||||
// Блокируем кнопку и форму
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Импорт...';
|
||||
fileInput.disabled = true;
|
||||
dropZone.style.pointerEvents = 'none';
|
||||
dropZone.style.opacity = '0.6';
|
||||
|
||||
// Показываем прогресс-бар
|
||||
progressContainer.classList.remove('d-none');
|
||||
|
||||
// Анимация прогресса (имитация, т.к. реальный прогресс без WebSocket сложен)
|
||||
let progress = 0;
|
||||
const progressInterval = setInterval(() => {
|
||||
if (progress < 90) {
|
||||
progress += Math.random() * 15;
|
||||
if (progress > 90) progress = 90;
|
||||
|
||||
progressBar.style.width = progress + '%';
|
||||
progressBar.setAttribute('aria-valuenow', progress);
|
||||
progressPercent.textContent = Math.round(progress) + '%';
|
||||
|
||||
if (progress < 30) {
|
||||
progressText.textContent = 'Чтение файла...';
|
||||
} else if (progress < 60) {
|
||||
progressText.textContent = 'Обработка данных...';
|
||||
} else {
|
||||
progressText.textContent = 'Сохранение в базу...';
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
|
||||
// Сохраняем интервал для очистки при завершении страницы
|
||||
window.importProgressInterval = progressInterval;
|
||||
|
||||
// Включаем защиту от закрытия страницы
|
||||
window.importInProgress = true;
|
||||
}
|
||||
|
||||
// Предупреждение при закрытии страницы во время импорта
|
||||
window.addEventListener('beforeunload', function(e) {
|
||||
if (window.importInProgress) {
|
||||
e.preventDefault();
|
||||
e.returnValue = 'Импорт ещё не завершён. Вы уверены, что хотите покинуть страницу?';
|
||||
return e.returnValue;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user