""" Device Service - 设备管理服务 Provides CRUD operations and statistics for badge devices. """ from datetime import datetime, timezone from sqlalchemy import func, select, or_ from sqlalchemy.ext.asyncio import AsyncSession from app.models import Device from app.schemas import DeviceCreate, DeviceUpdate async def get_devices( db: AsyncSession, page: int = 1, page_size: int = 20, status_filter: str | None = None, search: str | None = None, ) -> tuple[list[Device], int]: """ 获取设备列表(分页)/ Get paginated device list. Parameters ---------- db : AsyncSession Database session. page : int Page number (1-indexed). page_size : int Number of items per page. status_filter : str, optional Filter by device status (online / offline). search : str, optional Search by IMEI or device name. Returns ------- tuple[list[Device], int] (list of devices, total count) """ query = select(Device) count_query = select(func.count(Device.id)) if status_filter: query = query.where(Device.status == status_filter) count_query = count_query.where(Device.status == status_filter) if search: pattern = f"%{search}%" search_clause = or_( Device.imei.ilike(pattern), Device.name.ilike(pattern), ) query = query.where(search_clause) count_query = count_query.where(search_clause) # Total count total_result = await db.execute(count_query) total = total_result.scalar() or 0 # Paginated results offset = (page - 1) * page_size query = query.order_by(Device.updated_at.desc()).offset(offset).limit(page_size) result = await db.execute(query) devices = list(result.scalars().all()) return devices, total async def get_device(db: AsyncSession, device_id: int) -> Device | None: """ 按ID获取设备 / Get device by ID. Parameters ---------- db : AsyncSession Database session. device_id : int Device primary key. Returns ------- Device | None """ result = await db.execute(select(Device).where(Device.id == device_id)) return result.scalar_one_or_none() async def get_device_by_imei(db: AsyncSession, imei: str) -> Device | None: """ 按IMEI获取设备 / Get device by IMEI number. Parameters ---------- db : AsyncSession Database session. imei : str Device IMEI number. Returns ------- Device | None """ result = await db.execute(select(Device).where(Device.imei == imei)) return result.scalar_one_or_none() async def create_device(db: AsyncSession, device_data: DeviceCreate) -> Device: """ 创建设备 / Create a new device. Parameters ---------- db : AsyncSession Database session. device_data : DeviceCreate Device creation data. Returns ------- Device The newly created device. """ device = Device(**device_data.model_dump()) db.add(device) await db.flush() await db.refresh(device) return device async def update_device( db: AsyncSession, device_id: int, device_data: DeviceUpdate ) -> Device | None: """ 更新设备信息 / Update device information. Parameters ---------- db : AsyncSession Database session. device_id : int Device primary key. device_data : DeviceUpdate Fields to update (only non-None fields are applied). Returns ------- Device | None The updated device, or None if not found. """ device = await get_device(db, device_id) if device is None: return None update_fields = device_data.model_dump(exclude_unset=True) for field, value in update_fields.items(): setattr(device, field, value) device.updated_at = datetime.now(timezone.utc) await db.flush() await db.refresh(device) return device async def delete_device(db: AsyncSession, device_id: int) -> bool: """ 删除设备 / Delete a device. Parameters ---------- db : AsyncSession Database session. device_id : int Device primary key. Returns ------- bool True if the device was deleted, False if not found. """ device = await get_device(db, device_id) if device is None: return False await db.delete(device) await db.flush() return True async def get_device_stats(db: AsyncSession) -> dict: """ 获取设备统计信息 / Get device statistics. Returns ------- dict {"total": int, "online": int, "offline": int} """ total_result = await db.execute(select(func.count(Device.id))) total = total_result.scalar() or 0 online_result = await db.execute( select(func.count(Device.id)).where(Device.status == "online") ) online = online_result.scalar() or 0 offline = total - online return { "total": total, "online": online, "offline": offline, }