Добавлена функциональность системного клиента для анонимных покупок

- Добавлено поле 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:
2025-11-20 00:07:38 +03:00
parent 755e4fc9d9
commit 685c06d94d
8 changed files with 215 additions and 65 deletions

View File

@@ -41,15 +41,23 @@ class Customer(models.Model):
)
total_spent = models.DecimalField(
max_digits=10,
decimal_places=2,
max_digits=10,
decimal_places=2,
default=0,
verbose_name="Общая сумма покупок"
)
# System customer flag
is_system_customer = models.BooleanField(
default=False,
db_index=True,
verbose_name="Системный клиент",
help_text="Автоматически созданный клиент для анонимных покупок и наличных продаж"
)
# Additional notes
notes = models.TextField(
blank=True,
blank=True,
null=True,
verbose_name="Заметки",
help_text="Заметки о клиенте, особые предпочтения и т.д."
@@ -168,6 +176,19 @@ class Customer(models.Model):
super().clean()
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 вызывается непосредственно в модели, нам все равно нужно нормализовать
@@ -189,6 +210,36 @@ class Customer(models.Model):
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):
"""Увеличивает общую сумму покупок"""
self.total_spent = self.total_spent + amount