feat(discounts): добавлено приложение скидок
Создано новое Django приложение для управления скидками: Модели: - BaseDiscount: абстрактный базовый класс с общими полями - Discount: основная модель скидки (процент/фикс, на заказ/товар/категорию) - PromoCode: промокоды для активации скидок - DiscountApplication: история применения скидок Сервисы: - DiscountCalculator: расчёт скидок для корзины и заказов - DiscountApplier: применение скидок к заказам (атомарно) - DiscountValidator: валидация промокодов и условий Админ-панель: - DiscountAdmin: управление скидками - PromoCodeAdmin: управление промокодами - DiscountApplicationAdmin: история применения (только чтение) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
203
myproject/discounts/admin.py
Normal file
203
myproject/discounts/admin.py
Normal file
@@ -0,0 +1,203 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.contrib import admin
|
||||
from .models import Discount, PromoCode, DiscountApplication
|
||||
|
||||
|
||||
@admin.register(Discount)
|
||||
class DiscountAdmin(admin.ModelAdmin):
|
||||
"""Админ-панель для управления скидками."""
|
||||
list_display = [
|
||||
'name',
|
||||
'discount_type',
|
||||
'value_display',
|
||||
'scope',
|
||||
'is_auto',
|
||||
'is_active',
|
||||
'current_usage_count',
|
||||
'validity_period',
|
||||
]
|
||||
|
||||
list_filter = [
|
||||
'discount_type',
|
||||
'scope',
|
||||
'is_auto',
|
||||
'is_active',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'name',
|
||||
'description',
|
||||
]
|
||||
|
||||
readonly_fields = [
|
||||
'current_usage_count',
|
||||
'created_at',
|
||||
]
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('name', 'description', 'is_active', 'priority')
|
||||
}),
|
||||
('Параметры скидки', {
|
||||
'fields': ('discount_type', 'value', 'scope')
|
||||
}),
|
||||
('Ограничения', {
|
||||
'fields': (
|
||||
'start_date',
|
||||
'end_date',
|
||||
'max_usage_count',
|
||||
'current_usage_count',
|
||||
'is_auto'
|
||||
)
|
||||
}),
|
||||
('Условия применения', {
|
||||
'fields': (
|
||||
'min_order_amount',
|
||||
'products',
|
||||
'categories',
|
||||
'excluded_products'
|
||||
)
|
||||
}),
|
||||
('Метаданные', {
|
||||
'fields': ('created_at', 'created_by'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
def value_display(self, obj):
|
||||
if obj.discount_type == 'percentage':
|
||||
return f"{obj.value}%"
|
||||
return f"{obj.value} руб."
|
||||
value_display.short_description = "Значение"
|
||||
|
||||
def validity_period(self, obj):
|
||||
if obj.start_date and obj.end_date:
|
||||
return f"{obj.start_date.date()} - {obj.end_date.date()}"
|
||||
elif obj.start_date:
|
||||
return f"с {obj.start_date.date()}"
|
||||
elif obj.end_date:
|
||||
return f"до {obj.end_date.date()}"
|
||||
return "Бессрочная"
|
||||
validity_period.short_description = "Период действия"
|
||||
|
||||
|
||||
@admin.register(PromoCode)
|
||||
class PromoCodeAdmin(admin.ModelAdmin):
|
||||
"""Админ-панель для управления промокодами."""
|
||||
list_display = [
|
||||
'code',
|
||||
'discount_name',
|
||||
'is_active',
|
||||
'current_uses',
|
||||
'usage_limit',
|
||||
'validity_period',
|
||||
]
|
||||
|
||||
list_filter = [
|
||||
'is_active',
|
||||
'discount__scope',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'code',
|
||||
'discount__name',
|
||||
]
|
||||
|
||||
readonly_fields = [
|
||||
'current_uses',
|
||||
'created_at',
|
||||
]
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('code', 'discount', 'is_active')
|
||||
}),
|
||||
('Ограничения', {
|
||||
'fields': (
|
||||
'max_uses_per_user',
|
||||
'max_total_uses',
|
||||
'current_uses',
|
||||
'start_date',
|
||||
'end_date',
|
||||
)
|
||||
}),
|
||||
('Метаданные', {
|
||||
'fields': ('created_at', 'created_by'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
def discount_name(self, obj):
|
||||
return obj.discount.name
|
||||
discount_name.short_description = "Скидка"
|
||||
|
||||
def usage_limit(self, obj):
|
||||
if obj.max_total_uses:
|
||||
return f"{obj.current_uses} / {obj.max_total_uses}"
|
||||
return str(obj.current_uses)
|
||||
usage_limit.short_description = "Использования"
|
||||
|
||||
def validity_period(self, obj):
|
||||
if obj.start_date and obj.end_date:
|
||||
return f"{obj.start_date.date()} - {obj.end_date.date()}"
|
||||
elif obj.start_date:
|
||||
return f"с {obj.start_date.date()}"
|
||||
elif obj.end_date:
|
||||
return f"до {obj.end_date.date()}"
|
||||
return "Бессрочный"
|
||||
validity_period.short_description = "Период действия"
|
||||
|
||||
|
||||
@admin.register(DiscountApplication)
|
||||
class DiscountApplicationAdmin(admin.ModelAdmin):
|
||||
"""Админ-панель для истории применения скидок."""
|
||||
list_display = [
|
||||
'order_link',
|
||||
'discount_name',
|
||||
'promo_code_display',
|
||||
'target',
|
||||
'discount_amount',
|
||||
'customer',
|
||||
'applied_at',
|
||||
]
|
||||
|
||||
list_filter = [
|
||||
'target',
|
||||
'applied_at',
|
||||
'discount__discount_type',
|
||||
]
|
||||
|
||||
readonly_fields = [
|
||||
'order',
|
||||
'order_item',
|
||||
'discount',
|
||||
'promo_code',
|
||||
'target',
|
||||
'base_amount',
|
||||
'discount_amount',
|
||||
'final_amount',
|
||||
'customer',
|
||||
'applied_at',
|
||||
'applied_by',
|
||||
]
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False # Только чтение
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return False # Только чтение
|
||||
|
||||
def order_link(self, obj):
|
||||
from django.urls import reverse
|
||||
url = reverse('admin:orders_order_change', args=[obj.order.id])
|
||||
return f'<a href="{url}">#{obj.order.order_number}</a>'
|
||||
order_link.short_description = "Заказ"
|
||||
order_link.allow_tags = True
|
||||
|
||||
def discount_name(self, obj):
|
||||
return obj.discount.name if obj.discount else '-'
|
||||
discount_name.short_description = "Скидка"
|
||||
|
||||
def promo_code_display(self, obj):
|
||||
return obj.promo_code.code if obj.promo_code else '-'
|
||||
promo_code_display.short_description = "Промокод"
|
||||
Reference in New Issue
Block a user