Feature: Добавлены методы получения суммы заказов клиента
Добавлены методы в модель Customer для расчета суммы успешных заказов: - get_successful_orders_total() - гибкий метод с фильтрацией по датам - get_last_year_orders_total() - сумма за последний год Удалено устаревшее поле total_spent: - Методы предоставляют более точные и актуальные данные - Используют агрегацию на уровне БД для производительности Обновлен UI карточки клиента: - Отображается сумма всех успешных заказов - Отображается сумма заказов за последний год - Убрана колонка total_spent из списка клиентов Изменения: - customers/models.py: добавлены методы, удалено поле total_spent - customers/views.py: добавлен расчет сумм в контекст - customers/templates: обновлены шаблоны - customers/admin.py: удалены упоминания total_spent - Создана миграция 0005_remove_total_spent 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,6 @@ class CustomerAdmin(admin.ModelAdmin):
|
|||||||
'email',
|
'email',
|
||||||
'phone',
|
'phone',
|
||||||
'wallet_balance_display',
|
'wallet_balance_display',
|
||||||
'total_spent',
|
|
||||||
'is_system_customer',
|
'is_system_customer',
|
||||||
'created_at'
|
'created_at'
|
||||||
)
|
)
|
||||||
@@ -45,7 +44,7 @@ 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_system_customer', 'wallet_balance')
|
readonly_fields = ('created_at', 'updated_at', 'is_system_customer', 'wallet_balance')
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Основная информация', {
|
('Основная информация', {
|
||||||
@@ -54,10 +53,6 @@ class CustomerAdmin(admin.ModelAdmin):
|
|||||||
('Кошелёк', {
|
('Кошелёк', {
|
||||||
'fields': ('wallet_balance',),
|
'fields': ('wallet_balance',),
|
||||||
}),
|
}),
|
||||||
('Статистика покупок', {
|
|
||||||
'fields': ('total_spent',),
|
|
||||||
'classes': ('collapse',)
|
|
||||||
}),
|
|
||||||
('Заметки', {
|
('Заметки', {
|
||||||
'fields': ('notes',)
|
'fields': ('notes',)
|
||||||
}),
|
}),
|
||||||
@@ -82,7 +77,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', 'total_spent', 'is_system_customer', 'wallet_balance', 'notes', 'created_at', 'updated_at']
|
return ['name', 'email', 'phone', 'is_system_customer', 'wallet_balance', '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):
|
||||||
|
|||||||
17
myproject/customers/migrations/0005_remove_total_spent.py
Normal file
17
myproject/customers/migrations/0005_remove_total_spent.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2025-12-05 21:17
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('customers', '0004_customer_wallet_balance_wallettransaction'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='customer',
|
||||||
|
name='total_spent',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -24,13 +24,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
|
||||||
|
|
||||||
total_spent = models.DecimalField(
|
|
||||||
max_digits=10,
|
|
||||||
decimal_places=2,
|
|
||||||
default=0,
|
|
||||||
verbose_name="Общая сумма покупок"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Wallet balance for overpayments
|
# Wallet balance for overpayments
|
||||||
wallet_balance = models.DecimalField(
|
wallet_balance = models.DecimalField(
|
||||||
@@ -259,6 +252,56 @@ class Customer(models.Model):
|
|||||||
"""
|
"""
|
||||||
return self.wallet_transactions.all()
|
return self.wallet_transactions.all()
|
||||||
|
|
||||||
|
def get_successful_orders_total(self, start_date=None, end_date=None):
|
||||||
|
"""
|
||||||
|
Получить сумму успешных заказов за указанный период.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start_date: Дата начала периода (DateField или None)
|
||||||
|
end_date: Дата окончания периода (DateField или None)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Decimal: Сумма успешных заказов
|
||||||
|
"""
|
||||||
|
from django.db.models import Sum, Value, DecimalField
|
||||||
|
from django.db.models.functions import Coalesce
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
# Базовый queryset: только успешные заказы
|
||||||
|
queryset = self.orders.filter(status__is_positive_end=True)
|
||||||
|
|
||||||
|
# Фильтрация по датам (используем delivery_date)
|
||||||
|
if start_date:
|
||||||
|
queryset = queryset.filter(delivery_date__gte=start_date)
|
||||||
|
if end_date:
|
||||||
|
queryset = queryset.filter(delivery_date__lte=end_date)
|
||||||
|
|
||||||
|
# Агрегация суммы
|
||||||
|
result = queryset.aggregate(
|
||||||
|
total=Coalesce(
|
||||||
|
Sum('total_amount'),
|
||||||
|
Value(0),
|
||||||
|
output_field=DecimalField()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return result['total'] or Decimal('0')
|
||||||
|
|
||||||
|
def get_last_year_orders_total(self):
|
||||||
|
"""
|
||||||
|
Получить сумму успешных заказов за последний календарный год.
|
||||||
|
(С этой даты прошлого года по текущую дату)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Decimal: Сумма успешных заказов за год
|
||||||
|
"""
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
today = date.today()
|
||||||
|
year_ago = today - timedelta(days=365)
|
||||||
|
|
||||||
|
return self.get_successful_orders_total(start_date=year_ago, end_date=today)
|
||||||
|
|
||||||
|
|
||||||
class WalletTransaction(models.Model):
|
class WalletTransaction(models.Model):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -39,8 +39,12 @@
|
|||||||
<td>{{ customer.phone|default:"Не указано" }}</td>
|
<td>{{ customer.phone|default:"Не указано" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Сумма покупок:</th>
|
<th>Сумма всех успешных заказов:</th>
|
||||||
<td>{{ customer.total_spent|floatformat:2 }} руб.</td>
|
<td><strong>{{ total_orders_sum|floatformat:2 }} руб.</strong></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Сумма заказов за последний год:</th>
|
||||||
|
<td><strong>{{ last_year_orders_sum|floatformat:2 }} руб.</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Общий долг по активным заказам:</th>
|
<th>Общий долг по активным заказам:</th>
|
||||||
|
|||||||
@@ -81,7 +81,6 @@
|
|||||||
<th>Имя</th>
|
<th>Имя</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Телефон</th>
|
<th>Телефон</th>
|
||||||
<th>Сумма покупок</th>
|
|
||||||
<th class="text-end">Действия</th>
|
<th class="text-end">Действия</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -95,8 +94,6 @@
|
|||||||
<td>{{ customer.email|default:'—' }}</td>
|
<td>{{ customer.email|default:'—' }}</td>
|
||||||
<td>{{ customer.phone|default:'—' }}</td>
|
<td>{{ customer.phone|default:'—' }}</td>
|
||||||
|
|
||||||
<td>{{ customer.total_spent|default:0|floatformat:2 }} руб.</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>
|
||||||
|
|||||||
@@ -122,6 +122,12 @@ def customer_detail(request, pk):
|
|||||||
)
|
)
|
||||||
refund_amount = refund_amount_result['total_refund'] or Decimal('0')
|
refund_amount = refund_amount_result['total_refund'] or Decimal('0')
|
||||||
|
|
||||||
|
# Сумма всех успешных заказов
|
||||||
|
total_orders_sum = customer.get_successful_orders_total()
|
||||||
|
|
||||||
|
# Сумма успешных заказов за последний год
|
||||||
|
last_year_orders_sum = customer.get_last_year_orders_total()
|
||||||
|
|
||||||
# История транзакций кошелька (последние 20)
|
# История транзакций кошелька (последние 20)
|
||||||
from .models import WalletTransaction
|
from .models import WalletTransaction
|
||||||
wallet_transactions = WalletTransaction.objects.filter(
|
wallet_transactions = WalletTransaction.objects.filter(
|
||||||
@@ -141,6 +147,8 @@ def customer_detail(request, pk):
|
|||||||
'refund_amount': refund_amount,
|
'refund_amount': refund_amount,
|
||||||
'wallet_transactions': wallet_transactions,
|
'wallet_transactions': wallet_transactions,
|
||||||
'orders_page': orders_page,
|
'orders_page': orders_page,
|
||||||
|
'total_orders_sum': total_orders_sum,
|
||||||
|
'last_year_orders_sum': last_year_orders_sum,
|
||||||
}
|
}
|
||||||
return render(request, 'customers/customer_detail.html', context)
|
return render(request, 'customers/customer_detail.html', context)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user