""" Billing Module Admin """ from django.contrib import admin from django.utils.html import format_html from igny8_core.admin.base import AccountAdminMixin from igny8_core.business.billing.models import ( CreditCostConfig, Invoice, Payment, CreditPackage, PaymentMethodConfig, ) from .models import CreditTransaction, CreditUsageLog, AccountPaymentMethod @admin.register(CreditTransaction) class CreditTransactionAdmin(AccountAdminMixin, admin.ModelAdmin): list_display = ['id', 'account', 'transaction_type', 'amount', 'balance_after', 'description', 'created_at'] list_filter = ['transaction_type', 'created_at', 'account'] search_fields = ['description', 'account__name'] readonly_fields = ['created_at'] date_hierarchy = 'created_at' def get_account_display(self, obj): """Safely get account name""" try: account = getattr(obj, 'account', None) return account.name if account else '-' except: return '-' get_account_display.short_description = 'Account' @admin.register(CreditUsageLog) class CreditUsageLogAdmin(AccountAdminMixin, admin.ModelAdmin): 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'] readonly_fields = ['created_at'] date_hierarchy = 'created_at' def get_account_display(self, obj): """Safely get account name""" try: account = getattr(obj, 'account', None) return account.name if account else '-' except: return '-' get_account_display.short_description = 'Account' @admin.register(Invoice) class InvoiceAdmin(AccountAdminMixin, admin.ModelAdmin): list_display = [ 'invoice_number', 'account', 'status', 'total', 'currency', 'invoice_date', 'due_date', ] list_filter = ['status', 'currency', 'invoice_date', 'account'] search_fields = ['invoice_number', 'account__name'] readonly_fields = ['created_at', 'updated_at'] @admin.register(Payment) class PaymentAdmin(AccountAdminMixin, admin.ModelAdmin): list_display = [ 'id', 'invoice', 'account', 'payment_method', 'status', 'amount', 'currency', 'manual_reference', 'approved_by', 'processed_at', ] list_filter = ['status', 'payment_method', 'currency', 'created_at', 'processed_at'] search_fields = [ 'invoice__invoice_number', 'account__name', 'stripe_payment_intent_id', 'paypal_order_id', 'manual_reference', 'admin_notes', 'manual_notes' ] readonly_fields = ['created_at', 'updated_at', 'approved_at', 'processed_at', 'failed_at', 'refunded_at'] actions = ['approve_payments', 'reject_payments'] def approve_payments(self, request, queryset): """Approve selected manual payments""" from django.db import transaction from django.utils import timezone from igny8_core.business.billing.services.credit_service import CreditService count = 0 errors = [] for payment in queryset.filter(status='pending_approval'): try: with transaction.atomic(): invoice = payment.invoice subscription = invoice.subscription if hasattr(invoice, 'subscription') else None account = payment.account # Update Payment payment.status = 'succeeded' payment.approved_by = request.user payment.approved_at = timezone.now() payment.processed_at = timezone.now() payment.admin_notes = f'Bulk approved by {request.user.email}' payment.save() # Update Invoice invoice.status = 'paid' invoice.paid_at = timezone.now() invoice.save() # Update Subscription if subscription: subscription.status = 'active' subscription.external_payment_id = payment.manual_reference subscription.save() # Update Account account.status = 'active' account.save() # Add Credits if subscription and subscription.plan: CreditService.add_credits( account=account, amount=subscription.plan.included_credits, transaction_type='subscription', description=f'{subscription.plan.name} - Invoice {invoice.invoice_number}', metadata={ 'subscription_id': subscription.id, 'invoice_id': invoice.id, 'payment_id': payment.id, 'approved_by': request.user.email } ) count += 1 except Exception as e: errors.append(f'Payment {payment.id}: {str(e)}') if count: self.message_user(request, f'Successfully approved {count} payment(s)') if errors: for error in errors: self.message_user(request, error, level='ERROR') approve_payments.short_description = 'Approve selected manual payments' def reject_payments(self, request, queryset): """Reject selected manual payments""" from django.utils import timezone count = queryset.filter(status='pending_approval').update( status='failed', approved_by=request.user, approved_at=timezone.now(), failed_at=timezone.now(), admin_notes=f'Bulk rejected by {request.user.email}', failure_reason='Rejected by admin' ) self.message_user(request, f'Rejected {count} payment(s)') reject_payments.short_description = 'Reject selected manual payments' @admin.register(CreditPackage) class CreditPackageAdmin(admin.ModelAdmin): 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'] @admin.register(PaymentMethodConfig) class PaymentMethodConfigAdmin(admin.ModelAdmin): list_display = ['country_code', 'payment_method', 'display_name', 'is_enabled', 'sort_order', 'updated_at'] list_filter = ['payment_method', 'is_enabled', 'country_code'] search_fields = ['country_code', 'display_name', 'payment_method'] list_editable = ['is_enabled', 'sort_order'] readonly_fields = ['created_at', 'updated_at'] @admin.register(AccountPaymentMethod) class AccountPaymentMethodAdmin(AccountAdminMixin, admin.ModelAdmin): list_display = [ 'display_name', 'type', 'account', 'is_default', 'is_enabled', 'is_verified', 'country_code', 'updated_at', ] list_filter = ['type', 'is_default', 'is_enabled', 'is_verified', 'country_code'] search_fields = ['display_name', 'account__name', 'account__id'] readonly_fields = ['created_at', 'updated_at'] fieldsets = ( ('Payment Method', { 'fields': ('account', 'type', 'display_name', 'is_default', 'is_enabled', 'is_verified', 'country_code') }), ('Instructions / Metadata', { 'fields': ('instructions', 'metadata') }), ('Timestamps', { 'fields': ('created_at', 'updated_at'), 'classes': ('collapse',) }), ) @admin.register(CreditCostConfig) class CreditCostConfigAdmin(admin.ModelAdmin): list_display = [ 'operation_type', 'display_name', 'credits_cost_display', 'unit', 'is_active', 'cost_change_indicator', 'updated_at', 'updated_by' ] list_filter = ['is_active', 'unit', 'updated_at'] search_fields = ['operation_type', 'display_name', 'description'] fieldsets = ( ('Operation', { 'fields': ('operation_type', 'display_name', 'description') }), ('Cost Configuration', { 'fields': ('credits_cost', 'unit', 'is_active') }), ('Audit Trail', { 'fields': ('previous_cost', 'updated_by', 'created_at', 'updated_at'), 'classes': ('collapse',) }), ) readonly_fields = ['created_at', 'updated_at', 'previous_cost'] def credits_cost_display(self, obj): """Show cost with color coding""" if obj.credits_cost >= 20: color = 'red' elif obj.credits_cost >= 10: color = 'orange' else: color = 'green' return format_html( '{} credits', color, obj.credits_cost ) credits_cost_display.short_description = 'Cost' def cost_change_indicator(self, obj): """Show if cost changed recently""" if obj.previous_cost is not None: if obj.credits_cost > obj.previous_cost: icon = '📈' # Increased color = 'red' elif obj.credits_cost < obj.previous_cost: icon = '📉' # Decreased color = 'green' else: icon = '➡️' # Same color = 'gray' return format_html( '{} ({} → {})', icon, color, obj.previous_cost, obj.credits_cost ) return '—' cost_change_indicator.short_description = 'Recent Change' def save_model(self, request, obj, form, change): """Track who made the change""" obj.updated_by = request.user super().save_model(request, obj, form, change)