Добавлена функциональность системного клиента для анонимных покупок
- Добавлено поле is_system_customer в модель Customer с индексом - Системный клиент создается автоматически при создании нового тенанта - Реализована защита системного клиента от редактирования и удаления: - Защита на уровне модели (save/delete методы) - Защита на уровне формы (валидация) - Защита на уровне представлений (проверки с дружественными сообщениями) - Защита в админке (readonly поля, запрет удаления) - Системный клиент скрыт из списков и поиска на фронтенде - Создан информационный шаблон для отображения системного клиента - Исправлена обработка NULL значений для полей email/phone (Django best practice) - Добавлено отображение "Не указано" вместо None в карточке клиента 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -26,7 +26,8 @@ def normalize_query_phone(q):
|
||||
def customer_list(request):
|
||||
"""Список всех клиентов"""
|
||||
query = request.GET.get('q')
|
||||
customers = Customer.objects.all()
|
||||
# Исключаем системного клиента из списка
|
||||
customers = Customer.objects.filter(is_system_customer=False)
|
||||
|
||||
if query:
|
||||
# Используем ту же логику поиска, что и в AJAX API (api_search_customers)
|
||||
@@ -80,6 +81,10 @@ def customer_detail(request, pk):
|
||||
"""Детали клиента"""
|
||||
customer = get_object_or_404(Customer, pk=pk)
|
||||
|
||||
# Для системного клиента показываем специальную заглушку
|
||||
if customer.is_system_customer:
|
||||
return render(request, 'customers/customer_system.html')
|
||||
|
||||
context = {
|
||||
'customer': customer,
|
||||
}
|
||||
@@ -104,12 +109,20 @@ def customer_update(request, pk):
|
||||
"""Редактирование клиента"""
|
||||
customer = get_object_or_404(Customer, pk=pk)
|
||||
|
||||
# Проверяем, не системный ли это клиент
|
||||
if customer.is_system_customer:
|
||||
messages.warning(request, 'Системный клиент не может быть изменен. Он создается автоматически и необходим для корректной работы системы.')
|
||||
return redirect('customers:customer-detail', 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)
|
||||
try:
|
||||
form.save()
|
||||
messages.success(request, f'Клиент {customer.full_name} успешно обновлён.')
|
||||
return redirect('customers:customer-detail', pk=customer.pk)
|
||||
except ValidationError as e:
|
||||
messages.error(request, str(e))
|
||||
else:
|
||||
form = CustomerForm(instance=customer)
|
||||
|
||||
@@ -120,11 +133,20 @@ def customer_delete(request, pk):
|
||||
"""Удаление клиента"""
|
||||
customer = get_object_or_404(Customer, pk=pk)
|
||||
|
||||
# Проверяем, не системный ли это клиент
|
||||
if customer.is_system_customer:
|
||||
messages.error(request, 'Невозможно удалить системного клиента. Он необходим для корректной работы системы.')
|
||||
return redirect('customers:customer-detail', pk=pk)
|
||||
|
||||
if request.method == 'POST':
|
||||
customer_name = customer.full_name
|
||||
customer.delete()
|
||||
messages.success(request, f'Клиент {customer_name} успешно удален.')
|
||||
return redirect('customers:customer-list')
|
||||
try:
|
||||
customer.delete()
|
||||
messages.success(request, f'Клиент {customer_name} успешно удален.')
|
||||
return redirect('customers:customer-list')
|
||||
except ValidationError as e:
|
||||
messages.error(request, str(e))
|
||||
return redirect('customers:customer-detail', pk=pk)
|
||||
|
||||
context = {
|
||||
'customer': customer
|
||||
@@ -316,7 +338,8 @@ def api_search_customers(request):
|
||||
if customers_by_phone.exists():
|
||||
q_objects |= Q(pk__in=customers_by_phone.values_list('pk', flat=True))
|
||||
|
||||
customers = Customer.objects.filter(q_objects).distinct().order_by('name')[:20]
|
||||
# Исключаем системного клиента из результатов поиска
|
||||
customers = Customer.objects.filter(q_objects).filter(is_system_customer=False).distinct().order_by('name')[:20]
|
||||
|
||||
results = []
|
||||
|
||||
@@ -442,46 +465,3 @@ def api_create_customer(request):
|
||||
'success': False,
|
||||
'error': f'Ошибка сервера: {str(e)}'
|
||||
}, status=500)
|
||||
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
def api_create_system_customer(request):
|
||||
"""
|
||||
Создать или получить системного анонимного клиента для POS.
|
||||
|
||||
Идентификаторы системного клиента:
|
||||
- email: system@pos.customer
|
||||
- name: АНОНИМНЫЙ ПОКУПАТЕЛЬ (POS)
|
||||
- loyalty_tier: 'no_discount'
|
||||
- notes: 'SYSTEM_CUSTOMER'
|
||||
|
||||
Поведение:
|
||||
- Если клиент уже существует (по уникальному email), новый не создаётся.
|
||||
- Если не существует — создаётся с указанными полями.
|
||||
- Возвращает JSON с признаком, был ли создан новый клиент.
|
||||
|
||||
Возвращаемый JSON:
|
||||
{
|
||||
"success": true,
|
||||
"created": false, # или true, если впервые создан
|
||||
"id": 123,
|
||||
"name": "АНОНИМНЫЙ ПОКУПАТЕЛЬ (POS)",
|
||||
"email": "system@pos.customer"
|
||||
}
|
||||
"""
|
||||
customer, created = Customer.objects.get_or_create(
|
||||
email="system@pos.customer",
|
||||
defaults={
|
||||
"name": "АНОНИМНЫЙ ПОКУПАТЕЛЬ (POS)",
|
||||
"loyalty_tier": "no_discount",
|
||||
"notes": "SYSTEM_CUSTOMER",
|
||||
},
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
"success": True,
|
||||
"created": created,
|
||||
"id": customer.pk,
|
||||
"name": customer.name,
|
||||
"email": customer.email,
|
||||
})
|
||||
Reference in New Issue
Block a user