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"]) 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) # Для поиска по телефону: извлекаем только цифры и ищем по ним # Это позволит найти клиента независимо от формата ввода query_digits = ''.join(c for c in query if c.isdigit()) # Ищем по имени, email или телефону # Используем Q-объекты для OR условий # Для email используем iexact (точное совпадение без учета регистра) q_objects = Q(name__icontains=query) | Q(email__iexact=query) # Для телефона ищем по нормализованному номеру и по цифрам if phone_normalized: q_objects |= Q(phone__icontains=phone_normalized) if query_digits: # Ищем клиентов, чьи телефоны содержат введенные цифры customers_by_phone = Customer.objects.filter(phone__isnull=False) matching_by_digits = [] for customer in customers_by_phone: customer_digits = ''.join(c for c in str(customer.phone) if c.isdigit()) if query_digits in customer_digits: matching_by_digits.append(customer.pk) if matching_by_digits: q_objects |= Q(pk__in=matching_by_digits) customers = Customer.objects.filter(q_objects).distinct().order_by('name')[: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"]) 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)