felhasználói felületre pü
This commit is contained in:
@@ -1,22 +1,29 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/api/v1/endpoints/finance_admin.py
|
||||
"""
|
||||
Finance Admin API endpoints for managing Issuers with strict RBAC protection.
|
||||
Finance Admin API endpoints for managing Issuers and FinancialLedger with strict RBAC protection.
|
||||
Protected by DB-driven RequirePermission("finance:view") dependency.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
import logging
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select
|
||||
from typing import List
|
||||
from sqlalchemy import select, func, text
|
||||
from typing import List, Optional
|
||||
|
||||
from app.api import deps
|
||||
from app.models.identity import User
|
||||
from app.models.marketplace.finance import Issuer
|
||||
from app.schemas.finance import IssuerResponse, IssuerUpdate
|
||||
from app.models.system.audit import FinancialLedger
|
||||
from app.schemas.finance import IssuerResponse, IssuerUpdate, FinancialLedgerListResponse, FinancialLedgerResponse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# Issuer Endpoints (existing)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/", response_model=List[IssuerResponse], tags=["finance-admin"])
|
||||
async def list_issuers(
|
||||
db: AsyncSession = Depends(deps.get_db),
|
||||
@@ -27,6 +34,7 @@ async def list_issuers(
|
||||
List all Issuers (billing entities).
|
||||
Protected by RequirePermission("finance:view").
|
||||
"""
|
||||
from app.models.marketplace.finance import Issuer
|
||||
result = await db.execute(select(Issuer).order_by(Issuer.id))
|
||||
issuers = result.scalars().all()
|
||||
return issuers
|
||||
@@ -44,6 +52,7 @@ async def update_issuer(
|
||||
Update an Issuer's details (activate/deactivate, revenue limit, API config).
|
||||
Protected by RequirePermission("finance:edit").
|
||||
"""
|
||||
from app.models.marketplace.finance import Issuer
|
||||
result = await db.execute(select(Issuer).where(Issuer.id == issuer_id))
|
||||
issuer = result.scalar_one_or_none()
|
||||
|
||||
@@ -61,4 +70,134 @@ async def update_issuer(
|
||||
await db.commit()
|
||||
await db.refresh(issuer)
|
||||
|
||||
return issuer
|
||||
return issuer
|
||||
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
# FinancialLedger Admin Endpoints (NEW)
|
||||
# ──────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/ledger", response_model=FinancialLedgerListResponse, tags=["finance-admin"])
|
||||
async def list_financial_ledger(
|
||||
page: int = Query(1, ge=1, description="Oldalszám (1-indexelt)"),
|
||||
page_size: int = Query(50, ge=1, le=200, description="Rekordok száma oldalanként"),
|
||||
user_id: Optional[int] = Query(None, description="Szűrés felhasználó ID alapján"),
|
||||
transaction_type: Optional[str] = Query(None, description="Tranzakció típus szűrés"),
|
||||
entry_type: Optional[str] = Query(None, description="Könyvelési irány (DEBIT / CREDIT)"),
|
||||
wallet_type: Optional[str] = Query(None, description="Tárcatípus szűrés"),
|
||||
date_from: Optional[str] = Query(None, description="Dátum szűrés kezdete (ISO, pl. 2026-01-01)"),
|
||||
date_to: Optional[str] = Query(None, description="Dátum szűrés vége (ISO, pl. 2026-12-31)"),
|
||||
sort_by: str = Query("created_at", description="Rendezési mező (created_at, amount, id)"),
|
||||
sort_order: str = Query("desc", description="Rendezési irány (asc / desc)"),
|
||||
db: AsyncSession = Depends(deps.get_db),
|
||||
current_user: User = Depends(deps.get_current_active_user),
|
||||
_ = Depends(deps.RequirePermission("finance:view")),
|
||||
):
|
||||
"""
|
||||
List FinancialLedger entries with pagination, filtering, and user details.
|
||||
|
||||
Uses raw SQL with explicit joins to avoid SQLAlchemy relationship issues.
|
||||
Protected by RequirePermission("finance:view").
|
||||
|
||||
Returns paginated results with joined user email and person name.
|
||||
"""
|
||||
# ── Build WHERE clauses dynamically ──
|
||||
where_clauses = []
|
||||
params = {}
|
||||
|
||||
if user_id is not None:
|
||||
where_clauses.append("fl.user_id = :user_id")
|
||||
params["user_id"] = user_id
|
||||
if transaction_type is not None:
|
||||
where_clauses.append("fl.transaction_type = :transaction_type")
|
||||
params["transaction_type"] = transaction_type
|
||||
if entry_type is not None:
|
||||
where_clauses.append("fl.entry_type = :entry_type")
|
||||
params["entry_type"] = entry_type
|
||||
if wallet_type is not None:
|
||||
where_clauses.append("fl.wallet_type = :wallet_type")
|
||||
params["wallet_type"] = wallet_type
|
||||
if date_from is not None:
|
||||
where_clauses.append("fl.created_at >= :date_from::timestamp")
|
||||
params["date_from"] = date_from
|
||||
if date_to is not None:
|
||||
where_clauses.append("fl.created_at <= :date_to::timestamp")
|
||||
params["date_to"] = date_to
|
||||
|
||||
where_sql = " AND ".join(where_clauses) if where_clauses else "TRUE"
|
||||
|
||||
# Validate sort_by to prevent SQL injection
|
||||
allowed_sort_fields = {"created_at", "amount", "id"}
|
||||
if sort_by not in allowed_sort_fields:
|
||||
sort_by = "created_at"
|
||||
sort_direction = "ASC" if sort_order == "asc" else "DESC"
|
||||
|
||||
# ── Count query ──
|
||||
count_sql = f"""
|
||||
SELECT COUNT(*) FROM audit.financial_ledger fl
|
||||
WHERE {where_sql}
|
||||
"""
|
||||
count_result = await db.execute(text(count_sql), params)
|
||||
total = count_result.scalar() or 0
|
||||
|
||||
# ── Data query with joins ──
|
||||
offset = (page - 1) * page_size
|
||||
data_sql = f"""
|
||||
SELECT
|
||||
fl.id,
|
||||
fl.user_id,
|
||||
fl.person_id,
|
||||
fl.amount,
|
||||
fl.currency,
|
||||
fl.transaction_type,
|
||||
fl.entry_type::text,
|
||||
fl.wallet_type::text,
|
||||
fl.status::text,
|
||||
fl.details,
|
||||
fl.created_at,
|
||||
u.email AS user_email,
|
||||
p.first_name AS person_first_name,
|
||||
p.last_name AS person_last_name
|
||||
FROM audit.financial_ledger fl
|
||||
LEFT JOIN identity.users u ON u.id = fl.user_id
|
||||
LEFT JOIN identity.persons p ON p.id = u.person_id
|
||||
WHERE {where_sql}
|
||||
ORDER BY fl.{sort_by} {sort_direction}
|
||||
LIMIT :limit OFFSET :offset
|
||||
"""
|
||||
params["limit"] = page_size
|
||||
params["offset"] = offset
|
||||
data_result = await db.execute(text(data_sql), params)
|
||||
rows = data_result.all()
|
||||
|
||||
# ── Build response ──
|
||||
items = []
|
||||
for row in rows:
|
||||
user_name = None
|
||||
if row.person_first_name or row.person_last_name:
|
||||
parts = [p for p in [row.person_last_name, row.person_first_name] if p]
|
||||
user_name = " ".join(parts)
|
||||
|
||||
items.append(FinancialLedgerResponse(
|
||||
id=row.id,
|
||||
user_id=row.user_id,
|
||||
person_id=row.person_id,
|
||||
amount=float(row.amount) if row.amount else 0.0,
|
||||
currency=row.currency,
|
||||
transaction_type=row.transaction_type,
|
||||
entry_type=row.entry_type,
|
||||
wallet_type=row.wallet_type,
|
||||
status=row.status,
|
||||
details=row.details,
|
||||
created_at=row.created_at,
|
||||
user_email=row.user_email,
|
||||
user_name=user_name,
|
||||
))
|
||||
|
||||
return FinancialLedgerListResponse(
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
items=items,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user