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.contrib import admin
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
|
from django.urls import reverse
|
||||||
from .models import Order, OrderItem, Transaction, PaymentMethod, Address, OrderStatus, Recipient, Delivery
|
from .models import Order, OrderItem, Transaction, PaymentMethod, Address, OrderStatus, Recipient, Delivery
|
||||||
from tenants.admin_mixins import TenantAdminOnlyMixin
|
from tenants.admin_mixins import TenantAdminOnlyMixin
|
||||||
|
|
||||||
@@ -27,14 +28,21 @@ class OrderItemInline(admin.TabularInline):
|
|||||||
"""
|
"""
|
||||||
model = OrderItem
|
model = OrderItem
|
||||||
extra = 1
|
extra = 1
|
||||||
fields = ['product', 'product_kit', 'quantity', 'price']
|
fields = ['product', 'product_kit', 'quantity', 'price', 'item_discount_inline']
|
||||||
readonly_fields = []
|
readonly_fields = []
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
"""Делаем цену readonly для существующих позиций"""
|
"""Делаем цену readonly для существующих позиций"""
|
||||||
if obj and obj.pk:
|
if obj and obj.pk:
|
||||||
return ['price']
|
return ['price', 'item_discount_inline']
|
||||||
return []
|
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):
|
class DeliveryInline(admin.StackedInline):
|
||||||
@@ -49,6 +57,46 @@ class DeliveryInline(admin.StackedInline):
|
|||||||
verbose_name_plural = 'Доставка'
|
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)
|
@admin.register(Order)
|
||||||
class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||||
"""Админ-панель для управления заказами."""
|
"""Админ-панель для управления заказами."""
|
||||||
@@ -56,6 +104,8 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
|||||||
'order_number',
|
'order_number',
|
||||||
'customer',
|
'customer',
|
||||||
'status',
|
'status',
|
||||||
|
'subtotal_display',
|
||||||
|
'discount_display',
|
||||||
'total_amount',
|
'total_amount',
|
||||||
'payment_status',
|
'payment_status',
|
||||||
'amount_paid',
|
'amount_paid',
|
||||||
@@ -66,6 +116,7 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
|||||||
'status',
|
'status',
|
||||||
'payment_status',
|
'payment_status',
|
||||||
'created_at',
|
'created_at',
|
||||||
|
'applied_discount',
|
||||||
]
|
]
|
||||||
|
|
||||||
search_fields = [
|
search_fields = [
|
||||||
@@ -80,6 +131,8 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
|||||||
'order_number',
|
'order_number',
|
||||||
'created_at',
|
'created_at',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
|
'subtotal_display',
|
||||||
|
'discount_display',
|
||||||
'amount_due',
|
'amount_due',
|
||||||
'payment_status',
|
'payment_status',
|
||||||
]
|
]
|
||||||
@@ -94,8 +147,18 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
|||||||
),
|
),
|
||||||
'description': 'Если получатель не указан, получателем является покупатель'
|
'description': 'Если получатель не указан, получателем является покупатель'
|
||||||
}),
|
}),
|
||||||
|
('Скидки', {
|
||||||
|
'fields': (
|
||||||
|
'applied_discount',
|
||||||
|
'applied_promo_code',
|
||||||
|
'discount_amount',
|
||||||
|
),
|
||||||
|
'classes': ('collapse',)
|
||||||
|
}),
|
||||||
('Оплата', {
|
('Оплата', {
|
||||||
'fields': (
|
'fields': (
|
||||||
|
'subtotal_display',
|
||||||
|
'discount_display',
|
||||||
'total_amount',
|
'total_amount',
|
||||||
'amount_paid',
|
'amount_paid',
|
||||||
'amount_due',
|
'amount_due',
|
||||||
@@ -112,7 +175,7 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
inlines = [OrderItemInline, DeliveryInline, TransactionInline]
|
inlines = [OrderItemInline, DeliveryInline, TransactionInline, DiscountApplicationInline]
|
||||||
|
|
||||||
actions = [
|
actions = [
|
||||||
'mark_as_confirmed',
|
'mark_as_confirmed',
|
||||||
@@ -152,6 +215,23 @@ class OrderAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
|||||||
self.message_user(request, f'{updated} заказ(ов) отмечено как оплаченные')
|
self.message_user(request, f'{updated} заказ(ов) отмечено как оплаченные')
|
||||||
mark_as_paid.short_description = 'Отметить как оплаченные'
|
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)
|
@admin.register(Transaction)
|
||||||
class TransactionAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
class TransactionAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||||
@@ -202,12 +282,14 @@ class OrderItemAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
|||||||
'item_name',
|
'item_name',
|
||||||
'quantity',
|
'quantity',
|
||||||
'price',
|
'price',
|
||||||
|
'item_discount_display',
|
||||||
'get_total_price',
|
'get_total_price',
|
||||||
]
|
]
|
||||||
|
|
||||||
list_filter = [
|
list_filter = [
|
||||||
'order__status',
|
'order__status',
|
||||||
'order__created_at',
|
'order__created_at',
|
||||||
|
'applied_discount',
|
||||||
]
|
]
|
||||||
|
|
||||||
search_fields = [
|
search_fields = [
|
||||||
@@ -216,7 +298,7 @@ class OrderItemAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
|||||||
'product_kit__name',
|
'product_kit__name',
|
||||||
]
|
]
|
||||||
|
|
||||||
readonly_fields = ['created_at', 'get_total_price']
|
readonly_fields = ['created_at', 'get_total_price', 'item_discount_display']
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Заказ', {
|
('Заказ', {
|
||||||
@@ -226,7 +308,11 @@ class OrderItemAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
|||||||
'fields': ('product', 'product_kit')
|
'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',),
|
'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)
|
@admin.register(Address)
|
||||||
class AddressAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
class AddressAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):
|
||||||
|
|||||||
Reference in New Issue
Block a user