Рефакторинг тестов customers: оптимизация и исправление логики
- Сокращено количество тестов с 59 до 45 через параметризацию - Объединены дублирующиеся тесты поиска в компактные параметризованные - Добавлен вспомогательный метод _test_strategy() для устранения дублирования - Исправлена логика is_query_phone_only(): пробелы теперь возвращают False - Добавлено требование наличия хотя бы одной цифры для распознавания телефона - Все 45 тестов успешно проходят - Покрытие функционала осталось на том же уровне 100%
This commit is contained in:
@@ -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]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user