feat(orders): добавлено отображение скидок в админке заказов
- Добавлен DiscountApplicationInline для просмотра истории скидок на странице заказа - OrderAdmin: добавлены колонки subtotal_display и discount_display - OrderAdmin: добавлен фильтр по applied_discount - OrderAdmin: добавлена секция "Скидки" в fieldsets - OrderItemInline: добавлено отображение скидки в inline - OrderItemAdmin: добавлена колонка и фильтр по скидкам Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from .models import Order, OrderItem, Transaction, PaymentMethod, Address, OrderStatus, Recipient, Delivery
|
||||
from tenants.admin_mixins import TenantAdminOnlyMixin
|
||||
|
||||
@@ -27,14 +28,21 @@ class OrderItemInline(admin.TabularInline):
|
||||
"""
|
||||
model = OrderItem
|
||||
extra = 1
|
||||
fields = ['product', 'product_kit', 'quantity', 'price']
|
||||
fields = ['product', 'product_kit', 'quantity', 'price', 'item_discount_inline']
|
||||
readonly_fields = []
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
"""Делаем цену readonly для существующих позиций"""
|
||||
if obj and obj.pk:
|
||||
return ['price']
|
||||
return []
|
||||
return ['price', 'item_discount_inline']
|
||||
return ['item_discount_inline']
|
||||
|
||||
def item_discount_inline(self, obj):
|
||||
"""Отображение скидки в inline"""
|
||||
if obj.discount_amount and obj.discount_amount > 0:
|
||||
return f'-{obj.discount_amount:.2f} руб.'
|
||||
return '-'
|
||||
item_discount_inline.short_description = 'Скидка'
|
||||
|
||||
|
||||
class DeliveryInline(admin.StackedInline):
|
||||
@@ -49,6 +57,46 @@ class DeliveryInline(admin.StackedInline):
|
||||
verbose_name_plural = 'Доставка'
|
||||
|
||||
|
||||
class DiscountApplicationInline(admin.TabularInline):
|
||||
"""
|
||||
Inline для просмотра истории применённых скидок.
|
||||
"""
|
||||
from discounts.models import DiscountApplication
|
||||
model = DiscountApplication
|
||||
extra = 0
|
||||
can_delete = False
|
||||
verbose_name = 'Применённая скидка'
|
||||
verbose_name_plural = 'Скидки'
|
||||
|
||||
fields = [
|
||||
'discount_link',
|
||||
'promo_code_display',
|
||||
'target',
|
||||
'discount_amount',
|
||||
'applied_at',
|
||||
]
|
||||
readonly_fields = fields
|
||||
|
||||
def discount_link(self, obj):
|
||||
if obj.discount:
|
||||
return format_html(
|
||||
'<a href="/admin/discounts/discount/{}/">{}</a>',
|
||||
obj.discount.id, obj.discount.name
|
||||
)
|
||||
return '-'
|
||||
discount_link.short_description = 'Скидка'
|
||||
|
||||
def promo_code_display(self, obj):
|
||||
return obj.promo_code.code if obj.promo_code else '-'
|
||||
promo_code_display.short_description = 'Промокод'
|
||||
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
@admin.register(Order)
|
||||
class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
"""Админ-панель для управления заказами."""
|
||||
@@ -56,6 +104,8 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
'order_number',
|
||||
'customer',
|
||||
'status',
|
||||
'subtotal_display',
|
||||
'discount_display',
|
||||
'total_amount',
|
||||
'payment_status',
|
||||
'amount_paid',
|
||||
@@ -66,6 +116,7 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
'status',
|
||||
'payment_status',
|
||||
'created_at',
|
||||
'applied_discount',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
@@ -80,6 +131,8 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
'order_number',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'subtotal_display',
|
||||
'discount_display',
|
||||
'amount_due',
|
||||
'payment_status',
|
||||
]
|
||||
@@ -94,8 +147,18 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
),
|
||||
'description': 'Если получатель не указан, получателем является покупатель'
|
||||
}),
|
||||
('Скидки', {
|
||||
'fields': (
|
||||
'applied_discount',
|
||||
'applied_promo_code',
|
||||
'discount_amount',
|
||||
),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('Оплата', {
|
||||
'fields': (
|
||||
'subtotal_display',
|
||||
'discount_display',
|
||||
'total_amount',
|
||||
'amount_paid',
|
||||
'amount_due',
|
||||
@@ -112,7 +175,7 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
}),
|
||||
)
|
||||
|
||||
inlines = [OrderItemInline, DeliveryInline, TransactionInline]
|
||||
inlines = [OrderItemInline, DeliveryInline, TransactionInline, DiscountApplicationInline]
|
||||
|
||||
actions = [
|
||||
'mark_as_confirmed',
|
||||
@@ -152,6 +215,23 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
self.message_user(request, f'{updated} заказ(ов) отмечено как оплаченные')
|
||||
mark_as_paid.short_description = 'Отметить как оплаченные'
|
||||
|
||||
def subtotal_display(self, obj):
|
||||
"""Отображение subtotal (сумма товаров)"""
|
||||
return f'{obj.subtotal:.2f} руб.' if obj.subtotal else '0.00 руб.'
|
||||
subtotal_display.short_description = 'Подытог'
|
||||
|
||||
def discount_display(self, obj):
|
||||
"""Отображение информации о скидке"""
|
||||
if not obj.discount_amount or obj.discount_amount == 0:
|
||||
return '-'
|
||||
discount_info = f'-{obj.discount_amount:.2f} руб.'
|
||||
if obj.applied_discount:
|
||||
from django.utils.safestring import mark_safe
|
||||
discount_link = f'<a href="/admin/discounts/discount/{obj.applied_discount.id}/">{obj.applied_discount.name}</a>'
|
||||
return format_html('{}<br><small class="text-muted">{}</small>', discount_info, discount_link)
|
||||
return discount_info
|
||||
discount_display.short_description = 'Скидка'
|
||||
|
||||
|
||||
@admin.register(Transaction)
|
||||
class TransactionAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
@@ -202,12 +282,14 @@ class OrderItemAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
'item_name',
|
||||
'quantity',
|
||||
'price',
|
||||
'item_discount_display',
|
||||
'get_total_price',
|
||||
]
|
||||
|
||||
list_filter = [
|
||||
'order__status',
|
||||
'order__created_at',
|
||||
'applied_discount',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
@@ -216,7 +298,7 @@ class OrderItemAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
'product_kit__name',
|
||||
]
|
||||
|
||||
readonly_fields = ['created_at', 'get_total_price']
|
||||
readonly_fields = ['created_at', 'get_total_price', 'item_discount_display']
|
||||
|
||||
fieldsets = (
|
||||
('Заказ', {
|
||||
@@ -226,7 +308,11 @@ class OrderItemAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
'fields': ('product', 'product_kit')
|
||||
}),
|
||||
('Информация', {
|
||||
'fields': ('quantity', 'price', 'get_total_price')
|
||||
'fields': ('quantity', 'price', 'item_discount_display', 'get_total_price')
|
||||
}),
|
||||
('Скидка', {
|
||||
'fields': ('applied_discount', 'discount_amount'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('Системная информация', {
|
||||
'fields': ('created_at',),
|
||||
@@ -234,6 +320,16 @@ class OrderItemAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
}),
|
||||
)
|
||||
|
||||
def item_discount_display(self, obj):
|
||||
"""Отображение скидки на позицию"""
|
||||
if not obj.discount_amount or obj.discount_amount == 0:
|
||||
return '-'
|
||||
result = f'-{obj.discount_amount:.2f} руб.'
|
||||
if obj.applied_discount:
|
||||
result += format_html('<br><small class="text-muted">{}</small>', obj.applied_discount.name)
|
||||
return result
|
||||
item_discount_display.short_description = 'Скидка'
|
||||
|
||||
|
||||
@admin.register(Address)
|
||||
class AddressAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||
|
||||
Reference in New Issue
Block a user