from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Sum from django.shortcuts import get_object_or_404, redirect from django.urls import reverse_lazy from django.views import View from django.views.generic import FormView, TemplateView from apps.billing.models import PointsLedger, RechargeOrder from apps.billing.services import get_balance_snapshot from apps.users.models import ApiKey from .forms import ApiKeyCreateForm NEW_API_KEY_SESSION_KEY = "portal_new_api_key" RECORDS_PAGE_SIZE = 50 def _sum_or_zero(queryset, field_name: str): return queryset.aggregate(total=Sum(field_name))["total"] or 0 def get_portal_account_summary(user) -> dict: paid_orders = RechargeOrder.objects.filter( user=user, status=RechargeOrder.Status.PAID, ) consume_entries = PointsLedger.objects.filter( user=user, change_type=PointsLedger.ChangeType.CONSUME, ) refund_entries = PointsLedger.objects.filter( user=user, change_type=PointsLedger.ChangeType.REFUND, ) recharge_entries = PointsLedger.objects.filter( user=user, change_type=PointsLedger.ChangeType.RECHARGE, ) consumed_points = abs(int(_sum_or_zero(consume_entries, "points_delta"))) refunded_points = int(_sum_or_zero(refund_entries, "points_delta")) return { "recharge_total_amount": _sum_or_zero(paid_orders, "amount_money"), "recharge_points_total": int(_sum_or_zero(recharge_entries, "points_delta")), "consumed_points_total": consumed_points, "refunded_points_total": refunded_points, "net_used_points": consumed_points - refunded_points, "paid_recharge_count": paid_orders.count(), } def get_recharge_orders_for_user(user): return RechargeOrder.objects.filter(user=user).order_by("-created_at", "-id") def get_usage_ledger_entries_for_user(user): return ( PointsLedger.objects.filter( user=user, change_type__in=( PointsLedger.ChangeType.CONSUME, PointsLedger.ChangeType.REFUND, ), ) .select_related("ref_call", "ref_call__api_key") .order_by("-created_at", "-id") ) class DashboardView(LoginRequiredMixin, TemplateView): template_name = "portal/dashboard.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["balance"] = get_balance_snapshot(self.request.user) context.update(get_portal_account_summary(self.request.user)) context["recent_recharge_orders"] = get_recharge_orders_for_user( self.request.user )[:5] context["recent_usage_entries"] = get_usage_ledger_entries_for_user( self.request.user )[:5] return context class ApiKeyListCreateView(LoginRequiredMixin, FormView): template_name = "portal/apikeys.html" form_class = ApiKeyCreateForm success_url = reverse_lazy("portal-apikeys") def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["api_keys"] = ApiKey.objects.filter(user=self.request.user).order_by( "-created_at", "-id", ) context["new_api_key"] = self.request.session.pop(NEW_API_KEY_SESSION_KEY, None) return context def form_valid(self, form): name = form.cleaned_data["name"].strip() api_key, raw_key = ApiKey.create_for_user(self.request.user, name=name) self.request.session[NEW_API_KEY_SESSION_KEY] = { "name": api_key.name, "key_prefix": api_key.key_prefix, "raw_key": raw_key, } messages.success(self.request, "API Key 已生成") return super().form_valid(form) class ApiKeyDeleteView(LoginRequiredMixin, View): def post(self, request, pk): api_key = get_object_or_404(ApiKey, pk=pk, user=request.user) if api_key.status != ApiKey.Status.REVOKED: api_key.status = ApiKey.Status.REVOKED api_key.save(update_fields=("status", "updated_at")) messages.success(request, "API Key 已删除") return redirect("portal-apikeys") class RechargeRecordListView(LoginRequiredMixin, TemplateView): template_name = "portal/recharge_records.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) orders = get_recharge_orders_for_user(self.request.user) context.update(get_portal_account_summary(self.request.user)) context["recharge_orders"] = orders[:RECORDS_PAGE_SIZE] return context class UsageRecordListView(LoginRequiredMixin, TemplateView): template_name = "portal/usage_records.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) entries = get_usage_ledger_entries_for_user(self.request.user) context.update(get_portal_account_summary(self.request.user)) context["usage_entries"] = entries[:RECORDS_PAGE_SIZE] return context