Реализованы следующие функции: - AJAX API endpoint для поиска клиента по имени, телефону или email одновременно - AJAX API endpoint для создания нового клиента прямо при создании заказа - Интерактивная форма поиска в поле "Клиент" с использованием Select2 - При отсутствии результатов поиска предлагается создать нового клиента с автоматическим заполнением формы введенными данными - Модальное окно для создания клиента во всплывающем окне (не на отдельной странице) - Автоматический выбор созданного клиента после сохранения Изменения: 1. customers/views.py - добавлены endpoints api_search_customers и api_create_customer 2. customers/urls.py - добавлены URL маршруты для новых endpoints 3. orders/templates/orders/order_form.html - обновлена инициализация Select2 для поиска, добавлено модальное окно и стили 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
281 lines
9.0 KiB
Python
281 lines
9.0 KiB
Python
from django.shortcuts import render, get_object_or_404, redirect
|
||
from django.contrib import messages
|
||
from django.core.paginator import Paginator
|
||
from django.core.exceptions import ValidationError
|
||
from django.db.models import Q
|
||
from django.http import JsonResponse
|
||
from django.views.decorators.http import require_http_methods
|
||
from django.contrib.auth.decorators import login_required
|
||
import phonenumbers
|
||
import json
|
||
from .models import Customer, Address
|
||
from .forms import CustomerForm
|
||
|
||
|
||
def normalize_query_phone(q):
|
||
"""Normalize phone number for search"""
|
||
try:
|
||
parsed = phonenumbers.parse(q, "BY")
|
||
if phonenumbers.is_valid_number(parsed):
|
||
return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
|
||
return q
|
||
except:
|
||
return q
|
||
|
||
|
||
def customer_list(request):
|
||
"""Список всех клиентов"""
|
||
query = request.GET.get('q')
|
||
customers = Customer.objects.all()
|
||
|
||
if query:
|
||
# Try to normalize the phone number for searching
|
||
phone_normalized = normalize_query_phone(query)
|
||
customers = customers.filter(
|
||
Q(name__icontains=query) |
|
||
Q(email__icontains=query) |
|
||
Q(phone__icontains=phone_normalized)
|
||
)
|
||
|
||
customers = customers.order_by('-created_at')
|
||
|
||
# Пагинация
|
||
paginator = Paginator(customers, 25) # 25 клиентов на страницу
|
||
page_number = request.GET.get('page')
|
||
page_obj = paginator.get_page(page_number)
|
||
|
||
context = {
|
||
'page_obj': page_obj,
|
||
'query': query,
|
||
}
|
||
return render(request, 'customers/customer_list.html', context)
|
||
|
||
|
||
def customer_detail(request, pk):
|
||
"""Детали клиента"""
|
||
customer = get_object_or_404(Customer, pk=pk)
|
||
addresses = customer.addresses.all()
|
||
|
||
context = {
|
||
'customer': customer,
|
||
'addresses': addresses,
|
||
}
|
||
return render(request, 'customers/customer_detail.html', context)
|
||
|
||
|
||
def customer_create(request):
|
||
"""Создание нового клиента"""
|
||
if request.method == 'POST':
|
||
form = CustomerForm(request.POST)
|
||
if form.is_valid():
|
||
customer = form.save()
|
||
messages.success(request, f'Клиент {customer.full_name} успешно создан.')
|
||
return redirect('customers:customer-detail', pk=customer.pk)
|
||
else:
|
||
form = CustomerForm()
|
||
|
||
return render(request, 'customers/customer_form.html', {'form': form, 'is_creating': True})
|
||
|
||
|
||
def customer_update(request, pk):
|
||
"""Редактирование клиента"""
|
||
customer = get_object_or_404(Customer, pk=pk)
|
||
|
||
if request.method == 'POST':
|
||
form = CustomerForm(request.POST, instance=customer)
|
||
if form.is_valid():
|
||
form.save()
|
||
messages.success(request, f'Клиент {customer.full_name} успешно обновлён.')
|
||
return redirect('customers:customer-detail', pk=customer.pk)
|
||
else:
|
||
form = CustomerForm(instance=customer)
|
||
|
||
return render(request, 'customers/customer_form.html', {'form': form, 'is_creating': False})
|
||
|
||
|
||
def customer_delete(request, pk):
|
||
"""Удаление клиента"""
|
||
customer = get_object_or_404(Customer, pk=pk)
|
||
|
||
if request.method == 'POST':
|
||
customer_name = customer.full_name
|
||
customer.delete()
|
||
messages.success(request, f'Клиент {customer_name} успешно удален.')
|
||
return redirect('customers:customer-list')
|
||
|
||
context = {
|
||
'customer': customer
|
||
}
|
||
return render(request, 'customers/customer_confirm_delete.html', context)
|
||
|
||
|
||
# === AJAX API ENDPOINTS ===
|
||
|
||
@require_http_methods(["GET"])
|
||
@login_required
|
||
def api_search_customers(request):
|
||
"""
|
||
AJAX endpoint для поиска клиента по имени, телефону или email.
|
||
|
||
Параметры GET:
|
||
- q: поисковая строка
|
||
|
||
Возвращает JSON с результатами поиска:
|
||
{
|
||
"results": [
|
||
{"id": 1, "text": "Иван Петров (+375291234567)", "name": "Иван Петров", "phone": "+375291234567", "email": "ivan@example.com"},
|
||
...
|
||
],
|
||
"pagination": {"more": false}
|
||
}
|
||
|
||
Если ничего не найдено и заданы параметры поиска, возвращает:
|
||
{
|
||
"results": [
|
||
{"id": null, "text": "Создать клиента: 'Поиск'", "is_create_option": true, "search_text": "Поиск"}
|
||
],
|
||
"pagination": {"more": false}
|
||
}
|
||
"""
|
||
query = request.GET.get('q', '').strip()
|
||
|
||
if not query or len(query) < 1:
|
||
return JsonResponse({
|
||
'results': [],
|
||
'pagination': {'more': False}
|
||
})
|
||
|
||
# Пытаемся нормализовать номер телефона для поиска
|
||
phone_normalized = normalize_query_phone(query)
|
||
|
||
# Ищем по имени, email или телефону
|
||
customers = Customer.objects.filter(
|
||
Q(name__icontains=query) |
|
||
Q(email__icontains=query) |
|
||
Q(phone__icontains=phone_normalized)
|
||
).order_by('name')[:20] # Ограничиваем 20 результатами
|
||
|
||
results = []
|
||
|
||
# Добавляем найденные клиентов
|
||
for customer in customers:
|
||
phone_display = str(customer.phone) if customer.phone else ''
|
||
text = customer.name
|
||
if phone_display:
|
||
text += f' ({phone_display})'
|
||
|
||
results.append({
|
||
'id': customer.pk,
|
||
'text': text,
|
||
'name': customer.name,
|
||
'phone': phone_display,
|
||
'email': customer.email,
|
||
})
|
||
|
||
# Если ничего не найдено, предлагаем создать нового клиента
|
||
if not results:
|
||
results.append({
|
||
'id': None,
|
||
'text': f'Создать клиента: "{query}"',
|
||
'is_create_option': True,
|
||
'search_text': query,
|
||
})
|
||
|
||
return JsonResponse({
|
||
'results': results,
|
||
'pagination': {'more': False}
|
||
})
|
||
|
||
|
||
@require_http_methods(["POST"])
|
||
@login_required
|
||
def api_create_customer(request):
|
||
"""
|
||
AJAX endpoint для создания нового клиента.
|
||
|
||
Принимает POST JSON:
|
||
{
|
||
"name": "Иван Петров",
|
||
"phone": "+375291234567",
|
||
"email": "ivan@example.com"
|
||
}
|
||
|
||
Возвращает JSON:
|
||
{
|
||
"success": true,
|
||
"id": 123,
|
||
"name": "Иван Петров",
|
||
"phone": "+375291234567",
|
||
"email": "ivan@example.com"
|
||
}
|
||
|
||
При ошибке:
|
||
{
|
||
"success": false,
|
||
"error": "Клиент с таким номером телефона уже существует"
|
||
}
|
||
"""
|
||
try:
|
||
data = json.loads(request.body)
|
||
|
||
name = data.get('name', '').strip()
|
||
phone = data.get('phone', '').strip()
|
||
email = data.get('email', '').strip()
|
||
|
||
# Валидация: имя обязательно
|
||
if not name:
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': 'Имя клиента обязательно'
|
||
}, status=400)
|
||
|
||
# Нормализуем телефон если он указан
|
||
if phone:
|
||
phone = normalize_query_phone(phone)
|
||
|
||
# Проверяем, не существует ли уже клиент с таким телефоном
|
||
if phone and Customer.objects.filter(phone=phone).exists():
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': 'Клиент с таким номером телефона уже существует'
|
||
}, status=400)
|
||
|
||
# Проверяем, не существует ли уже клиент с таким email
|
||
if email and Customer.objects.filter(email=email).exists():
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': 'Клиент с таким email уже существует'
|
||
}, status=400)
|
||
|
||
# Создаем нового клиента
|
||
customer = Customer.objects.create(
|
||
name=name,
|
||
phone=phone if phone else None,
|
||
email=email if email else None
|
||
)
|
||
|
||
phone_display = str(customer.phone) if customer.phone else ''
|
||
|
||
return JsonResponse({
|
||
'success': True,
|
||
'id': customer.pk,
|
||
'name': customer.name,
|
||
'phone': phone_display,
|
||
'email': customer.email,
|
||
}, status=201)
|
||
|
||
except json.JSONDecodeError:
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': 'Некорректный JSON'
|
||
}, status=400)
|
||
except ValidationError as e:
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': str(e)
|
||
}, status=400)
|
||
except Exception as e:
|
||
return JsonResponse({
|
||
'success': False,
|
||
'error': f'Ошибка сервера: {str(e)}'
|
||
}, status=500) |