Добавлен настраиваемый экспорт клиентов с выбором полей и форматов
Реализован полностью новый функционал экспорта клиентов с возможностью
выбора полей, формата файла (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>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<!-- 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>
|
||||
@@ -19,9 +19,11 @@
|
||||
<a href="{% url 'customers:customer-import' %}" class="btn btn-outline-success">
|
||||
<i class="bi bi-upload"></i> Импорт
|
||||
</a>
|
||||
<a href="{% url 'customers:customer-export' %}" class="btn btn-outline-info">
|
||||
<i class="bi bi-download"></i> Экспорт
|
||||
</a>
|
||||
{% if user.is_owner or user.is_superuser %}
|
||||
<button type="button" class="btn btn-outline-info" data-bs-toggle="modal" data-bs-target="#exportModal">
|
||||
<i class="bi bi-download"></i> Экспорт
|
||||
</button>
|
||||
{% endif %}
|
||||
<a href="{% url 'customers:customer-create' %}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-circle"></i> Добавить клиента
|
||||
</a>
|
||||
@@ -207,4 +209,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'customers/customer_export_modal.html' %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user