Удалена система лояльности из модели Customer
Удалены поля loyalty_tier, is_vip, get_loyalty_discount(), increment_total_spent(). Поле total_spent оставлено для будущего расчёта по заказам. Обновлены admin, forms, views и шаблоны. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,24 +3,6 @@ from django.db import models
|
|||||||
from .models import Customer
|
from .models import Customer
|
||||||
|
|
||||||
|
|
||||||
class IsVipFilter(admin.SimpleListFilter):
|
|
||||||
title = 'VIP статус'
|
|
||||||
parameter_name = 'is_vip'
|
|
||||||
|
|
||||||
def lookups(self, request, model_admin):
|
|
||||||
return (
|
|
||||||
('yes', 'VIP'),
|
|
||||||
('no', 'Не VIP'),
|
|
||||||
)
|
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
|
||||||
if self.value() == 'yes':
|
|
||||||
return queryset.filter(loyalty_tier__in=['gold', 'platinum'])
|
|
||||||
if self.value() == 'no':
|
|
||||||
return queryset.exclude(loyalty_tier__in=['gold', 'platinum'])
|
|
||||||
return queryset
|
|
||||||
|
|
||||||
|
|
||||||
class IsSystemCustomerFilter(admin.SimpleListFilter):
|
class IsSystemCustomerFilter(admin.SimpleListFilter):
|
||||||
title = 'Системный клиент'
|
title = 'Системный клиент'
|
||||||
parameter_name = 'is_system_customer'
|
parameter_name = 'is_system_customer'
|
||||||
@@ -46,15 +28,11 @@ class CustomerAdmin(admin.ModelAdmin):
|
|||||||
'full_name',
|
'full_name',
|
||||||
'email',
|
'email',
|
||||||
'phone',
|
'phone',
|
||||||
'loyalty_tier',
|
|
||||||
'total_spent',
|
'total_spent',
|
||||||
'is_vip',
|
|
||||||
'is_system_customer',
|
'is_system_customer',
|
||||||
'created_at'
|
'created_at'
|
||||||
)
|
)
|
||||||
list_filter = (
|
list_filter = (
|
||||||
'loyalty_tier',
|
|
||||||
IsVipFilter,
|
|
||||||
IsSystemCustomerFilter,
|
IsSystemCustomerFilter,
|
||||||
'created_at'
|
'created_at'
|
||||||
)
|
)
|
||||||
@@ -65,14 +43,14 @@ 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', 'is_system_customer')
|
readonly_fields = ('created_at', 'updated_at', 'total_spent', 'is_system_customer')
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Основная информация', {
|
('Основная информация', {
|
||||||
'fields': ('name', 'email', 'phone', 'is_system_customer')
|
'fields': ('name', 'email', 'phone', 'is_system_customer')
|
||||||
}),
|
}),
|
||||||
('Программа лояльности', {
|
('Статистика покупок', {
|
||||||
'fields': ('loyalty_tier', 'total_spent', 'is_vip'),
|
'fields': ('total_spent',),
|
||||||
'classes': ('collapse',)
|
'classes': ('collapse',)
|
||||||
}),
|
}),
|
||||||
('Заметки', {
|
('Заметки', {
|
||||||
@@ -88,7 +66,7 @@ class CustomerAdmin(admin.ModelAdmin):
|
|||||||
"""Делаем все поля read-only для системного клиента"""
|
"""Делаем все поля read-only для системного клиента"""
|
||||||
if obj and obj.is_system_customer:
|
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 ['name', 'email', 'phone', 'total_spent', 'is_system_customer', 'notes', 'created_at', 'updated_at']
|
||||||
return self.readonly_fields
|
return self.readonly_fields
|
||||||
|
|
||||||
def has_delete_permission(self, request, obj=None):
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class CustomerForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Customer
|
model = Customer
|
||||||
fields = ['name', 'email', 'phone', 'loyalty_tier', 'notes']
|
fields = ['name', 'email', 'phone', 'notes']
|
||||||
exclude = ['is_system_customer']
|
exclude = ['is_system_customer']
|
||||||
widgets = {
|
widgets = {
|
||||||
'notes': forms.Textarea(attrs={'rows': 3}),
|
'notes': forms.Textarea(attrs={'rows': 3}),
|
||||||
@@ -30,12 +30,6 @@ class CustomerForm(forms.ModelForm):
|
|||||||
if field_name == 'notes':
|
if field_name == 'notes':
|
||||||
# Textarea already has rows=3 from widget, just add class
|
# Textarea already has rows=3 from widget, just add class
|
||||||
field.widget.attrs.update({'class': 'form-control'})
|
field.widget.attrs.update({'class': 'form-control'})
|
||||||
elif field_name == 'loyalty_tier':
|
|
||||||
# Select fields need form-select class
|
|
||||||
field.widget.attrs.update({'class': 'form-select'})
|
|
||||||
elif field_name == 'phone':
|
|
||||||
# Phone field gets form-control class
|
|
||||||
field.widget.attrs.update({'class': 'form-control'})
|
|
||||||
else:
|
else:
|
||||||
# Regular input fields get form-control class
|
# Regular input fields get form-control class
|
||||||
field.widget.attrs.update({'class': 'form-control'})
|
field.widget.attrs.update({'class': 'form-control'})
|
||||||
|
|||||||
@@ -26,20 +26,6 @@ class Customer(models.Model):
|
|||||||
# Temporary field to store raw phone number during initialization
|
# Temporary field to store raw phone number during initialization
|
||||||
_raw_phone = None
|
_raw_phone = None
|
||||||
|
|
||||||
# Loyalty program
|
|
||||||
loyalty_tier = models.CharField(
|
|
||||||
max_length=20,
|
|
||||||
choices=[
|
|
||||||
('no_discount', 'Без скидки'),
|
|
||||||
('bronze', 'Бронза'),
|
|
||||||
('silver', 'Серебро'),
|
|
||||||
('gold', 'Золото'),
|
|
||||||
('platinum', 'Платина'),
|
|
||||||
],
|
|
||||||
default='no_discount',
|
|
||||||
verbose_name="Уровень лояльности"
|
|
||||||
)
|
|
||||||
|
|
||||||
total_spent = models.DecimalField(
|
total_spent = models.DecimalField(
|
||||||
max_digits=10,
|
max_digits=10,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
@@ -77,7 +63,6 @@ class Customer(models.Model):
|
|||||||
models.Index(fields=['email']),
|
models.Index(fields=['email']),
|
||||||
models.Index(fields=['phone']),
|
models.Index(fields=['phone']),
|
||||||
models.Index(fields=['created_at']),
|
models.Index(fields=['created_at']),
|
||||||
models.Index(fields=['loyalty_tier']),
|
|
||||||
]
|
]
|
||||||
ordering = ['-created_at']
|
ordering = ['-created_at']
|
||||||
|
|
||||||
@@ -95,22 +80,6 @@ class Customer(models.Model):
|
|||||||
"""Полное имя клиента"""
|
"""Полное имя клиента"""
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@property
|
|
||||||
def is_vip(self):
|
|
||||||
"""Проверяет, является ли клиент VIP на основе уровня лояльности"""
|
|
||||||
return self.loyalty_tier in ("gold", "platinum")
|
|
||||||
|
|
||||||
def get_loyalty_discount(self):
|
|
||||||
"""Возвращает скидку в зависимости от уровня лояльности"""
|
|
||||||
discounts = {
|
|
||||||
'no_discount': 0,
|
|
||||||
'bronze': 0,
|
|
||||||
'silver': 5, # 5%
|
|
||||||
'gold': 10, # 10%
|
|
||||||
'platinum': 15 # 15%
|
|
||||||
}
|
|
||||||
return discounts.get(self.loyalty_tier, 0)
|
|
||||||
|
|
||||||
def validate_unique(self, exclude=None):
|
def validate_unique(self, exclude=None):
|
||||||
"""Переопределение для корректной проверки уникальности телефона при обновлениях"""
|
"""Переопределение для корректной проверки уникальности телефона при обновлениях"""
|
||||||
# Снова нормализуем номер телефона перед проверкой уникальности
|
# Снова нормализуем номер телефона перед проверкой уникальности
|
||||||
@@ -234,13 +203,8 @@ class Customer(models.Model):
|
|||||||
defaults={
|
defaults={
|
||||||
"name": "АНОНИМНЫЙ ПОКУПАТЕЛЬ (POS)",
|
"name": "АНОНИМНЫЙ ПОКУПАТЕЛЬ (POS)",
|
||||||
"is_system_customer": True,
|
"is_system_customer": True,
|
||||||
"loyalty_tier": "no_discount",
|
|
||||||
"notes": "SYSTEM_CUSTOMER - автоматически созданный клиент для анонимных покупок и наличных продаж",
|
"notes": "SYSTEM_CUSTOMER - автоматически созданный клиент для анонимных покупок и наличных продаж",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return customer, created
|
return customer, created
|
||||||
|
|
||||||
def increment_total_spent(self, amount):
|
|
||||||
"""Увеличивает общую сумму покупок"""
|
|
||||||
self.total_spent = self.total_spent + amount
|
|
||||||
self.save(update_fields=['total_spent'])
|
|
||||||
|
|||||||
@@ -38,34 +38,10 @@
|
|||||||
<th>Телефон:</th>
|
<th>Телефон:</th>
|
||||||
<td>{{ customer.phone|default:"Не указано" }}</td>
|
<td>{{ customer.phone|default:"Не указано" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th>Уровень лояльности:</th>
|
|
||||||
<td>
|
|
||||||
<span>({{ customer.get_loyalty_discount }}% скидка)</span>
|
|
||||||
<span class="badge ms-2
|
|
||||||
{% if customer.loyalty_tier == 'bronze' %}bg-secondary text-dark
|
|
||||||
{% elif customer.loyalty_tier == 'silver' %}bg-light text-dark
|
|
||||||
{% elif customer.loyalty_tier == 'gold' %}bg-warning text-dark
|
|
||||||
{% elif customer.loyalty_tier == 'platinum' %}bg-primary text-white
|
|
||||||
{% endif %}">
|
|
||||||
{{ customer.get_loyalty_tier_display }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Сумма покупок:</th>
|
<th>Сумма покупок:</th>
|
||||||
<td>{{ customer.total_spent|floatformat:2 }} руб.</td>
|
<td>{{ customer.total_spent|floatformat:2 }} руб.</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<th>VIP:</th>
|
|
||||||
<td>
|
|
||||||
{% if customer.is_vip %}
|
|
||||||
<span class="badge bg-success">Да</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="badge bg-secondary">Нет</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th>Заметки:</th>
|
<th>Заметки:</th>
|
||||||
<td>{{ customer.notes|default:"Нет" }}</td>
|
<td>{{ customer.notes|default:"Нет" }}</td>
|
||||||
|
|||||||
@@ -81,16 +81,13 @@
|
|||||||
<th>Имя</th>
|
<th>Имя</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Телефон</th>
|
<th>Телефон</th>
|
||||||
<th>Уровень лояльности</th>
|
|
||||||
<th>Сумма покупок</th>
|
<th>Сумма покупок</th>
|
||||||
<th>VIP</th>
|
|
||||||
<th class="text-end">Действия</th>
|
<th class="text-end">Действия</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for customer in page_obj %}
|
{% for customer in page_obj %}
|
||||||
<tr
|
<tr
|
||||||
class="{% if customer.is_vip %}table-warning{% endif %}"
|
|
||||||
style="cursor:pointer"
|
style="cursor:pointer"
|
||||||
onclick="window.location='{% url 'customers:customer-detail' customer.pk %}'"
|
onclick="window.location='{% url 'customers:customer-detail' customer.pk %}'"
|
||||||
>
|
>
|
||||||
@@ -98,33 +95,12 @@
|
|||||||
<td>{{ customer.email|default:'—' }}</td>
|
<td>{{ customer.email|default:'—' }}</td>
|
||||||
<td>{{ customer.phone|default:'—' }}</td>
|
<td>{{ customer.phone|default:'—' }}</td>
|
||||||
|
|
||||||
<td>
|
|
||||||
<span class="badge
|
|
||||||
{% if customer.loyalty_tier == 'no_discount' %}bg-light text-muted
|
|
||||||
{% elif customer.loyalty_tier == 'bronze' %}bg-secondary text-white
|
|
||||||
{% elif customer.loyalty_tier == 'silver' %}bg-info text-dark
|
|
||||||
{% elif customer.loyalty_tier == 'gold' %}bg-warning text-dark
|
|
||||||
{% elif customer.loyalty_tier == 'platinum' %}bg-primary text-white
|
|
||||||
{% endif %}
|
|
||||||
">
|
|
||||||
{{ customer.get_loyalty_tier_display }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>{{ customer.total_spent|default:0|floatformat:2 }} руб.</td>
|
<td>{{ customer.total_spent|default:0|floatformat:2 }} руб.</td>
|
||||||
|
|
||||||
<td>
|
|
||||||
{% if customer.is_vip %}
|
|
||||||
<span class="badge bg-success">Да</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="badge bg-secondary text-white">Нет</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="text-end" onclick="event.stopPropagation();">
|
<td class="text-end" onclick="event.stopPropagation();">
|
||||||
<a href="{% url 'customers:customer-detail' customer.pk %}"
|
<a href="{% url 'customers:customer-detail' customer.pk %}"
|
||||||
class="btn btn-sm btn-outline-primary">👁</a>
|
class="btn btn-sm btn-outline-primary">👁</a>
|
||||||
<a href="{% url 'customers:customer-update' customer.pk %}"
|
<a href="{% url 'customers:customer-update' customer.pk %}"
|
||||||
class="btn btn-sm btn-outline-secondary">✎</a>
|
class="btn btn-sm btn-outline-secondary">✎</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -413,7 +413,6 @@ def api_create_customer(request):
|
|||||||
'name': name,
|
'name': name,
|
||||||
'phone': phone if phone else None,
|
'phone': phone if phone else None,
|
||||||
'email': email if email else None,
|
'email': email if email else None,
|
||||||
'loyalty_tier': 'no_discount', # Значение по умолчанию для новых клиентов
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Используем форму для валидации и создания
|
# Используем форму для валидации и создания
|
||||||
|
|||||||
Reference in New Issue
Block a user