Реализация системы кошелька клиента для переплат

- Добавлено поле wallet_balance в модель Customer
- Создана модель WalletTransaction для истории операций
- Реализован сервис WalletService с методами:
  * add_overpayment - автоматическое зачисление переплаты
  * pay_with_wallet - оплата заказа из кошелька
  * adjust_balance - ручная корректировка баланса
- Интеграция с Payment.save() для автоматической обработки переплат
- UI для оплаты из кошелька в деталях заказа
- Отображение баланса и долга на странице клиента
- Админка с inline транзакций и запретом ручного создания
- Добавлен способ оплаты account_balance
- Миграция 0004 для customers приложения
This commit is contained in:
2025-11-26 14:47:11 +03:00
parent 0653ec0545
commit 5ead7fdd2e
16 changed files with 1401 additions and 3 deletions

View File

@@ -1,6 +1,7 @@
from django.contrib import admin
from django.db import models
from .models import Customer
from django.utils.html import format_html
from .models import Customer, WalletTransaction
class IsSystemCustomerFilter(admin.SimpleListFilter):
@@ -28,6 +29,7 @@ class CustomerAdmin(admin.ModelAdmin):
'full_name',
'email',
'phone',
'wallet_balance_display',
'total_spent',
'is_system_customer',
'created_at'
@@ -43,12 +45,15 @@ class CustomerAdmin(admin.ModelAdmin):
)
date_hierarchy = 'created_at'
ordering = ('-created_at',)
readonly_fields = ('created_at', 'updated_at', 'total_spent', 'is_system_customer')
readonly_fields = ('created_at', 'updated_at', 'total_spent', 'is_system_customer', 'wallet_balance')
fieldsets = (
('Основная информация', {
'fields': ('name', 'email', 'phone', 'is_system_customer')
}),
('Кошелёк', {
'fields': ('wallet_balance',),
}),
('Статистика покупок', {
'fields': ('total_spent',),
'classes': ('collapse',)
@@ -62,11 +67,22 @@ class CustomerAdmin(admin.ModelAdmin):
}),
)
def wallet_balance_display(self, obj):
"""Отображение баланса кошелька с цветом"""
if obj.wallet_balance > 0:
return format_html(
'<span style="color: green; font-weight: bold;">{} руб.</span>',
obj.wallet_balance
)
return f'{obj.wallet_balance} руб.'
wallet_balance_display.short_description = 'Баланс кошелька'
wallet_balance_display.admin_order_field = 'wallet_balance'
def get_readonly_fields(self, request, obj=None):
"""Делаем все поля read-only для системного клиента"""
if obj and obj.is_system_customer:
# Для системного клиента все поля только для чтения
return ['name', 'email', 'phone', 'total_spent', 'is_system_customer', 'notes', 'created_at', 'updated_at']
return ['name', 'email', 'phone', 'total_spent', 'is_system_customer', 'wallet_balance', 'notes', 'created_at', 'updated_at']
return self.readonly_fields
def has_delete_permission(self, request, obj=None):
@@ -85,3 +101,56 @@ class CustomerAdmin(admin.ModelAdmin):
from django.contrib import messages
messages.warning(request, 'Это системный клиент. Редактирование запрещено для обеспечения корректной работы системы.')
return super().changeform_view(request, object_id, form_url, extra_context)
class WalletTransactionInline(admin.TabularInline):
"""
line для отображения транзакций кошелька"""
model = WalletTransaction
extra = 0
can_delete = False
readonly_fields = ('transaction_type', 'amount', 'order', 'description', 'created_at', 'created_by')
fields = ('created_at', 'transaction_type', 'amount', 'order', 'description', 'created_by')
ordering = ('-created_at',)
def has_add_permission(self, request, obj=None):
"""Запрещаем ручное создание транзакций - только через сервис"""
return False
# Добавляем inline в CustomerAdmin
CustomerAdmin.inlines = [WalletTransactionInline]
@admin.register(WalletTransaction)
class WalletTransactionAdmin(admin.ModelAdmin):
"""Админка для просмотра всех транзакций кошелька"""
list_display = ('created_at', 'customer', 'transaction_type', 'amount_display', 'order', 'created_by')
list_filter = ('transaction_type', 'created_at')
search_fields = ('customer__name', 'customer__email', 'customer__phone', 'description')
readonly_fields = ('customer', 'transaction_type', 'amount', 'order', 'description', 'created_at', 'created_by')
date_hierarchy = 'created_at'
ordering = ('-created_at',)
def amount_display(self, obj):
"""Отображение суммы с цветом"""
if obj.transaction_type == 'deposit':
return format_html(
'<span style="color: green; font-weight: bold;">+{} руб.</span>',
obj.amount
)
elif obj.transaction_type == 'spend':
return format_html(
'<span style="color: red; font-weight: bold;">-{} руб.</span>',
obj.amount
)
return f'{obj.amount} руб.'
amount_display.short_description = 'Сумма'
def has_add_permission(self, request):
"""Запрещаем ручное создание - только через сервис"""
return False
def has_delete_permission(self, request, obj=None):
"""Запрещаем удаление - аудит должен быть неизменяем"""
return False