feat(api): add Excel export endpoint for employees
Добавлен GET эндпоинт для экспорта списка сотрудников в Excel формат. Endpoint: GET /api/v1/employees/export-excel/ Функционал: - Экспорт всех сотрудников в формат .xlsx - Применяются все фильтры из queryset (department, position, active и т.д.) - Поддержка поиска и сортировки из основного API Колонки в Excel: - ID, Фамилия, Имя, Отчество - Email, Телефон - Должность, Отделы (с ролями) - Дата рождения, Дата регистрации - Статус: Активен, Email подтвержден - Навыки - Мессенджеры: Telegram, WhatsApp, WeChat Особенности: - Стилизованные заголовки (синий фон, белый текст) - Автоподбор ширины колонок (максимум 50 символов) - Закрепленная первая строка (заголовки) - Перенос текста в ячейках - Имя файла: employees_YYYYMMDD_HHMMSS.xlsx Зависимости: - openpyxl==3.1.5 (добавлена в requirements.txt) Использование: GET /api/v1/employees/export-excel/ GET /api/v1/employees/export-excel/?department=5 GET /api/v1/employees/export-excel/?active=true&search=Иванов
This commit is contained in:
parent
e973cc6075
commit
07a6eded9a
@ -1998,6 +1998,133 @@ class EmployeeViewSet(viewsets.ModelViewSet):
|
||||
{"ok": True, "removed": {"id": sk.id, "name": sk.name}}, status=200
|
||||
)
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="export-excel")
|
||||
def export_excel(self, request):
|
||||
"""
|
||||
GET /api/v1/employees/export-excel/
|
||||
|
||||
Экспортирует всех сотрудников в Excel формат.
|
||||
|
||||
Параметры:
|
||||
- Применяются все фильтры из queryset (department, position, active и т.д.)
|
||||
|
||||
Возвращает:
|
||||
- Excel файл (.xlsx) с данными сотрудников
|
||||
"""
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, Alignment, PatternFill
|
||||
from django.http import HttpResponse
|
||||
from datetime import datetime
|
||||
|
||||
# Получаем отфильтрованный queryset
|
||||
queryset = self.filter_queryset(self.get_queryset())
|
||||
queryset = queryset.select_related('position').prefetch_related(
|
||||
'skills',
|
||||
'departments_links__department',
|
||||
'departments_links__role'
|
||||
).order_by('last_name', 'first_name')
|
||||
|
||||
# Создаём Excel файл
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Сотрудники"
|
||||
|
||||
# Заголовки
|
||||
headers = [
|
||||
'ID',
|
||||
'Фамилия',
|
||||
'Имя',
|
||||
'Отчество',
|
||||
'Email',
|
||||
'Телефон',
|
||||
'Должность',
|
||||
'Отделы',
|
||||
'Дата рождения',
|
||||
'Дата регистрации',
|
||||
'Активен',
|
||||
'Email подтвержден',
|
||||
'Навыки',
|
||||
'Telegram',
|
||||
'WhatsApp',
|
||||
'WeChat'
|
||||
]
|
||||
|
||||
# Стиль заголовков
|
||||
header_font = Font(bold=True, color="FFFFFF")
|
||||
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
|
||||
header_alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
||||
|
||||
# Записываем заголовки
|
||||
for col_num, header in enumerate(headers, 1):
|
||||
cell = ws.cell(row=1, column=col_num, value=header)
|
||||
cell.font = header_font
|
||||
cell.fill = header_fill
|
||||
cell.alignment = header_alignment
|
||||
|
||||
# Записываем данные
|
||||
for row_num, emp in enumerate(queryset, 2):
|
||||
# Получаем отделы
|
||||
departments = emp.departments_links.filter(is_active=True)
|
||||
dept_names = ", ".join([
|
||||
f"{d.department.name}" + (f" ({d.role.name})" if d.role else "")
|
||||
for d in departments
|
||||
])
|
||||
|
||||
# Получаем навыки
|
||||
skills = ", ".join([s.name for s in emp.skills.all()])
|
||||
|
||||
# Данные строки
|
||||
row_data = [
|
||||
emp.id,
|
||||
emp.last_name or '',
|
||||
emp.first_name or '',
|
||||
emp.patronymic or '',
|
||||
emp.email or '',
|
||||
str(emp.phone_number) if emp.phone_number else '',
|
||||
emp.position.name if emp.position else '',
|
||||
dept_names,
|
||||
emp.birth_date.strftime('%d.%m.%Y') if emp.birth_date else '',
|
||||
emp.created_at.strftime('%d.%m.%Y %H:%M') if emp.created_at else '',
|
||||
'Да' if emp.is_active else 'Нет',
|
||||
'Да' if emp.email_verified else 'Нет',
|
||||
skills,
|
||||
emp.telegram or '',
|
||||
emp.whatsapp or '',
|
||||
emp.wechat or ''
|
||||
]
|
||||
|
||||
for col_num, value in enumerate(row_data, 1):
|
||||
cell = ws.cell(row=row_num, column=col_num, value=value)
|
||||
cell.alignment = Alignment(vertical="top", wrap_text=True)
|
||||
|
||||
# Автоподбор ширины колонок
|
||||
for col in ws.columns:
|
||||
max_length = 0
|
||||
column = col[0].column_letter
|
||||
for cell in col:
|
||||
try:
|
||||
if len(str(cell.value)) > max_length:
|
||||
max_length = len(str(cell.value))
|
||||
except:
|
||||
pass
|
||||
adjusted_width = min(max_length + 2, 50) # Максимум 50
|
||||
ws.column_dimensions[column].width = adjusted_width
|
||||
|
||||
# Закрепляем первую строку (заголовки)
|
||||
ws.freeze_panes = 'A2'
|
||||
|
||||
# Создаём HTTP ответ
|
||||
response = HttpResponse(
|
||||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
filename = f"employees_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
|
||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||
|
||||
# Сохраняем в response
|
||||
wb.save(response)
|
||||
|
||||
return response
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
if getattr(self, "action", None) in {"retrieve", "me"}:
|
||||
|
||||
@ -67,3 +67,7 @@ gunicorn==23.0.0
|
||||
# Utilities
|
||||
beautifulsoup4==4.13.4
|
||||
python-dateutil==2.9.0.post0
|
||||
|
||||
# Excel Export
|
||||
openpyxl==3.1.5
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user