bulk actions & some next audits docs
This commit is contained in:
@@ -17,7 +17,7 @@ from igny8_core.business.billing.models import (
|
||||
PlanLimitUsage,
|
||||
)
|
||||
from .models import CreditTransaction, CreditUsageLog, AccountPaymentMethod
|
||||
from import_export.admin import ExportMixin
|
||||
from import_export.admin import ExportMixin, ImportExportMixin
|
||||
from import_export import resources
|
||||
from rangefilter.filters import DateRangeFilter
|
||||
|
||||
@@ -50,8 +50,18 @@ class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
get_account_display.short_description = 'Account'
|
||||
|
||||
|
||||
class CreditUsageLogResource(resources.ModelResource):
|
||||
"""Resource class for exporting Credit Usage Logs"""
|
||||
class Meta:
|
||||
model = CreditUsageLog
|
||||
fields = ('id', 'account__name', 'operation_type', 'credits_used', 'cost_usd',
|
||||
'model_used', 'created_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(CreditUsageLog)
|
||||
class CreditUsageLogAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
class CreditUsageLogAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = CreditUsageLogResource
|
||||
list_display = ['id', 'account', 'operation_type', 'credits_used', 'cost_usd', 'model_used', 'created_at']
|
||||
list_filter = ['operation_type', 'created_at', 'account', 'model_used']
|
||||
search_fields = ['account__name', 'model_used']
|
||||
@@ -68,8 +78,18 @@ class CreditUsageLogAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
get_account_display.short_description = 'Account'
|
||||
|
||||
|
||||
class InvoiceResource(resources.ModelResource):
|
||||
"""Resource class for exporting Invoices"""
|
||||
class Meta:
|
||||
model = Invoice
|
||||
fields = ('id', 'invoice_number', 'account__name', 'status', 'total', 'currency',
|
||||
'invoice_date', 'due_date', 'created_at', 'updated_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(Invoice)
|
||||
class InvoiceAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
class InvoiceAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = InvoiceResource
|
||||
list_display = [
|
||||
'invoice_number',
|
||||
'account',
|
||||
@@ -82,6 +102,56 @@ class InvoiceAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
list_filter = ['status', 'currency', 'invoice_date', 'account']
|
||||
search_fields = ['invoice_number', 'account__name']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = [
|
||||
'bulk_set_status_draft',
|
||||
'bulk_set_status_sent',
|
||||
'bulk_set_status_paid',
|
||||
'bulk_set_status_overdue',
|
||||
'bulk_set_status_cancelled',
|
||||
'bulk_send_reminders',
|
||||
]
|
||||
|
||||
def bulk_set_status_draft(self, request, queryset):
|
||||
"""Set selected invoices to draft status"""
|
||||
updated = queryset.update(status='draft')
|
||||
self.message_user(request, f'{updated} invoice(s) set to draft.', messages.SUCCESS)
|
||||
bulk_set_status_draft.short_description = 'Set status to Draft'
|
||||
|
||||
def bulk_set_status_sent(self, request, queryset):
|
||||
"""Set selected invoices to sent status"""
|
||||
updated = queryset.update(status='sent')
|
||||
self.message_user(request, f'{updated} invoice(s) set to sent.', messages.SUCCESS)
|
||||
bulk_set_status_sent.short_description = 'Set status to Sent'
|
||||
|
||||
def bulk_set_status_paid(self, request, queryset):
|
||||
"""Set selected invoices to paid status"""
|
||||
updated = queryset.update(status='paid')
|
||||
self.message_user(request, f'{updated} invoice(s) set to paid.', messages.SUCCESS)
|
||||
bulk_set_status_paid.short_description = 'Set status to Paid'
|
||||
|
||||
def bulk_set_status_overdue(self, request, queryset):
|
||||
"""Set selected invoices to overdue status"""
|
||||
updated = queryset.update(status='overdue')
|
||||
self.message_user(request, f'{updated} invoice(s) set to overdue.', messages.SUCCESS)
|
||||
bulk_set_status_overdue.short_description = 'Set status to Overdue'
|
||||
|
||||
def bulk_set_status_cancelled(self, request, queryset):
|
||||
"""Set selected invoices to cancelled status"""
|
||||
updated = queryset.update(status='cancelled')
|
||||
self.message_user(request, f'{updated} invoice(s) set to cancelled.', messages.SUCCESS)
|
||||
bulk_set_status_cancelled.short_description = 'Set status to Cancelled'
|
||||
|
||||
def bulk_send_reminders(self, request, queryset):
|
||||
"""Send reminder emails for selected invoices"""
|
||||
# TODO: Implement email sending logic when email service is configured
|
||||
unpaid = queryset.filter(status__in=['sent', 'overdue'])
|
||||
count = unpaid.count()
|
||||
self.message_user(
|
||||
request,
|
||||
f'{count} invoice reminder(s) queued for sending. (Email integration required)',
|
||||
messages.INFO
|
||||
)
|
||||
bulk_send_reminders.short_description = 'Send payment reminders'
|
||||
|
||||
|
||||
class PaymentResource(resources.ModelResource):
|
||||
@@ -128,7 +198,7 @@ class PaymentAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
|
||||
'manual_notes'
|
||||
]
|
||||
readonly_fields = ['created_at', 'updated_at', 'approved_at', 'processed_at', 'failed_at', 'refunded_at']
|
||||
actions = ['approve_payments', 'reject_payments']
|
||||
actions = ['approve_payments', 'reject_payments', 'bulk_refund']
|
||||
|
||||
fieldsets = (
|
||||
('Payment Info', {
|
||||
@@ -374,14 +444,71 @@ class PaymentAdmin(ExportMixin, AccountAdminMixin, SimpleHistoryAdmin, Igny8Mode
|
||||
self.message_user(request, f'Rejected {count} payment(s)')
|
||||
|
||||
reject_payments.short_description = 'Reject selected manual payments'
|
||||
|
||||
def bulk_refund(self, request, queryset):
|
||||
"""Refund selected payments"""
|
||||
from django.utils import timezone
|
||||
|
||||
# Only refund succeeded payments
|
||||
succeeded_payments = queryset.filter(status='succeeded')
|
||||
count = 0
|
||||
|
||||
for payment in succeeded_payments:
|
||||
# Mark as refunded
|
||||
payment.status = 'refunded'
|
||||
payment.refunded_at = timezone.now()
|
||||
payment.admin_notes = f'{payment.admin_notes or ""}\nBulk refunded by {request.user.email} on {timezone.now()}'
|
||||
payment.save()
|
||||
|
||||
# TODO: Process actual refund through payment gateway (Stripe/PayPal)
|
||||
# For now, just marking as refunded in database
|
||||
|
||||
count += 1
|
||||
|
||||
self.message_user(
|
||||
request,
|
||||
f'{count} payment(s) marked as refunded. Note: Actual gateway refunds need to be processed separately.',
|
||||
messages.WARNING
|
||||
)
|
||||
bulk_refund.short_description = 'Refund selected payments'
|
||||
|
||||
|
||||
class CreditPackageResource(resources.ModelResource):
|
||||
"""Resource class for importing/exporting Credit Packages"""
|
||||
class Meta:
|
||||
model = CreditPackage
|
||||
fields = ('id', 'name', 'slug', 'credits', 'price', 'discount_percentage',
|
||||
'is_active', 'is_featured', 'sort_order', 'created_at')
|
||||
export_order = fields
|
||||
import_id_fields = ('id',)
|
||||
skip_unchanged = True
|
||||
|
||||
|
||||
@admin.register(CreditPackage)
|
||||
class CreditPackageAdmin(Igny8ModelAdmin):
|
||||
class CreditPackageAdmin(ImportExportMixin, Igny8ModelAdmin):
|
||||
resource_class = CreditPackageResource
|
||||
list_display = ['name', 'slug', 'credits', 'price', 'discount_percentage', 'is_active', 'is_featured', 'sort_order']
|
||||
list_filter = ['is_active', 'is_featured']
|
||||
search_fields = ['name', 'slug']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = [
|
||||
'bulk_activate',
|
||||
'bulk_deactivate',
|
||||
]
|
||||
actions = [
|
||||
'bulk_activate',
|
||||
'bulk_deactivate',
|
||||
]
|
||||
|
||||
def bulk_activate(self, request, queryset):
|
||||
updated = queryset.update(is_active=True)
|
||||
self.message_user(request, f'{updated} credit package(s) activated.', messages.SUCCESS)
|
||||
bulk_activate.short_description = 'Activate selected packages'
|
||||
|
||||
def bulk_deactivate(self, request, queryset):
|
||||
updated = queryset.update(is_active=False)
|
||||
self.message_user(request, f'{updated} credit package(s) deactivated.', messages.SUCCESS)
|
||||
bulk_deactivate.short_description = 'Deactivate selected packages'
|
||||
|
||||
|
||||
@admin.register(PaymentMethodConfig)
|
||||
@@ -499,8 +626,18 @@ class CreditCostConfigAdmin(SimpleHistoryAdmin, Igny8ModelAdmin):
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
|
||||
class PlanLimitUsageResource(resources.ModelResource):
|
||||
"""Resource class for exporting Plan Limit Usage"""
|
||||
class Meta:
|
||||
model = PlanLimitUsage
|
||||
fields = ('id', 'account__name', 'limit_type', 'amount_used',
|
||||
'period_start', 'period_end', 'created_at')
|
||||
export_order = fields
|
||||
|
||||
|
||||
@admin.register(PlanLimitUsage)
|
||||
class PlanLimitUsageAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
class PlanLimitUsageAdmin(ExportMixin, AccountAdminMixin, Igny8ModelAdmin):
|
||||
resource_class = PlanLimitUsageResource
|
||||
"""Admin for tracking plan limit usage across billing periods"""
|
||||
list_display = [
|
||||
'account',
|
||||
@@ -518,6 +655,10 @@ class PlanLimitUsageAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
search_fields = ['account__name']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
date_hierarchy = 'period_start'
|
||||
actions = [
|
||||
'bulk_reset_usage',
|
||||
'bulk_delete_old_records',
|
||||
]
|
||||
|
||||
fieldsets = (
|
||||
('Usage Info', {
|
||||
@@ -540,6 +681,24 @@ class PlanLimitUsageAdmin(AccountAdminMixin, Igny8ModelAdmin):
|
||||
"""Display billing period range"""
|
||||
return f"{obj.period_start} to {obj.period_end}"
|
||||
period_display.short_description = 'Billing Period'
|
||||
|
||||
def bulk_reset_usage(self, request, queryset):
|
||||
"""Reset usage counters to zero"""
|
||||
updated = queryset.update(amount_used=0)
|
||||
self.message_user(request, f'{updated} usage counter(s) reset to zero.', messages.SUCCESS)
|
||||
bulk_reset_usage.short_description = 'Reset usage counters'
|
||||
|
||||
def bulk_delete_old_records(self, request, queryset):
|
||||
"""Delete usage records older than 1 year"""
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
cutoff_date = timezone.now() - timedelta(days=365)
|
||||
old_records = queryset.filter(period_end__lt=cutoff_date)
|
||||
count = old_records.count()
|
||||
old_records.delete()
|
||||
self.message_user(request, f'{count} old usage record(s) deleted (older than 1 year).', messages.SUCCESS)
|
||||
bulk_delete_old_records.short_description = 'Delete old records (>1 year)'
|
||||
|
||||
|
||||
@admin.register(BillingConfiguration)
|
||||
|
||||
Reference in New Issue
Block a user