billing accoutn with all the mess here

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-05 03:59:54 +00:00
parent 6b291671bd
commit 6cf786b03f
41 changed files with 7257 additions and 685 deletions

View File

@@ -0,0 +1,31 @@
"""
Account API URLs
"""
from django.urls import path
from igny8_core.api.account_views import (
AccountSettingsViewSet,
TeamManagementViewSet,
UsageAnalyticsViewSet
)
urlpatterns = [
# Account Settings
path('settings/', AccountSettingsViewSet.as_view({
'get': 'retrieve',
'patch': 'partial_update'
}), name='account-settings'),
# Team Management
path('team/', TeamManagementViewSet.as_view({
'get': 'list',
'post': 'create'
}), name='team-list'),
path('team/<int:pk>/', TeamManagementViewSet.as_view({
'delete': 'destroy'
}), name='team-detail'),
# Usage Analytics
path('usage/analytics/', UsageAnalyticsViewSet.as_view({
'get': 'overview'
}), name='usage-analytics'),
]

View File

@@ -0,0 +1,231 @@
"""
Account Management API Views
Handles account settings, team management, and usage analytics
"""
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from django.contrib.auth import get_user_model
from django.db.models import Q, Count, Sum
from django.utils import timezone
from datetime import timedelta
from igny8_core.auth.models import Account
from igny8_core.business.billing.models import CreditTransaction
User = get_user_model()
class AccountSettingsViewSet(viewsets.ViewSet):
"""Account settings management"""
permission_classes = [IsAuthenticated]
def retrieve(self, request):
"""Get account settings"""
account = request.user.account
return Response({
'id': account.id,
'name': account.name,
'slug': account.slug,
'billing_address_line1': account.billing_address_line1 or '',
'billing_address_line2': account.billing_address_line2 or '',
'billing_city': account.billing_city or '',
'billing_state': account.billing_state or '',
'billing_postal_code': account.billing_postal_code or '',
'billing_country': account.billing_country or '',
'tax_id': account.tax_id or '',
'billing_email': account.billing_email or '',
'credits': account.credits,
'created_at': account.created_at.isoformat(),
'updated_at': account.updated_at.isoformat(),
})
def partial_update(self, request):
"""Update account settings"""
account = request.user.account
# Update allowed fields
allowed_fields = [
'name', 'billing_address_line1', 'billing_address_line2',
'billing_city', 'billing_state', 'billing_postal_code',
'billing_country', 'tax_id', 'billing_email'
]
for field in allowed_fields:
if field in request.data:
setattr(account, field, request.data[field])
account.save()
return Response({
'message': 'Account settings updated successfully',
'account': {
'id': account.id,
'name': account.name,
'slug': account.slug,
'billing_address_line1': account.billing_address_line1,
'billing_address_line2': account.billing_address_line2,
'billing_city': account.billing_city,
'billing_state': account.billing_state,
'billing_postal_code': account.billing_postal_code,
'billing_country': account.billing_country,
'tax_id': account.tax_id,
'billing_email': account.billing_email,
}
})
class TeamManagementViewSet(viewsets.ViewSet):
"""Team members management"""
permission_classes = [IsAuthenticated]
def list(self, request):
"""List team members"""
account = request.user.account
users = User.objects.filter(account=account)
return Response({
'results': [
{
'id': user.id,
'email': user.email,
'first_name': user.first_name,
'last_name': user.last_name,
'is_active': user.is_active,
'is_staff': user.is_staff,
'date_joined': user.date_joined.isoformat(),
'last_login': user.last_login.isoformat() if user.last_login else None,
}
for user in users
],
'count': users.count()
})
def create(self, request):
"""Invite new team member"""
account = request.user.account
email = request.data.get('email')
if not email:
return Response(
{'error': 'Email is required'},
status=status.HTTP_400_BAD_REQUEST
)
# Check if user already exists
if User.objects.filter(email=email).exists():
return Response(
{'error': 'User with this email already exists'},
status=status.HTTP_400_BAD_REQUEST
)
# Create user (simplified - in production, send invitation email)
user = User.objects.create_user(
email=email,
first_name=request.data.get('first_name', ''),
last_name=request.data.get('last_name', ''),
account=account
)
return Response({
'message': 'Team member invited successfully',
'user': {
'id': user.id,
'email': user.email,
'first_name': user.first_name,
'last_name': user.last_name,
}
}, status=status.HTTP_201_CREATED)
def destroy(self, request, pk=None):
"""Remove team member"""
account = request.user.account
try:
user = User.objects.get(id=pk, account=account)
# Prevent removing yourself
if user.id == request.user.id:
return Response(
{'error': 'Cannot remove yourself'},
status=status.HTTP_400_BAD_REQUEST
)
user.is_active = False
user.save()
return Response({
'message': 'Team member removed successfully'
})
except User.DoesNotExist:
return Response(
{'error': 'User not found'},
status=status.HTTP_404_NOT_FOUND
)
class UsageAnalyticsViewSet(viewsets.ViewSet):
"""Usage analytics and statistics"""
permission_classes = [IsAuthenticated]
@action(detail=False, methods=['get'])
def overview(self, request):
"""Get usage analytics overview"""
account = request.user.account
# Get date range (default: last 30 days)
days = int(request.query_params.get('days', 30))
start_date = timezone.now() - timedelta(days=days)
# Get transactions in period
transactions = CreditTransaction.objects.filter(
account=account,
created_at__gte=start_date
)
# Calculate totals by type
usage_by_type = transactions.filter(
amount__lt=0
).values('transaction_type').annotate(
total=Sum('amount'),
count=Count('id')
)
purchases_by_type = transactions.filter(
amount__gt=0
).values('transaction_type').annotate(
total=Sum('amount'),
count=Count('id')
)
# Daily usage
daily_usage = []
for i in range(days):
date = start_date + timedelta(days=i)
day_txns = transactions.filter(
created_at__date=date.date()
)
usage = day_txns.filter(amount__lt=0).aggregate(Sum('amount'))['amount__sum'] or 0
purchases = day_txns.filter(amount__gt=0).aggregate(Sum('amount'))['amount__sum'] or 0
daily_usage.append({
'date': date.date().isoformat(),
'usage': abs(usage),
'purchases': purchases,
'net': purchases + usage
})
return Response({
'period_days': days,
'start_date': start_date.isoformat(),
'end_date': timezone.now().isoformat(),
'current_balance': account.credit_balance,
'usage_by_type': list(usage_by_type),
'purchases_by_type': list(purchases_by_type),
'daily_usage': daily_usage,
'total_usage': abs(transactions.filter(amount__lt=0).aggregate(Sum('amount'))['amount__sum'] or 0),
'total_purchases': transactions.filter(amount__gt=0).aggregate(Sum('amount'))['amount__sum'] or 0,
})

