Files
octopus/myproject/customers/templates/customers/customer_export_modal.html
Andrey Smakotin 95036ed285 Добавлен настраиваемый экспорт клиентов с выбором полей и форматов
Реализован полностью новый функционал экспорта клиентов с возможностью
выбора полей, формата файла (CSV/XLSX) и сохранением предпочтений.

Ключевые изменения:

1. CustomerExporter (import_export.py):
   - Полностью переписан класс с поддержкой динамического выбора полей
   - Добавлена конфигурация AVAILABLE_FIELDS с метаданными полей
   - Реализован метод get_available_fields() для фильтрации по ролям
   - Новый метод export_to_xlsx() с автоподстройкой ширины столбцов
   - Форматирование ContactChannel с переводами строк
   - Поддержка фильтрации queryset

2. CustomerExportForm (forms.py):
   - Динамическое создание checkbox полей на основе роли пользователя
   - Выбор формата файла (CSV/XLSX) через radio buttons
   - Валидация выбора хотя бы одного поля

3. View customer_export (views.py):
   - КРИТИЧНО: Изменён декоратор с @manager_or_owner_required на @owner_required
   - Обработка GET (редирект) и POST запросов
   - Применение фильтров CustomerFilter из списка клиентов
   - Оптимизация с prefetch_related('contact_channels')
   - Сохранение настроек экспорта в session

4. UI изменения:
   - Создан шаблон customer_export_modal.html с модальным окном
   - Обновлён customer_list.html: кнопка экспорта с проверкой роли
   - JavaScript для восстановления сохранённых настроек из session
   - Отображение количества экспортируемых клиентов
   - Бейдж "Только для владельца" на поле баланса кошелька

Безопасность:
- Экспорт доступен ТОЛЬКО владельцу тенанта (OWNER) и superuser
- Поле "Баланс кошелька" скрыто от менеджеров на уровне формы
- Двойная проверка роли при экспорте баланса
- Кнопка экспорта скрыта в UI для всех кроме owner/superuser

Функциональность:
- Выбор полей: ID, имя, email, телефон, заметки, каналы связи, баланс, дата создания
- Форматы: CSV (с BOM для Excel) и XLSX
- Учёт текущих фильтров и поиска из списка клиентов
- Сохранение предпочтений между экспортами в session
- Исключение системного клиента из экспорта

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 21:12:08 +03:00

106 lines
5.0 KiB
HTML
Raw 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.
<!-- Modal for Customer Export Configuration -->
<div class="modal fade" id="exportModal" tabindex="-1" aria-labelledby="exportModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form method="post" action="{% url 'customers:customer-export' %}?{{ request.GET.urlencode }}" id="exportForm">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title" id="exportModalLabel">
<i class="bi bi-download"></i> Настройка экспорта клиентов
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<!-- Export info -->
<div class="alert alert-info">
<i class="bi bi-info-circle"></i>
Будет экспортировано клиентов: <strong>{{ total_customers }}</strong>
{% if query or filter.form.has_notes.value or filter.form.no_phone.value or filter.form.no_email.value or filter.form.has_contact_channel.value %}
<br><small>С учётом текущих фильтров и поиска</small>
{% endif %}
</div>
<!-- Field Selection -->
<div class="mb-4">
<h6>Выберите поля для экспорта:</h6>
<div class="row">
{% for field in export_form %}
{% if field.name != 'export_format' %}
<div class="col-md-6 mb-2">
<div class="form-check">
{{ field }}
<label class="form-check-label" for="{{ field.id_for_label }}">
{{ field.label }}
{% if 'wallet_balance' in field.name %}
<span class="badge bg-warning text-dark">Только для владельца</span>
{% endif %}
</label>
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
<!-- Format Selection -->
<div class="mb-3">
<h6>Формат файла:</h6>
{% for choice in export_form.export_format %}
<div class="form-check form-check-inline">
{{ choice.tag }}
<label class="form-check-label" for="{{ choice.id_for_label }}">
{{ choice.choice_label }}
</label>
</div>
{% endfor %}
</div>
<div class="text-muted small">
<i class="bi bi-lightbulb"></i>
Ваш выбор будет сохранён для следующего экспорта
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
Отмена
</button>
<button type="submit" class="btn btn-primary">
<i class="bi bi-download"></i> Экспортировать
</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Pre-select saved preferences from session
var preferencesJson = '{{ export_preferences|escapejs }}';
if (preferencesJson && preferencesJson !== '{}') {
try {
var preferences = JSON.parse(preferencesJson.replace(/'/g, '"'));
// Restore selected fields
if (preferences.selected_fields) {
preferences.selected_fields.forEach(function(field) {
var checkbox = document.getElementById('id_field_' + field);
if (checkbox) checkbox.checked = true;
});
}
// Restore format selection
if (preferences.format) {
var radio = document.querySelector('input[name="export_format"][value="' + preferences.format + '"]');
if (radio) radio.checked = true;
}
} catch (e) {
console.log('Could not parse export preferences:', e);
}
}
});
</script>