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:
2025-12-06 00:21:45 +03:00
parent 2f7fed4a1a
commit 8d50613876
6 changed files with 83 additions and 19 deletions

View File

@@ -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):

View 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',
),
]

View File

@@ -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):
""" """

View File

@@ -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>

View File

@@ -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>

View File

@@ -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)