View File

@@ -0,0 +1,26 @@
"""
URL patterns for account management API
"""
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .account_views import (
AccountSettingsViewSet,
TeamManagementViewSet,
UsageAnalyticsViewSet
)
router = DefaultRouter()
urlpatterns = [
# Account settings (non-router endpoints for simplified access)
path('settings/', AccountSettingsViewSet.as_view({'get': 'retrieve', 'patch': 'partial_update'}), name='account-settings'),
# Team management
path('team/', TeamManagementViewSet.as_view({'get': 'list', 'post': 'create'}), name='team-list'),
path('team/<int:pk>/', TeamManagementViewSet.as_view({'delete': 'destroy'}), name='team-detail'),
# Usage analytics
path('usage/analytics/', UsageAnalyticsViewSet.as_view({'get': 'overview'}), name='usage-analytics'),
path('', include(router.urls)),
]

View File

@@ -19,5 +19,7 @@ router.register(r'transactions', CreditTransactionViewSet, basename='transaction
router.register(r'admin', AdminBillingViewSet, basename='admin-billing')
urlpatterns = [
# Payment methods alias for easier frontend access
path('payment-methods/', PaymentViewSet.as_view({'get': 'available_methods'}), name='payment-methods'),
path('', include(router.urls)),
]

View File

@@ -385,26 +385,94 @@ class AdminBillingViewSet(viewsets.ViewSet):
from django.db.models import Sum, Count
from ...auth.models import Account
from datetime import datetime, timedelta
from django.utils import timezone
# Date ranges
now = timezone.now()
this_month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
last_30_days = now - timedelta(days=30)
# Account stats
total_accounts = Account.objects.count()
active_accounts = Account.objects.filter(is_active=True).count()
new_accounts_this_month = Account.objects.filter(
created_at__gte=this_month_start
).count()
# Subscription stats
active_subscriptions = Account.objects.filter(
subscriptions__status='active'
).distinct().count()
# Revenue stats
total_revenue = Payment.objects.filter(
status='completed',
amount__gt=0
).aggregate(total=Sum('amount'))['total'] or 0
revenue_this_month = Payment.objects.filter(
status='completed',
processed_at__gte=this_month_start,
amount__gt=0
).aggregate(total=Sum('amount'))['total'] or 0
# Credit stats
credits_issued = CreditTransaction.objects.filter(
transaction_type='purchase',
created_at__gte=last_30_days
).aggregate(total=Sum('amount'))['total'] or 0
credits_used = abs(CreditTransaction.objects.filter(
transaction_type__in=['generate_content', 'keyword_research', 'ai_task'],
created_at__gte=last_30_days,
amount__lt=0
).aggregate(total=Sum('amount'))['total'] or 0)
# Payment/Invoice stats
pending_approvals = Payment.objects.filter(
status='pending_approval'
).count()
invoices_pending = Invoice.objects.filter(status='pending').count()
invoices_overdue = Invoice.objects.filter(
status='pending',
due_date__lt=now
).count()
# Recent activity
recent_payments = Payment.objects.filter(
status='completed'
).order_by('-processed_at')[:5]
recent_activity = [
{
'id': pay.id,
'type': 'payment',
'account_name': pay.account.name,
'amount': str(pay.amount),
'currency': pay.currency,
'timestamp': pay.processed_at.isoformat(),
'description': f'Payment received via {pay.payment_method}'
}
for pay in recent_payments
]
return Response({
'total_accounts': total_accounts,
'active_accounts': active_accounts,
'new_accounts_this_month': new_accounts_this_month,
'active_subscriptions': active_subscriptions,
'total_revenue': str(total_revenue),
'revenue_this_month': str(revenue_this_month),
'credits_issued_30d': credits_issued,
'credits_used_30d': credits_used,
'pending_approvals': pending_approvals,
'invoices_pending': Invoice.objects.filter(status='pending').count(),
'invoices_paid': Invoice.objects.filter(status='paid').count()
'invoices_pending': invoices_pending,
'invoices_overdue': invoices_overdue,
'recent_activity': recent_activity,
'system_health': {
'status': 'operational',
'last_check': now.isoformat()
}
})

View File

@@ -37,11 +37,12 @@ urlpatterns = [
path('admin/igny8_core_auth/seedkeyword/csv-import/', seedkeyword_csv_import, name='admin_seedkeyword_csv_import'),
path('admin/', admin.site.urls),
path('api/v1/auth/', include('igny8_core.auth.urls')), # Auth endpoints
path('api/v1/account/', include('igny8_core.api.urls')), # Account management (settings, team, usage)
path('api/v1/planner/', include('igny8_core.modules.planner.urls')),
path('api/v1/writer/', include('igny8_core.modules.writer.urls')),
path('api/v1/system/', include('igny8_core.modules.system.urls')),
path('api/v1/billing/', include('igny8_core.modules.billing.urls')), # Billing endpoints (legacy)
path('api/v1/billing/v2/', include('igny8_core.business.billing.urls')), # New billing endpoints (invoices, payments)
path('api/v1/billing/', include('igny8_core.modules.billing.urls')), # Basic billing endpoints (credits, usage)
path('api/v1/billing/', include('igny8_core.business.billing.urls')), # Advanced billing (invoices, payments, packages)
path('api/v1/admin/', include('igny8_core.modules.billing.admin_urls')), # Admin billing
path('api/v1/automation/', include('igny8_core.business.automation.urls')), # Automation endpoints
path('api/v1/linker/', include('igny8_core.modules.linker.urls')), # Linker endpoints