TorbatYar/backend/services/loyalty/app/api/v1/ledger.py
Mortezakoohjani 071c484530 Ship Loyalty phases 7.2-7.6 (points through wallet) with production deploy.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 21:27:22 +03:30

116 lines
3.7 KiB
Python

"""Point ledger APIs — Phase 7.2."""
from __future__ import annotations
from uuid import UUID
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.deps import get_db, require_tenant
from app.api.permissions import require_permissions
from app.models.types import LedgerEntryType
from app.permissions.definitions import (
LOYALTY_POINTS_ADJUST,
LOYALTY_POINTS_EARN,
LOYALTY_POINTS_EXPIRE,
LOYALTY_POINTS_REDEEM,
LOYALTY_POINTS_VIEW,
)
from app.schemas.ledger import (
PointAdjustRequest,
PointBalanceRead,
PointEarnRequest,
PointExpireRequest,
PointLedgerEntryRead,
PointLedgerPage,
PointRedeemRequest,
)
from app.services.point_engine import PointEngineService
from shared.security import CurrentUser
router = APIRouter()
@router.get("/{account_id}/balance", response_model=PointBalanceRead)
async def get_balance(
account_id: UUID,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(require_permissions(LOYALTY_POINTS_VIEW)),
):
return await PointEngineService(db).get_balance(tenant_id, account_id)
@router.get("/{account_id}/ledger", response_model=PointLedgerPage)
async def list_ledger(
account_id: UUID,
offset: int = Query(default=0, ge=0),
limit: int = Query(default=50, ge=1, le=200),
entry_type: LedgerEntryType | None = None,
q: str | None = Query(default=None, max_length=100),
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
_user: CurrentUser = Depends(require_permissions(LOYALTY_POINTS_VIEW)),
):
rows, total = await PointEngineService(db).list_entries(
tenant_id,
account_id,
entry_type=entry_type,
offset=offset,
limit=limit,
q=q,
)
return PointLedgerPage(
items=rows, total=total, offset=offset, limit=limit
)
@router.post("/{account_id}/earn", response_model=PointLedgerEntryRead)
async def earn_points(
account_id: UUID,
body: PointEarnRequest,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
user: CurrentUser = Depends(require_permissions(LOYALTY_POINTS_EARN)),
):
return await PointEngineService(db).earn(tenant_id, account_id, body, actor=user)
@router.post("/{account_id}/redeem", response_model=PointLedgerEntryRead)
async def redeem_points(
account_id: UUID,
body: PointRedeemRequest,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
user: CurrentUser = Depends(require_permissions(LOYALTY_POINTS_REDEEM)),
):
return await PointEngineService(db).redeem(
tenant_id, account_id, body, actor=user
)
@router.post("/{account_id}/adjust", response_model=PointLedgerEntryRead)
async def adjust_points(
account_id: UUID,
body: PointAdjustRequest,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
user: CurrentUser = Depends(require_permissions(LOYALTY_POINTS_ADJUST)),
):
return await PointEngineService(db).adjust(
tenant_id, account_id, body, actor=user
)
@router.post("/{account_id}/expire-points", response_model=PointLedgerEntryRead)
async def expire_points(
account_id: UUID,
body: PointExpireRequest,
tenant_id: UUID = Depends(require_tenant),
db: AsyncSession = Depends(get_db),
user: CurrentUser = Depends(require_permissions(LOYALTY_POINTS_EXPIRE)),
):
return await PointEngineService(db).expire(
tenant_id, account_id, body, actor=user
)