2025-11-10 15:46:40 +01:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
|
from decimal import Decimal
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
|
|
|
|
from ..auth import get_current_user
|
|
|
|
|
from ..models import User
|
|
|
|
|
from ..billing.models import PricingConfig, Invoice, SubscriptionPlan
|
|
|
|
|
from ..billing.invoice_generator import InvoiceGenerator
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
def require_superuser(current_user: User = Depends(get_current_user)):
|
|
|
|
|
if not current_user.is_superuser:
|
|
|
|
|
raise HTTPException(status_code=403, detail="Superuser privileges required")
|
|
|
|
|
return current_user
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
router = APIRouter(
|
|
|
|
|
prefix="/api/admin/billing",
|
|
|
|
|
tags=["admin", "billing"],
|
2025-11-13 23:22:05 +01:00
|
|
|
dependencies=[Depends(require_superuser)],
|
2025-11-10 15:46:40 +01:00
|
|
|
)
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
class PricingConfigUpdate(BaseModel):
|
|
|
|
|
config_key: str
|
|
|
|
|
config_value: float
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
class PlanCreate(BaseModel):
|
|
|
|
|
name: str
|
|
|
|
|
display_name: str
|
|
|
|
|
description: str
|
|
|
|
|
storage_gb: int
|
|
|
|
|
bandwidth_gb: int
|
|
|
|
|
price_monthly: float
|
|
|
|
|
price_yearly: float = None
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
@router.get("/pricing")
|
|
|
|
|
async def get_all_pricing(current_user: User = Depends(require_superuser)):
|
|
|
|
|
configs = await PricingConfig.all()
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
"id": c.id,
|
|
|
|
|
"config_key": c.config_key,
|
|
|
|
|
"config_value": float(c.config_value),
|
|
|
|
|
"description": c.description,
|
|
|
|
|
"unit": c.unit,
|
2025-11-13 23:22:05 +01:00
|
|
|
"updated_at": c.updated_at,
|
2025-11-10 15:46:40 +01:00
|
|
|
}
|
|
|
|
|
for c in configs
|
|
|
|
|
]
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
@router.put("/pricing/{config_id}")
|
|
|
|
|
async def update_pricing(
|
|
|
|
|
config_id: int,
|
|
|
|
|
update: PricingConfigUpdate,
|
2025-11-13 23:22:05 +01:00
|
|
|
current_user: User = Depends(require_superuser),
|
2025-11-10 15:46:40 +01:00
|
|
|
):
|
|
|
|
|
config = await PricingConfig.get_or_none(id=config_id)
|
|
|
|
|
if not config:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Config not found")
|
|
|
|
|
|
|
|
|
|
config.config_value = Decimal(str(update.config_value))
|
|
|
|
|
config.updated_by = current_user
|
|
|
|
|
await config.save()
|
|
|
|
|
|
|
|
|
|
return {"message": "Pricing updated successfully"}
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
@router.post("/generate-invoices/{year}/{month}")
|
|
|
|
|
async def generate_all_invoices(
|
2025-11-13 23:22:05 +01:00
|
|
|
year: int, month: int, current_user: User = Depends(require_superuser)
|
2025-11-10 15:46:40 +01:00
|
|
|
):
|
|
|
|
|
users = await User.filter(is_active=True).all()
|
|
|
|
|
generated = []
|
|
|
|
|
skipped = []
|
|
|
|
|
|
|
|
|
|
for user in users:
|
|
|
|
|
invoice = await InvoiceGenerator.generate_monthly_invoice(user, year, month)
|
|
|
|
|
if invoice:
|
2025-11-13 23:22:05 +01:00
|
|
|
generated.append(
|
|
|
|
|
{
|
|
|
|
|
"user_id": user.id,
|
|
|
|
|
"invoice_id": invoice.id,
|
|
|
|
|
"total": float(invoice.total),
|
|
|
|
|
}
|
|
|
|
|
)
|
2025-11-10 15:46:40 +01:00
|
|
|
else:
|
|
|
|
|
skipped.append(user.id)
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
return {"generated": len(generated), "skipped": len(skipped), "invoices": generated}
|
|
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
|
|
|
|
|
@router.post("/plans")
|
|
|
|
|
async def create_plan(
|
2025-11-13 23:22:05 +01:00
|
|
|
plan_data: PlanCreate, current_user: User = Depends(require_superuser)
|
2025-11-10 15:46:40 +01:00
|
|
|
):
|
|
|
|
|
plan = await SubscriptionPlan.create(**plan_data.dict())
|
|
|
|
|
return {"id": plan.id, "message": "Plan created successfully"}
|
|
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
|
2025-11-10 15:46:40 +01:00
|
|
|
@router.get("/stats")
|
|
|
|
|
async def get_billing_stats(current_user: User = Depends(require_superuser)):
|
2025-11-13 23:22:05 +01:00
|
|
|
from tortoise.functions import Sum
|
2025-11-10 15:46:40 +01:00
|
|
|
|
2025-11-13 23:22:05 +01:00
|
|
|
total_revenue = (
|
|
|
|
|
await Invoice.filter(status="paid")
|
|
|
|
|
.annotate(total_sum=Sum("total"))
|
|
|
|
|
.values("total_sum")
|
|
|
|
|
)
|
2025-11-10 15:46:40 +01:00
|
|
|
|
|
|
|
|
invoice_count = await Invoice.all().count()
|
|
|
|
|
pending_invoices = await Invoice.filter(status="open").count()
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"total_revenue": float(total_revenue[0]["total_sum"] or 0),
|
|
|
|
|
"total_invoices": invoice_count,
|
2025-11-13 23:22:05 +01:00
|
|
|
"pending_invoices": pending_invoices,
|
2025-11-10 15:46:40 +01:00
|
|
|
}
|