Добавлен настраиваемый экспорт клиентов с выбором полей и форматов

Реализован полностью новый функционал экспорта клиентов с возможностью
выбора полей, формата файла (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:
2026-01-03 21:12:08 +03:00
parent 0f09702094
commit 95036ed285
5 changed files with 416 additions and 51 deletions

View File

@@ -7,7 +7,7 @@ from django.db.models.functions import Greatest, Coalesce
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.contrib.auth.decorators import login_required
from user_roles.decorators import manager_or_owner_required
from user_roles.decorators import manager_or_owner_required, owner_required
import phonenumbers
import json
from decimal import Decimal
@@ -80,11 +80,18 @@ def customer_list(request):
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
# Подготовка формы экспорта и настроек из сессии
from .forms import CustomerExportForm
export_form = CustomerExportForm(user=request.user)
export_preferences = request.session.get('customer_export_preferences', {})
context = {
'page_obj': page_obj,
'query': query,
'total_customers': paginator.count, # Используем count из paginator, чтобы избежать дублирования SQL запроса
'filter': customer_filter, # Добавляем фильтр в контекст
'export_form': export_form, # Форма экспорта для модального окна
'export_preferences': export_preferences, # Сохранённые настройки экспорта
}
return render(request, 'customers/customer_list.html', context)
@@ -886,12 +893,65 @@ def customer_import_download_errors(request):
@login_required
@manager_or_owner_required
@owner_required
def customer_export(request):
"""
Экспорт клиентов в CSV файл.
Экспорт клиентов в CSV/XLSX файл.
GET: Перенаправление на список клиентов
POST: Обработка экспорта с выбранными полями и форматом
Поддерживает фильтрацию - экспортирует только клиентов, соответствующих текущим фильтрам.
Доступен только владельцу (OWNER) и superuser.
"""
from .services.import_export import CustomerExporter
exporter = CustomerExporter()
return exporter.export_to_csv()
from .forms import CustomerExportForm
from .filters import CustomerFilter
# Базовый queryset (исключаем системного клиента)
queryset = Customer.objects.filter(is_system_customer=False)
# Применяем фильтры (та же логика что в customer_list)
customer_filter = CustomerFilter(request.GET, queryset=queryset)
filtered_queryset = customer_filter.qs
# GET запрос: перенаправление на список клиентов
if request.method != 'POST':
messages.info(
request,
'Используйте кнопку "Экспорт" в списке клиентов для настройки экспорта.'
)
return redirect('customers:customer-list')
# POST запрос: обработка экспорта
form = CustomerExportForm(request.POST, user=request.user)
if not form.is_valid():
messages.error(request, 'Ошибка в настройках экспорта. Выберите хотя бы одно поле.')
return redirect('customers:customer-list')
# Получение конфигурации экспорта
selected_fields = form.cleaned_data['selected_fields']
export_format = form.cleaned_data['export_format']
# Сохранение настроек в сессии
request.session['customer_export_preferences'] = {
'selected_fields': selected_fields,
'format': export_format,
}
# Оптимизация запроса (prefetch contact channels)
filtered_queryset = filtered_queryset.prefetch_related('contact_channels').order_by('-created_at')
# Создание экспортера с отфильтрованным queryset
exporter = CustomerExporter(
queryset=filtered_queryset,
selected_fields=selected_fields,
user=request.user
)
# Генерация и возврат файла экспорта
if export_format == 'xlsx':
return exporter.export_to_xlsx()
else:
return exporter.export_to_csv()