Устранение дублирования логики поиска между customer_list() и api_search_customers()

Проблема: Было две разных реализации логики поиска:
- customer_list() использовала простой icontains везде
- api_search_customers() использовала новую smart-логику с determine_search_strategy()

Решение:
1. Создана функция build_customer_search_query() которая строит Q-объект
   на основе стратегии поиска

2. Обновлена customer_list() чтобы использовать:
   - determine_search_strategy() для определения стратегии
   - build_customer_search_query() для построения Q-объекта

3. Обновлена api_search_customers() чтобы использовать
   build_customer_search_query() вместо дублирования логики

Результат: ЕДИНАЯ логика поиска везде ✓

Архитектура:
1. normalize_query_phone() — нормализация номеров телефонов
2. determine_search_strategy() — определение стратегии поиска
3. build_customer_search_query() — построение Q-объекта ← NEW
4. customer_list() — используется в веб-интерфейсе списка клиентов
5. api_search_customers() — используется в AJAX для Select2

Все 23 unit-теста проходят успешно ✓

Преимущества:
- Единая логика поиска во всем приложении
- Легче поддерживать и расширять
- Новая функция можно переиспользовать в других местах
- Меньше дублирования кода

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-11 00:52:12 +03:00
parent 9fc0af2c2e
commit 96aa0b2f7f

View File

@@ -29,13 +29,23 @@ def customer_list(request):
customers = Customer.objects.all()
if query:
# Try to normalize the phone number for searching
# Используем ту же логику поиска, что и в AJAX API (api_search_customers)
# Это обеспечивает согласованность между веб-интерфейсом и API
# Нормализуем номер телефона
phone_normalized = normalize_query_phone(query)
customers = customers.filter(
Q(name__icontains=query) |
Q(email__icontains=query) |
Q(phone__icontains=phone_normalized)
)
# Определяем стратегию поиска
strategy, search_value = determine_search_strategy(query)
# Строим Q-объект для поиска (единая функция)
q_objects = build_customer_search_query(query, strategy, search_value)
# Добавляем поиск по телефону
if phone_normalized:
q_objects |= Q(phone__icontains=phone_normalized)
customers = customers.filter(q_objects)
customers = customers.order_by('-created_at')
@@ -157,6 +167,44 @@ def determine_search_strategy(query):
return ('name_only', query)
def build_customer_search_query(query, strategy, search_value):
"""
Строит Q-объект для поиска клиентов на основе стратегии.
Используется в customer_list() и api_search_customers() для единообразия.
Args:
query: Исходный поисковый запрос (для fallback)
strategy: Стратегия поиска (из determine_search_strategy)
search_value: Значение для поиска (из determine_search_strategy)
Returns:
Q-объект для фильтрации Customer.objects
"""
if strategy == 'name_only':
return Q(name__icontains=search_value)
elif strategy == 'email_prefix':
# Query вида "team_x3m@" — ищем email, начинающиеся с "team_x3m"
return Q(email__istartswith=search_value)
elif strategy == 'email_domain':
# Query вида "@bk" — ищем все email с доменом "@bk.*"
return Q(email__icontains=f'@{search_value}')
elif strategy == 'email_full':
# Query вида "test@bk.ru" — полный поиск по email
return Q(email__icontains=search_value)
elif strategy == 'universal':
# Query вида "natul" (3+ символов) — ищем везде
return Q(name__icontains=search_value) | Q(email__icontains=search_value)
else:
# На случай неизвестной стратегии (не должно быть)
return Q(name__icontains=query)
@require_http_methods(["GET"])
def api_search_customers(request):
"""
@@ -203,31 +251,8 @@ def api_search_customers(request):
# Определяем стратегию поиска на основе содержимого query
strategy, search_value = determine_search_strategy(query)
# Строим Q-объект в зависимости от стратегии
if strategy == 'name_only':
# Поиск только по имени (для коротких запросов)
q_objects = Q(name__icontains=search_value)
elif strategy == 'email_prefix':
# Query вида "team_x3m@" — ищем email, начинающиеся с "team_x3m"
# Это решает проблему: не найдёт "natulj@bk.ru"
q_objects = Q(email__istartswith=search_value)
elif strategy == 'email_domain':
# Query вида "@bk" — ищем все email с доменом "@bk.*"
q_objects = Q(email__icontains=f'@{search_value}')
elif strategy == 'email_full':
# Query вида "test@bk.ru" — полный поиск по email
q_objects = Q(email__icontains=search_value)
elif strategy == 'universal':
# Query вида "natul" (3+ символов) — ищем везде
q_objects = Q(name__icontains=search_value) | Q(email__icontains=search_value)
else:
# На случай неизвестной стратегии (не должно быть)
q_objects = Q(name__icontains=query)
# Строим Q-объект для поиска (единая функция, используется везде)
q_objects = build_customer_search_query(query, strategy, search_value)
# Для телефона ищем по нормализованному номеру и по цифрам
if phone_normalized: