mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
312 lines
11 KiB
Python
312 lines
11 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select, func
|
|
from uuid import UUID
|
|
from datetime import datetime, timezone
|
|
from typing import Optional
|
|
from pydantic import BaseModel as Schema
|
|
|
|
from app.database import get_db
|
|
from app.api.deps import get_current_user, require_role
|
|
from app.models.user import User
|
|
from app.services.audit_service import record_audit
|
|
from app.services.operations_hub import emit_domain_event
|
|
from app.models.commission import Commission, CommissionStatus
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class CommissionCreate(Schema):
|
|
affiliate_id: UUID
|
|
deal_id: UUID
|
|
amount: float
|
|
rate: float
|
|
plan_type: Optional[str] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class CommissionUpdate(Schema):
|
|
amount: Optional[float] = None
|
|
rate: Optional[float] = None
|
|
plan_type: Optional[str] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class CommissionResponse(Schema):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
affiliate_id: UUID
|
|
deal_id: UUID
|
|
payout_id: Optional[UUID] = None
|
|
amount: float
|
|
rate: float
|
|
plan_type: Optional[str] = None
|
|
status: str
|
|
approved_by: Optional[UUID] = None
|
|
approved_at: Optional[datetime] = None
|
|
held_reason: Optional[str] = None
|
|
paid_at: Optional[datetime] = None
|
|
payment_reference: Optional[str] = None
|
|
dispute_id: Optional[UUID] = None
|
|
notes: Optional[str] = None
|
|
created_at: datetime
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class CommissionListResponse(Schema):
|
|
items: list[CommissionResponse]
|
|
total: int
|
|
page: int
|
|
per_page: int
|
|
|
|
|
|
class HoldRequest(Schema):
|
|
reason: str
|
|
|
|
|
|
class ClawbackRequest(Schema):
|
|
reason: str
|
|
|
|
|
|
@router.get("", response_model=CommissionListResponse)
|
|
async def list_commissions(
|
|
affiliate_id: UUID = Query(None),
|
|
status: str = Query(None),
|
|
page: int = Query(1, ge=1),
|
|
per_page: int = Query(20, ge=1, le=100),
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
query = select(Commission).where(Commission.tenant_id == current_user.tenant_id)
|
|
if affiliate_id:
|
|
query = query.where(Commission.affiliate_id == affiliate_id)
|
|
if status:
|
|
query = query.where(Commission.status == status)
|
|
|
|
total = (await db.execute(select(func.count()).select_from(query.subquery()))).scalar()
|
|
query = query.order_by(Commission.created_at.desc()).offset((page - 1) * per_page).limit(per_page)
|
|
result = await db.execute(query)
|
|
items = [CommissionResponse.model_validate(c) for c in result.scalars().all()]
|
|
return CommissionListResponse(items=items, total=total, page=page, per_page=per_page)
|
|
|
|
|
|
@router.get("/{commission_id}", response_model=CommissionResponse)
|
|
async def get_commission(
|
|
commission_id: UUID,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(
|
|
select(Commission).where(Commission.id == commission_id, Commission.tenant_id == current_user.tenant_id)
|
|
)
|
|
commission = result.scalar_one_or_none()
|
|
if not commission:
|
|
raise HTTPException(status_code=404, detail="Commission not found")
|
|
return CommissionResponse.model_validate(commission)
|
|
|
|
|
|
@router.post("", response_model=CommissionResponse, status_code=201)
|
|
async def create_commission(
|
|
data: CommissionCreate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
commission = Commission(
|
|
tenant_id=current_user.tenant_id,
|
|
status=CommissionStatus.DRAFT,
|
|
**data.model_dump(exclude_none=True),
|
|
)
|
|
db.add(commission)
|
|
await db.flush()
|
|
await db.refresh(commission)
|
|
return CommissionResponse.model_validate(commission)
|
|
|
|
|
|
@router.put("/{commission_id}", response_model=CommissionResponse)
|
|
async def update_commission(
|
|
commission_id: UUID,
|
|
data: CommissionUpdate,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(
|
|
select(Commission).where(Commission.id == commission_id, Commission.tenant_id == current_user.tenant_id)
|
|
)
|
|
commission = result.scalar_one_or_none()
|
|
if not commission:
|
|
raise HTTPException(status_code=404, detail="Commission not found")
|
|
if commission.status not in (CommissionStatus.DRAFT, CommissionStatus.PENDING):
|
|
raise HTTPException(status_code=400, detail="Cannot update commission in current status")
|
|
for field, value in data.model_dump(exclude_none=True).items():
|
|
setattr(commission, field, value)
|
|
await db.flush()
|
|
await db.refresh(commission)
|
|
return CommissionResponse.model_validate(commission)
|
|
|
|
|
|
@router.post("/{commission_id}/approve", response_model=CommissionResponse)
|
|
async def approve_commission(
|
|
commission_id: UUID,
|
|
current_user: User = Depends(require_role("admin", "manager")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(
|
|
select(Commission).where(Commission.id == commission_id, Commission.tenant_id == current_user.tenant_id)
|
|
)
|
|
commission = result.scalar_one_or_none()
|
|
if not commission:
|
|
raise HTTPException(status_code=404, detail="Commission not found")
|
|
if commission.status not in (CommissionStatus.DRAFT, CommissionStatus.PENDING):
|
|
raise HTTPException(status_code=400, detail=f"Cannot approve commission with status '{commission.status.value}'")
|
|
commission.status = CommissionStatus.APPROVED
|
|
commission.approved_by = current_user.id
|
|
commission.approved_at = datetime.now(timezone.utc)
|
|
await db.flush()
|
|
await record_audit(
|
|
db,
|
|
tenant_id=current_user.tenant_id,
|
|
user_id=current_user.id,
|
|
action="commission.approve",
|
|
entity_type="commission",
|
|
entity_id=commission.id,
|
|
changes={"deal_id": str(commission.deal_id), "amount": float(commission.amount)},
|
|
)
|
|
await emit_domain_event(
|
|
db,
|
|
tenant_id=current_user.tenant_id,
|
|
event_type="commission.approved",
|
|
payload={"commission_id": str(commission.id), "deal_id": str(commission.deal_id)},
|
|
)
|
|
await db.refresh(commission)
|
|
return CommissionResponse.model_validate(commission)
|
|
|
|
|
|
@router.post("/{commission_id}/hold", response_model=CommissionResponse)
|
|
async def hold_commission(
|
|
commission_id: UUID,
|
|
data: HoldRequest,
|
|
current_user: User = Depends(require_role("admin", "manager")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(
|
|
select(Commission).where(Commission.id == commission_id, Commission.tenant_id == current_user.tenant_id)
|
|
)
|
|
commission = result.scalar_one_or_none()
|
|
if not commission:
|
|
raise HTTPException(status_code=404, detail="Commission not found")
|
|
commission.status = CommissionStatus.HELD
|
|
commission.held_reason = data.reason
|
|
await db.flush()
|
|
await record_audit(
|
|
db,
|
|
tenant_id=current_user.tenant_id,
|
|
user_id=current_user.id,
|
|
action="commission.hold",
|
|
entity_type="commission",
|
|
entity_id=commission.id,
|
|
changes={"reason": data.reason},
|
|
)
|
|
await emit_domain_event(
|
|
db,
|
|
tenant_id=current_user.tenant_id,
|
|
event_type="commission.held",
|
|
payload={"commission_id": str(commission.id)},
|
|
)
|
|
await db.refresh(commission)
|
|
return CommissionResponse.model_validate(commission)
|
|
|
|
|
|
@router.post("/{commission_id}/pay", response_model=CommissionResponse)
|
|
async def pay_commission(
|
|
commission_id: UUID,
|
|
current_user: User = Depends(require_role("admin")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(
|
|
select(Commission).where(Commission.id == commission_id, Commission.tenant_id == current_user.tenant_id)
|
|
)
|
|
commission = result.scalar_one_or_none()
|
|
if not commission:
|
|
raise HTTPException(status_code=404, detail="Commission not found")
|
|
if commission.status != CommissionStatus.APPROVED:
|
|
raise HTTPException(status_code=400, detail="Commission must be approved before payment")
|
|
commission.status = CommissionStatus.PAID
|
|
commission.paid_at = datetime.now(timezone.utc)
|
|
await db.flush()
|
|
await record_audit(
|
|
db,
|
|
tenant_id=current_user.tenant_id,
|
|
user_id=current_user.id,
|
|
action="commission.pay",
|
|
entity_type="commission",
|
|
entity_id=commission.id,
|
|
changes={"paid_at": commission.paid_at.isoformat() if commission.paid_at else None},
|
|
)
|
|
await emit_domain_event(
|
|
db,
|
|
tenant_id=current_user.tenant_id,
|
|
event_type="commission.paid",
|
|
payload={"commission_id": str(commission.id)},
|
|
)
|
|
await db.refresh(commission)
|
|
return CommissionResponse.model_validate(commission)
|
|
|
|
|
|
@router.post("/{commission_id}/dispute", response_model=CommissionResponse)
|
|
async def dispute_commission(
|
|
commission_id: UUID,
|
|
current_user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(
|
|
select(Commission).where(Commission.id == commission_id, Commission.tenant_id == current_user.tenant_id)
|
|
)
|
|
commission = result.scalar_one_or_none()
|
|
if not commission:
|
|
raise HTTPException(status_code=404, detail="Commission not found")
|
|
commission.status = CommissionStatus.DISPUTED
|
|
await db.flush()
|
|
await db.refresh(commission)
|
|
return CommissionResponse.model_validate(commission)
|
|
|
|
|
|
@router.post("/{commission_id}/clawback", response_model=CommissionResponse)
|
|
async def clawback_commission(
|
|
commission_id: UUID,
|
|
data: ClawbackRequest,
|
|
current_user: User = Depends(require_role("admin")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(
|
|
select(Commission).where(Commission.id == commission_id, Commission.tenant_id == current_user.tenant_id)
|
|
)
|
|
commission = result.scalar_one_or_none()
|
|
if not commission:
|
|
raise HTTPException(status_code=404, detail="Commission not found")
|
|
if commission.status != CommissionStatus.PAID:
|
|
raise HTTPException(status_code=400, detail="Can only clawback paid commissions")
|
|
commission.status = CommissionStatus.CLAWBACK
|
|
commission.notes = f"Clawback: {data.reason}" + (f"\n{commission.notes}" if commission.notes else "")
|
|
await db.flush()
|
|
await db.refresh(commission)
|
|
return CommissionResponse.model_validate(commission)
|
|
|
|
|
|
@router.delete("/{commission_id}", status_code=204)
|
|
async def delete_commission(
|
|
commission_id: UUID,
|
|
current_user: User = Depends(require_role("admin")),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(
|
|
select(Commission).where(Commission.id == commission_id, Commission.tenant_id == current_user.tenant_id)
|
|
)
|
|
commission = result.scalar_one_or_none()
|
|
if not commission:
|
|
raise HTTPException(status_code=404, detail="Commission not found")
|
|
if commission.status not in (CommissionStatus.DRAFT,):
|
|
raise HTTPException(status_code=400, detail="Can only delete draft commissions")
|
|
await db.delete(commission)
|
|
await db.flush()
|