|
|
@@ -4,10 +4,12 @@ Device management service.
|
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
|
-from sqlalchemy import func, select
|
|
|
+from sqlalchemy import String, func, or_, select
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
+from sqlalchemy.orm import joinedload
|
|
|
|
|
|
from app.models.device import Device
|
|
|
+from app.models.organization import Organization
|
|
|
from app.schemas.device import DeviceCreate, DeviceUpdate
|
|
|
|
|
|
|
|
|
@@ -87,6 +89,7 @@ async def list_devices(
|
|
|
limit: int = 100,
|
|
|
organization_id: int | None = None,
|
|
|
status: str | None = None,
|
|
|
+ search: str | None = None,
|
|
|
) -> tuple[list[Device], int]:
|
|
|
"""
|
|
|
List devices with pagination and filters.
|
|
|
@@ -97,33 +100,63 @@ async def list_devices(
|
|
|
limit: Maximum number of records to return
|
|
|
organization_id: Filter by organization (optional)
|
|
|
status: Filter by status (optional)
|
|
|
+ search: Universal search across all fields (optional)
|
|
|
|
|
|
Returns:
|
|
|
Tuple of (devices list, total count)
|
|
|
"""
|
|
|
- # Build query
|
|
|
- query = select(Device)
|
|
|
+ # Build query with Organization join for search
|
|
|
+ query = select(Device).options(joinedload(Device.organization))
|
|
|
+
|
|
|
+ # Base filters
|
|
|
+ filters = []
|
|
|
|
|
|
if organization_id is not None:
|
|
|
- query = query.where(Device.organization_id == organization_id)
|
|
|
+ filters.append(Device.organization_id == organization_id)
|
|
|
|
|
|
if status:
|
|
|
- query = query.where(Device.status == status)
|
|
|
+ filters.append(Device.status == status)
|
|
|
+
|
|
|
+ # Universal search filter
|
|
|
+ if search and len(search) >= 2:
|
|
|
+ # Join with Organization for searching by org name/email
|
|
|
+ query = query.join(Organization, Device.organization_id == Organization.id, isouter=True)
|
|
|
+
|
|
|
+ # Search across multiple fields
|
|
|
+ search_pattern = f"%{search}%"
|
|
|
+ search_filters = [
|
|
|
+ Device.mac_address.ilike(search_pattern),
|
|
|
+ func.cast(Device.simple_id, String).ilike(search_pattern),
|
|
|
+ ]
|
|
|
+
|
|
|
+ # Only add organization filters if we have organization data
|
|
|
+ search_filters.extend([
|
|
|
+ Organization.name.ilike(search_pattern),
|
|
|
+ Organization.contact_email.ilike(search_pattern),
|
|
|
+ ])
|
|
|
+
|
|
|
+ filters.append(or_(*search_filters))
|
|
|
+
|
|
|
+ # Apply all filters
|
|
|
+ if filters:
|
|
|
+ query = query.where(*filters)
|
|
|
|
|
|
# Get total count
|
|
|
count_query = select(func.count()).select_from(Device)
|
|
|
|
|
|
- if organization_id is not None:
|
|
|
- count_query = count_query.where(Device.organization_id == organization_id)
|
|
|
+ if search and len(search) >= 2:
|
|
|
+ count_query = count_query.join(
|
|
|
+ Organization, Device.organization_id == Organization.id, isouter=True
|
|
|
+ )
|
|
|
|
|
|
- if status:
|
|
|
- count_query = count_query.where(Device.status == status)
|
|
|
+ if filters:
|
|
|
+ count_query = count_query.where(*filters)
|
|
|
|
|
|
total_result = await db.execute(count_query)
|
|
|
total = total_result.scalar_one()
|
|
|
|
|
|
# Get paginated results
|
|
|
- query = query.offset(skip).limit(limit).order_by(Device.simple_id)
|
|
|
+ query = query.offset(skip).limit(limit).order_by(Device.simple_id.desc())
|
|
|
result = await db.execute(query)
|
|
|
devices = list(result.scalars().all())
|
|
|
|