diff --git a/myproject/orders/admin.py b/myproject/orders/admin.py index fc3faa1..f5f0304 100644 --- a/myproject/orders/admin.py +++ b/myproject/orders/admin.py @@ -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( + '{}', + 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'{obj.applied_discount.name}' + return format_html('{}
{}', 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('
{}', obj.applied_discount.name) + return result + item_discount_display.short_description = 'Скидка' + @admin.register(Address) class AddressAdmin(TenantAdminOnlyMixin, admin.ModelAdmin):