Добавлена функциональность системного клиента для анонимных покупок
- Добавлено поле 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:
@@ -21,6 +21,24 @@ class IsVipFilter(admin.SimpleListFilter):
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class IsSystemCustomerFilter(admin.SimpleListFilter):
|
||||||
|
title = 'Системный клиент'
|
||||||
|
parameter_name = 'is_system_customer'
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return (
|
||||||
|
('yes', 'Системный'),
|
||||||
|
('no', 'Обычный'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
if self.value() == 'yes':
|
||||||
|
return queryset.filter(is_system_customer=True)
|
||||||
|
if self.value() == 'no':
|
||||||
|
return queryset.filter(is_system_customer=False)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Customer)
|
@admin.register(Customer)
|
||||||
class CustomerAdmin(admin.ModelAdmin):
|
class CustomerAdmin(admin.ModelAdmin):
|
||||||
"""Административный интерфейс для управления клиентами цветочного магазина"""
|
"""Административный интерфейс для управления клиентами цветочного магазина"""
|
||||||
@@ -31,11 +49,13 @@ class CustomerAdmin(admin.ModelAdmin):
|
|||||||
'loyalty_tier',
|
'loyalty_tier',
|
||||||
'total_spent',
|
'total_spent',
|
||||||
'is_vip',
|
'is_vip',
|
||||||
|
'is_system_customer',
|
||||||
'created_at'
|
'created_at'
|
||||||
)
|
)
|
||||||
list_filter = (
|
list_filter = (
|
||||||
'loyalty_tier',
|
'loyalty_tier',
|
||||||
IsVipFilter,
|
IsVipFilter,
|
||||||
|
IsSystemCustomerFilter,
|
||||||
'created_at'
|
'created_at'
|
||||||
)
|
)
|
||||||
search_fields = (
|
search_fields = (
|
||||||
@@ -45,19 +65,45 @@ class CustomerAdmin(admin.ModelAdmin):
|
|||||||
)
|
)
|
||||||
date_hierarchy = 'created_at'
|
date_hierarchy = 'created_at'
|
||||||
ordering = ('-created_at',)
|
ordering = ('-created_at',)
|
||||||
readonly_fields = ('created_at', 'updated_at', 'total_spent', 'is_vip')
|
readonly_fields = ('created_at', 'updated_at', 'total_spent', 'is_vip', 'is_system_customer')
|
||||||
|
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Основная информация', {
|
('Основная информация', {
|
||||||
'fields': ('name', 'email', 'phone')
|
'fields': ('name', 'email', 'phone', 'is_system_customer')
|
||||||
}),
|
}),
|
||||||
('Программа лояльности', {
|
('Программа лояльности', {
|
||||||
'fields': ('loyalty_tier', 'total_spent', 'is_vip'),
|
'fields': ('loyalty_tier', 'total_spent', 'is_vip'),
|
||||||
'classes': ('collapse',)
|
'classes': ('collapse',)
|
||||||
}),
|
}),
|
||||||
|
('Заметки', {
|
||||||
|
'fields': ('notes',)
|
||||||
|
}),
|
||||||
('Даты', {
|
('Даты', {
|
||||||
'fields': ('created_at', 'updated_at'),
|
'fields': ('created_at', 'updated_at'),
|
||||||
'classes': ('collapse',)
|
'classes': ('collapse',)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_readonly_fields(self, request, obj=None):
|
||||||
|
"""Делаем все поля read-only для системного клиента"""
|
||||||
|
if obj and obj.is_system_customer:
|
||||||
|
# Для системного клиента все поля только для чтения
|
||||||
|
return ['name', 'email', 'phone', 'loyalty_tier', 'total_spent', 'is_vip', 'is_system_customer', 'notes', 'created_at', 'updated_at']
|
||||||
|
return self.readonly_fields
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
"""Запрет на удаление системного клиента"""
|
||||||
|
if obj and obj.is_system_customer:
|
||||||
|
return False
|
||||||
|
return super().has_delete_permission(request, obj)
|
||||||
|
|
||||||
|
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
|
||||||
|
"""Добавляем предупреждение для системного клиента"""
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
if object_id:
|
||||||
|
obj = self.get_object(request, object_id)
|
||||||
|
if obj and obj.is_system_customer:
|
||||||
|
extra_context['readonly'] = True
|
||||||
|
from django.contrib import messages
|
||||||
|
messages.warning(request, 'Это системный клиент. Редактирование запрещено для обеспечения корректной работы системы.')
|
||||||
|
return super().changeform_view(request, object_id, form_url, extra_context)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ class CustomerForm(forms.ModelForm):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Customer
|
model = Customer
|
||||||
fields = ['name', 'email', 'phone', 'loyalty_tier', 'notes']
|
fields = ['name', 'email', 'phone', 'loyalty_tier', 'notes']
|
||||||
|
exclude = ['is_system_customer']
|
||||||
widgets = {
|
widgets = {
|
||||||
'notes': forms.Textarea(attrs={'rows': 3}),
|
'notes': forms.Textarea(attrs={'rows': 3}),
|
||||||
}
|
}
|
||||||
@@ -43,9 +44,9 @@ class CustomerForm(forms.ModelForm):
|
|||||||
"""Проверяет уникальность email при создании/редактировании"""
|
"""Проверяет уникальность email при создании/редактировании"""
|
||||||
email = self.cleaned_data.get('email')
|
email = self.cleaned_data.get('email')
|
||||||
|
|
||||||
# Если email пустой, это нормально (blank=True)
|
# Нормализуем пустые значения в None (Django best practice для nullable полей)
|
||||||
if not email:
|
if not email:
|
||||||
return email
|
return None
|
||||||
|
|
||||||
# Проверяем уникальность
|
# Проверяем уникальность
|
||||||
queryset = Customer.objects.filter(email=email)
|
queryset = Customer.objects.filter(email=email)
|
||||||
@@ -63,9 +64,9 @@ class CustomerForm(forms.ModelForm):
|
|||||||
"""Проверяет уникальность телефона при создании/редактировании"""
|
"""Проверяет уникальность телефона при создании/редактировании"""
|
||||||
phone = self.cleaned_data.get('phone')
|
phone = self.cleaned_data.get('phone')
|
||||||
|
|
||||||
# Если телефон пустой, это нормально (blank=True)
|
# Нормализуем пустые значения в None (Django best practice для nullable полей)
|
||||||
if not phone:
|
if not phone:
|
||||||
return phone
|
return None
|
||||||
|
|
||||||
# Проверяем уникальность
|
# Проверяем уникальность
|
||||||
queryset = Customer.objects.filter(phone=phone)
|
queryset = Customer.objects.filter(phone=phone)
|
||||||
@@ -77,4 +78,17 @@ class CustomerForm(forms.ModelForm):
|
|||||||
if queryset.exists():
|
if queryset.exists():
|
||||||
raise ValidationError('Клиент с таким номером телефона уже существует.')
|
raise ValidationError('Клиент с таким номером телефона уже существует.')
|
||||||
|
|
||||||
return phone
|
return phone
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
"""Дополнительная валидация формы"""
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
|
# Защита от редактирования системного клиента
|
||||||
|
if self.instance and self.instance.pk and self.instance.is_system_customer:
|
||||||
|
raise ValidationError(
|
||||||
|
'Системный клиент не может быть изменен. '
|
||||||
|
'Он необходим для корректной работы системы и создается автоматически.'
|
||||||
|
)
|
||||||
|
|
||||||
|
return cleaned_data
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2025-11-19 19:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('customers', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='customer',
|
||||||
|
name='is_system_customer',
|
||||||
|
field=models.BooleanField(db_index=True, default=False, help_text='Автоматически созданный клиент для анонимных покупок и наличных продаж', verbose_name='Системный клиент'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -41,15 +41,23 @@ class Customer(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
total_spent = models.DecimalField(
|
total_spent = models.DecimalField(
|
||||||
max_digits=10,
|
max_digits=10,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
default=0,
|
default=0,
|
||||||
verbose_name="Общая сумма покупок"
|
verbose_name="Общая сумма покупок"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# System customer flag
|
||||||
|
is_system_customer = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name="Системный клиент",
|
||||||
|
help_text="Автоматически созданный клиент для анонимных покупок и наличных продаж"
|
||||||
|
)
|
||||||
|
|
||||||
# Additional notes
|
# Additional notes
|
||||||
notes = models.TextField(
|
notes = models.TextField(
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
verbose_name="Заметки",
|
verbose_name="Заметки",
|
||||||
help_text="Заметки о клиенте, особые предпочтения и т.д."
|
help_text="Заметки о клиенте, особые предпочтения и т.д."
|
||||||
@@ -168,6 +176,19 @@ class Customer(models.Model):
|
|||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
# Защита системного клиента от изменений
|
||||||
|
if self.pk and self.is_system_customer:
|
||||||
|
# Получаем оригинальный объект из БД
|
||||||
|
try:
|
||||||
|
original = Customer.objects.get(pk=self.pk)
|
||||||
|
# Проверяем, не пытаются ли изменить критичные поля
|
||||||
|
if original.email != self.email:
|
||||||
|
raise ValidationError("Нельзя изменить email системного клиента")
|
||||||
|
if original.is_system_customer != self.is_system_customer:
|
||||||
|
raise ValidationError("Нельзя изменить флаг системного клиента")
|
||||||
|
except Customer.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
# Обеспечиваем нормализацию телефона, даже если save вызывается напрямую (не через форму)
|
# Обеспечиваем нормализацию телефона, даже если save вызывается напрямую (не через форму)
|
||||||
# На данный момент, если вызов прошел через валидацию формы, телефон уже должен быть нормализован
|
# На данный момент, если вызов прошел через валидацию формы, телефон уже должен быть нормализован
|
||||||
# Но если save вызывается непосредственно в модели, нам все равно нужно нормализовать
|
# Но если save вызывается непосредственно в модели, нам все равно нужно нормализовать
|
||||||
@@ -189,6 +210,36 @@ class Customer(models.Model):
|
|||||||
|
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
"""Защита системного клиента от удаления"""
|
||||||
|
if self.is_system_customer:
|
||||||
|
raise ValidationError("Нельзя удалить системного клиента. Он необходим для работы системы.")
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_or_create_system_customer(cls):
|
||||||
|
"""
|
||||||
|
Получить или создать системного клиента для анонимных покупок.
|
||||||
|
|
||||||
|
Системный клиент используется для:
|
||||||
|
- Анонимных покупок в POS системе
|
||||||
|
- Покупок от неизвестных клиентов (проходимость)
|
||||||
|
- Наличных продаж без указания покупателя
|
||||||
|
|
||||||
|
Возвращает:
|
||||||
|
tuple: (customer, created) - объект клиента и флаг создания
|
||||||
|
"""
|
||||||
|
customer, created = cls.objects.get_or_create(
|
||||||
|
email="system@pos.customer",
|
||||||
|
defaults={
|
||||||
|
"name": "АНОНИМНЫЙ ПОКУПАТЕЛЬ (POS)",
|
||||||
|
"is_system_customer": True,
|
||||||
|
"loyalty_tier": "no_discount",
|
||||||
|
"notes": "SYSTEM_CUSTOMER - автоматически созданный клиент для анонимных покупок и наличных продаж",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return customer, created
|
||||||
|
|
||||||
def increment_total_spent(self, amount):
|
def increment_total_spent(self, amount):
|
||||||
"""Увеличивает общую сумму покупок"""
|
"""Увеличивает общую сумму покупок"""
|
||||||
self.total_spent = self.total_spent + amount
|
self.total_spent = self.total_spent + amount
|
||||||
|
|||||||
@@ -32,11 +32,11 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Email:</th>
|
<th>Email:</th>
|
||||||
<td>{{ customer.email }}</td>
|
<td>{{ customer.email|default:"Не указано" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Телефон:</th>
|
<th>Телефон:</th>
|
||||||
<td>{{ customer.phone }}</td>
|
<td>{{ customer.phone|default:"Не указано" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Уровень лояльности:</th>
|
<th>Уровень лояльности:</th>
|
||||||
|
|||||||
27
myproject/customers/templates/customers/customer_system.html
Normal file
27
myproject/customers/templates/customers/customer_system.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Системный клиент{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6 col-lg-5">
|
||||||
|
<div class="card border-info mt-5">
|
||||||
|
<div class="card-header bg-info text-white">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="bi bi-info-circle"></i> Системный клиент
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<p class="lead mb-4">
|
||||||
|
Используется для анонимных покупок и наличных продаж
|
||||||
|
</p>
|
||||||
|
<a href="{% url 'customers:customer-list' %}" class="btn btn-primary">
|
||||||
|
<i class="bi bi-arrow-left"></i> Вернуться к списку
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -26,7 +26,8 @@ def normalize_query_phone(q):
|
|||||||
def customer_list(request):
|
def customer_list(request):
|
||||||
"""Список всех клиентов"""
|
"""Список всех клиентов"""
|
||||||
query = request.GET.get('q')
|
query = request.GET.get('q')
|
||||||
customers = Customer.objects.all()
|
# Исключаем системного клиента из списка
|
||||||
|
customers = Customer.objects.filter(is_system_customer=False)
|
||||||
|
|
||||||
if query:
|
if query:
|
||||||
# Используем ту же логику поиска, что и в AJAX API (api_search_customers)
|
# Используем ту же логику поиска, что и в AJAX API (api_search_customers)
|
||||||
@@ -80,6 +81,10 @@ def customer_detail(request, pk):
|
|||||||
"""Детали клиента"""
|
"""Детали клиента"""
|
||||||
customer = get_object_or_404(Customer, pk=pk)
|
customer = get_object_or_404(Customer, pk=pk)
|
||||||
|
|
||||||
|
# Для системного клиента показываем специальную заглушку
|
||||||
|
if customer.is_system_customer:
|
||||||
|
return render(request, 'customers/customer_system.html')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'customer': customer,
|
'customer': customer,
|
||||||
}
|
}
|
||||||
@@ -104,12 +109,20 @@ def customer_update(request, pk):
|
|||||||
"""Редактирование клиента"""
|
"""Редактирование клиента"""
|
||||||
customer = get_object_or_404(Customer, pk=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':
|
if request.method == 'POST':
|
||||||
form = CustomerForm(request.POST, instance=customer)
|
form = CustomerForm(request.POST, instance=customer)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
try:
|
||||||
messages.success(request, f'Клиент {customer.full_name} успешно обновлён.')
|
form.save()
|
||||||
return redirect('customers:customer-detail', pk=customer.pk)
|
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:
|
else:
|
||||||
form = CustomerForm(instance=customer)
|
form = CustomerForm(instance=customer)
|
||||||
|
|
||||||
@@ -120,11 +133,20 @@ def customer_delete(request, pk):
|
|||||||
"""Удаление клиента"""
|
"""Удаление клиента"""
|
||||||
customer = get_object_or_404(Customer, pk=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':
|
if request.method == 'POST':
|
||||||
customer_name = customer.full_name
|
customer_name = customer.full_name
|
||||||
customer.delete()
|
try:
|
||||||
messages.success(request, f'Клиент {customer_name} успешно удален.')
|
customer.delete()
|
||||||
return redirect('customers:customer-list')
|
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 = {
|
context = {
|
||||||
'customer': customer
|
'customer': customer
|
||||||
@@ -316,7 +338,8 @@ def api_search_customers(request):
|
|||||||
if customers_by_phone.exists():
|
if customers_by_phone.exists():
|
||||||
q_objects |= Q(pk__in=customers_by_phone.values_list('pk', flat=True))
|
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 = []
|
results = []
|
||||||
|
|
||||||
@@ -442,46 +465,3 @@ def api_create_customer(request):
|
|||||||
'success': False,
|
'success': False,
|
||||||
'error': f'Ошибка сервера: {str(e)}'
|
'error': f'Ошибка сервера: {str(e)}'
|
||||||
}, status=500)
|
}, 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,
|
|
||||||
})
|
|
||||||
@@ -285,6 +285,20 @@ class TenantRegistrationAdmin(admin.ModelAdmin):
|
|||||||
else:
|
else:
|
||||||
logger.warning(f"Пользователь с email {settings.TENANT_ADMIN_EMAIL} уже существует в тенанте")
|
logger.warning(f"Пользователь с email {settings.TENANT_ADMIN_EMAIL} уже существует в тенанте")
|
||||||
|
|
||||||
|
# Создаем системного клиента для анонимных продаж
|
||||||
|
logger.info(f"Создание системного клиента для тенанта: {client.id}")
|
||||||
|
from customers.models import Customer
|
||||||
|
|
||||||
|
try:
|
||||||
|
system_customer, created = Customer.get_or_create_system_customer()
|
||||||
|
if created:
|
||||||
|
logger.info(f"Системный клиент создан: {system_customer.id} ({system_customer.name})")
|
||||||
|
else:
|
||||||
|
logger.info(f"Системный клиент уже существует: {system_customer.id} ({system_customer.name})")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при создании системного клиента: {e}", exc_info=True)
|
||||||
|
# Не прерываем процесс, т.к. это не критично
|
||||||
|
|
||||||
# Возвращаемся в public схему
|
# Возвращаемся в public схему
|
||||||
connection.set_schema_to_public()
|
connection.set_schema_to_public()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user