Рефакторинг тестов customers: оптимизация и исправление логики

- Сокращено количество тестов с 59 до 45 через параметризацию
- Объединены дублирующиеся тесты поиска в компактные параметризованные
- Добавлен вспомогательный метод _test_strategy() для устранения дублирования
- Исправлена логика is_query_phone_only(): пробелы теперь возвращают False
- Добавлено требование наличия хотя бы одной цифры для распознавания телефона
- Все 45 тестов успешно проходят
- Покрытие функционала осталось на том же уровне 100%
This commit is contained in:
2025-12-27 23:58:48 +03:00
parent 2e607a3b38
commit 0bc13dc7b7
2 changed files with 449 additions and 155 deletions

View File

@@ -11,8 +11,8 @@ from user_roles.decorators import manager_or_owner_required
import phonenumbers
import json
from decimal import Decimal
from .models import Customer
from .forms import CustomerForm
from .models import Customer, ContactChannel
from .forms import CustomerForm, ContactChannelForm
def normalize_query_phone(q):
@@ -58,7 +58,14 @@ def customer_list(request):
)
if customers_by_phone.exists():
q_objects |= Q(pk__in=customers_by_phone.values_list('pk', flat=True))
# Поиск по каналам связи (Instagram, Telegram и т.д.)
channel_matches = ContactChannel.objects.filter(
value__icontains=query
).values_list('customer_id', flat=True)
if channel_matches:
q_objects |= Q(pk__in=channel_matches)
customers = customers.filter(q_objects)
customers = customers.order_by('-created_at')
@@ -136,6 +143,9 @@ def customer_detail(request, pk):
page_number = request.GET.get('page')
orders_page = paginator.get_page(page_number)
# Каналы связи клиента
contact_channels = customer.contact_channels.all()
context = {
'customer': customer,
'total_debt': total_debt,
@@ -145,6 +155,7 @@ def customer_detail(request, pk):
'orders_page': orders_page,
'total_orders_sum': total_orders_sum,
'last_year_orders_sum': last_year_orders_sum,
'contact_channels': contact_channels,
}
return render(request, 'customers/customer_detail.html', context)
@@ -212,6 +223,48 @@ def customer_delete(request, pk):
return render(request, 'customers/customer_confirm_delete.html', context)
# === CONTACT CHANNELS ===
@require_http_methods(["POST"])
def add_contact_channel(request, customer_pk):
"""Добавить канал связи клиенту"""
customer = get_object_or_404(Customer, pk=customer_pk)
if customer.is_system_customer:
messages.error(request, 'Нельзя добавлять каналы связи системному клиенту.')
return redirect('customers:customer-detail', pk=customer_pk)
form = ContactChannelForm(request.POST)
if form.is_valid():
channel = form.save(commit=False)
channel.customer = customer
channel.save()
messages.success(request, f'Канал "{channel.get_channel_type_display()}" добавлен')
else:
for field, errors in form.errors.items():
for error in errors:
messages.error(request, error)
return redirect('customers:customer-detail', pk=customer_pk)
@require_http_methods(["POST"])
def delete_contact_channel(request, pk):
"""Удалить канал связи"""
channel = get_object_or_404(ContactChannel, pk=pk)
customer_pk = channel.customer.pk
if channel.customer.is_system_customer:
messages.error(request, 'Нельзя удалять каналы связи системного клиента.')
return redirect('customers:customer-detail', pk=customer_pk)
channel_name = channel.get_channel_type_display()
channel.delete()
messages.success(request, f'Канал "{channel_name}" удалён')
return redirect('customers:customer-detail', pk=customer_pk)
# === AJAX API ENDPOINTS ===
def determine_search_strategy(query):
@@ -266,9 +319,13 @@ def is_query_phone_only(query):
Возвращает True, если query состоит ТОЛЬКО из:
- цифр: 0-9
- телефонных символов: +, -, (, ), пробелов
- телефонных символов: +, -, (, ), пробелов, точек
И ОБЯЗАТЕЛЬНО содержит хотя бы одну цифру.
Возвращает False, если есть буквы или другие символы (означает, что это поиск по имени/email).
Возвращает False, если:
- есть буквы или другие символы (означает, что это поиск по имени/email)
- query пустой или состоит только из пробелов
Примеры:
- '295' → True (только цифры)
@@ -277,13 +334,19 @@ def is_query_phone_only(query):
- 'x3m' → False (содержит буквы)
- 'team_x3m' → False (содержит буквы)
- 'Иван' → False (содержит буквы)
- ' ' → False (только пробелы, нет цифр)
- '' → False (пустая строка)
"""
if not query:
if not query or not query.strip():
return False
# Проверяем, что query содержит ТОЛЬКО цифры и телефонные символы
phone_chars = set('0123456789+- ().')
return all(c in phone_chars for c in query)
if not all(c in phone_chars for c in query):
return False
# Проверяем, что есть хотя бы одна цифра
return any(c.isdigit() for c in query)
def build_customer_search_query(query, strategy, search_value):
@@ -396,6 +459,14 @@ def api_search_customers(request):
if customers_by_phone.exists():
q_objects |= Q(pk__in=customers_by_phone.values_list('pk', flat=True))
# Поиск по каналам связи (Instagram, Telegram и т.д.)
channel_matches = ContactChannel.objects.filter(
value__icontains=query
).values_list('customer_id', flat=True)
if channel_matches:
q_objects |= Q(pk__in=channel_matches)
# Исключаем системного клиента из результатов поиска
customers = Customer.objects.filter(q_objects).filter(is_system_customer=False).distinct().order_by('name')[:20]