diff --git a/admin_api/urls.py b/admin_api/urls.py index 8347c3f..b367366 100644 --- a/admin_api/urls.py +++ b/admin_api/urls.py @@ -27,4 +27,8 @@ urlpatterns = [ path('events/', views.EventListView.as_view(), name='event-list'), path('events//', views.EventDetailView.as_view(), name='event-detail'), path('events//moderate/', views.EventModerationView.as_view(), name='event-moderate'), + path('financials/metrics/', views.FinancialMetricsView.as_view(), name='financial-metrics'), + path('financials/transactions/', views.TransactionListView.as_view(), name='transaction-list'), + path('financials/settlements/', views.SettlementListView.as_view(), name='settlement-list'), + path('financials/settlements//release/', views.SettlementReleaseView.as_view(), name='settlement-release'), ] diff --git a/admin_api/views.py b/admin_api/views.py index febed3b..c8338a5 100644 --- a/admin_api/views.py +++ b/admin_api/views.py @@ -717,3 +717,127 @@ class EventModerationView(APIView): return Response({'error': 'Invalid action'}, status=400) e.refresh_from_db() return Response(_serialize_event(e)) + + +# --------------------------------------------------------------------------- +# Phase 6: Financials & Payouts +# --------------------------------------------------------------------------- + +_TX_STATUS_MAP = { + 'captured': 'Completed', + 'created': 'Pending', + 'failed': 'Failed', + 'refunded': 'Failed', +} + +def _serialize_transaction(t): + ts = t.captured_at or t.created_at + order_ref = getattr(t, 'razorpay_order_id', None) or getattr(t, 'transaction_id', None) + return { + 'id': str(t.id), + 'title': f'Payment {order_ref}' if order_ref else f'Transaction #{t.id}', + 'partner': '', + 'amount': t.amount / 100, + 'date': ts.isoformat() if ts else '', + 'type': 'in', + 'method': 'Razorpay', + 'fees': 0, + 'net': t.amount / 100, + 'status': _TX_STATUS_MAP.get(t.status, 'Pending'), + } + +_SETTLEMENT_STATUS_MAP = { + 'pending': 'Ready', + 'failed': 'Overdue', + 'cancelled': 'Overdue', + 'completed': 'On Hold', + 'refunded': 'On Hold', +} + +def _serialize_settlement(p): + return { + 'id': str(p.id), + 'partnerName': '', + 'eventName': '', + 'amount': float(p.payment_transaction_amount), + 'dueDate': p.payment_transaction_date.isoformat() if p.payment_transaction_date else '', + 'status': _SETTLEMENT_STATUS_MAP.get(p.payment_transaction_status, 'On Hold'), + } + + +class FinancialMetricsView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + from ledger.models import RazorpayTransaction + from banking_operations.models import PaymentTransaction + from django.db.models import Sum + + total_paise = ( + RazorpayTransaction.objects.filter(status='captured') + .aggregate(t=Sum('amount'))['t'] or 0 + ) + total_revenue = total_paise / 100 + + total_payouts = float( + PaymentTransaction.objects + .filter(payment_type='debit', payment_transaction_status='completed') + .aggregate(t=Sum('payment_transaction_amount'))['t'] or 0 + ) + platform_earnings = round(total_revenue * 0.12, 2) + + pending_count = PaymentTransaction.objects.filter( + payment_type='debit', payment_transaction_status='pending' + ).count() + pending_amount = float( + PaymentTransaction.objects + .filter(payment_type='debit', payment_transaction_status='pending') + .aggregate(t=Sum('payment_transaction_amount'))['t'] or 0 + ) + + return Response({ + 'totalRevenue': total_revenue, + 'totalPayouts': total_payouts, + 'platformEarnings': platform_earnings, + 'pendingPayouts': pending_count, + 'pendingPayoutAmount': pending_amount, + }) + + +class TransactionListView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + from ledger.models import RazorpayTransaction + try: + page = max(1, int(request.GET.get('page', 1))) + page_size = min(100, int(request.GET.get('page_size', 20))) + except (ValueError, TypeError): + page, page_size = 1, 20 + qs = RazorpayTransaction.objects.order_by('-id') + total = qs.count() + txs = qs[(page - 1) * page_size: page * page_size] + return Response({'count': total, 'results': [_serialize_transaction(t) for t in txs]}) + + +class SettlementListView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + from banking_operations.models import PaymentTransaction + qs = PaymentTransaction.objects.filter( + payment_type='debit' + ).order_by('-id')[:50] + return Response([_serialize_settlement(p) for p in qs]) + + +class SettlementReleaseView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request, pk): + from banking_operations.models import PaymentTransaction + from django.shortcuts import get_object_or_404 + p = get_object_or_404(PaymentTransaction, pk=pk, payment_type='debit') + p.payment_transaction_status = 'completed' + p.save(update_fields=['payment_transaction_status']) + return Response(_serialize_settlement(p))