from typing import Literal from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.models.account import Account from app.models.subscription import Subscription from app.models.user import User from app.schemas.seat_enforcement import SeatCheckResult Role = Literal['engineer', 'l1_tech'] def _limit_for_role(subscription: Subscription, role: Role) -> int | None: if role == 'engineer': return subscription.seat_limit if role == 'l1_tech': return subscription.l1_seat_limit raise ValueError(f"Unknown role: {role}") async def check_seat_available( account: Account, subscription: Subscription, role: Role, db: AsyncSession, ) -> SeatCheckResult: """ Count active users with the given role in the account, compare against the role-specific seat limit on the subscription. Returns availability. None limit = unlimited (returns available=True). """ limit = _limit_for_role(subscription, role) stmt = ( select(func.count(User.id)) .where(User.account_id == account.id) .where(User.account_role == role) .where(User.is_active.is_(True)) ) current = (await db.execute(stmt)).scalar_one() if limit is None: return SeatCheckResult(available=True, current=current, limit=None, role=role) return SeatCheckResult( available=current < limit, current=current, limit=limit, role=role, ) async def get_seat_usage( account: Account, subscription: Subscription, db: AsyncSession, ) -> tuple[SeatCheckResult, SeatCheckResult]: """Return (engineer, l1_tech) seat-usage tuple for the seat-counter widget.""" eng = await check_seat_available(account, subscription, 'engineer', db) l1 = await check_seat_available(account, subscription, 'l1_tech', db) return eng, l1