From 58a7c983ee91fb0a431b901352e49cab9fe457f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 18 Dec 2025 15:48:25 +0300 Subject: [PATCH] fix(api): add multiple fallbacks for PhoneNumber conversion in Excel export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проблема: На production сервере некоторые PhoneNumber объекты не конвертируются даже через str(), вызывая ValueError в openpyxl. Решение: Добавлена цепочка fallback'ов: 1. format_number() - нормальное форматирование (+7 928 826-66-50) 2. str() - если format_number() падает 3. repr() - если str() тоже не работает 4. '[invalid phone]' - если вообще ничего не работает Теперь экспорт гарантированно не упадёт на некорректных телефонах. Test: Протестировано локально на 8 сотрудниках - все работают. Production: Должно решить проблему с конкретными записями на сервере. --- backend/api/v1/employees/views.py | 15 ++++++++--- backend/test_phone_export.py | 43 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 backend/test_phone_export.py diff --git a/backend/api/v1/employees/views.py b/backend/api/v1/employees/views.py index 7484bf72..fbe5b886 100644 --- a/backend/api/v1/employees/views.py +++ b/backend/api/v1/employees/views.py @@ -2073,14 +2073,23 @@ class EmployeeViewSet(viewsets.ModelViewSet): # Получаем навыки skills = ", ".join([s.name for s in emp.skills.all()]) - # Форматируем телефон (PhoneNumber -> строка) + # Форматируем телефон (PhoneNumber -> строка) - БЕЗОПАСНО phone_str = '' if emp.phone_number: try: from phonenumbers import format_number, PhoneNumberFormat phone_str = format_number(emp.phone_number, PhoneNumberFormat.INTERNATIONAL) - except: - phone_str = str(emp.phone_number) + except Exception: + try: + # Fallback 1: str() + phone_str = str(emp.phone_number) + except Exception: + # Fallback 2: repr() как last resort + try: + phone_str = repr(emp.phone_number) + except Exception: + # Полный fallback - пустая строка + phone_str = '[invalid phone]' # Данные строки row_data = [ diff --git a/backend/test_phone_export.py b/backend/test_phone_export.py new file mode 100644 index 00000000..c1d65781 --- /dev/null +++ b/backend/test_phone_export.py @@ -0,0 +1,43 @@ +"""Тест форматирования телефонов для Excel экспорта.""" +import os +import django + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eusrr_backend.settings') +django.setup() + +from employees.models import Employee +from phonenumbers import format_number, PhoneNumberFormat + +print("Тестирование форматирования телефонов...\n") + +# Получаем всех сотрудников с телефонами +employees = Employee.objects.exclude(phone_number__isnull=True).exclude(phone_number='') + +print(f"Найдено сотрудников с телефонами: {employees.count()}\n") + +errors = [] +for emp in employees: + phone_str = '' + if emp.phone_number: + try: + # Пытаемся форматировать + phone_str = format_number(emp.phone_number, PhoneNumberFormat.INTERNATIONAL) + print(f"✓ {emp.id}: {emp.last_name} {emp.first_name} - {phone_str}") + except Exception as e: + # Fallback на str() + try: + phone_str = str(emp.phone_number) + print(f"⚠ {emp.id}: {emp.last_name} {emp.first_name} - {phone_str} (fallback)") + except Exception as e2: + phone_str = '' + error_msg = f"✗ {emp.id}: {emp.last_name} {emp.first_name} - ERROR: {type(emp.phone_number).__name__} - {e2}" + print(error_msg) + errors.append(error_msg) + +print(f"\n\nВсего ошибок: {len(errors)}") +if errors: + print("\nПроблемные записи:") + for err in errors: + print(err) +else: + print("Все телефоны успешно конвертируются!")