Files

226 lines
7.5 KiB
Python

"""EquipmentGroup (Bundle) CRUD endpoints."""
from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload, selectinload
from app.api.deps import require_permission
from app.db.session import get_async_session
from app.models import User, EquipmentGroup, Equipment
from app.models.equipment_group import equipment_group_items
from app.schemas.equipment_group import (
EquipmentGroupCreateRequest,
EquipmentGroupUpdateRequest,
EquipmentGroupResponse,
EquipmentGroupListResponse,
)
router = APIRouter(prefix="/equipment-groups", tags=["equipment-groups"])
def _load_items(group: EquipmentGroup) -> list[dict]:
"""Build items list from M2M relationship."""
# The items relationship loads Equipment objects; we need quantity from the association
# We'll use a separate query approach in the endpoints.
return []
@router.get("", response_model=EquipmentGroupListResponse)
async def list_equipment_groups(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
search: str | None = Query(None, description="Search in name"),
current_user: User = Depends(require_permission("equipment:read")),
session: AsyncSession = Depends(get_async_session),
):
"""List equipment groups with search and pagination."""
account_id = current_user.account_id
base_q = select(EquipmentGroup).where(EquipmentGroup.account_id == account_id)
if search:
pattern = f"%{search}%"
base_q = base_q.where(EquipmentGroup.name.ilike(pattern))
count_q = select(func.count()).select_from(base_q.subquery())
total = (await session.execute(count_q)).scalar() or 0
q = (
base_q.options(
joinedload(EquipmentGroup.default_location),
selectinload(EquipmentGroup.items),
)
.order_by(EquipmentGroup.name)
.offset((page - 1) * size)
.limit(size)
)
result = await session.execute(q)
groups = result.unique().scalars().all()
return EquipmentGroupListResponse(
items=[EquipmentGroupResponse.model_validate(g) for g in groups],
total=total,
page=page,
size=size,
)
@router.post(
"", response_model=EquipmentGroupResponse, status_code=status.HTTP_201_CREATED
)
async def create_equipment_group(
body: EquipmentGroupCreateRequest,
current_user: User = Depends(require_permission("equipment:write")),
session: AsyncSession = Depends(get_async_session),
):
"""Create a new equipment group (bundle)."""
account_id = current_user.account_id
group = EquipmentGroup(
account_id=account_id,
name=body.name,
description=body.description,
daily_rate=body.daily_rate,
default_location_id=body.default_location_id,
)
if body.items:
equipment_ids = [i.equipment_id for i in body.items]
eq_result = await session.execute(
select(Equipment).where(
Equipment.id.in_(equipment_ids),
Equipment.account_id == account_id,
)
)
items = eq_result.scalars().all()
group.items = items
# Store quantities via the association table directly
for item_schema in body.items:
await session.execute(
equipment_group_items.update()
.where(
equipment_group_items.c.group_id == group.id,
equipment_group_items.c.equipment_id == item_schema.equipment_id,
)
.values(quantity=item_schema.quantity)
)
session.add(group)
await session.commit()
await session.refresh(group)
# Reload with relationships
await session.execute(
select(EquipmentGroup)
.options(
joinedload(EquipmentGroup.default_location),
selectinload(EquipmentGroup.items),
)
.where(EquipmentGroup.id == group.id)
)
await session.refresh(group)
return EquipmentGroupResponse.model_validate(group)
@router.get("/{group_id}", response_model=EquipmentGroupResponse)
async def get_equipment_group(
group_id: str,
current_user: User = Depends(require_permission("equipment:read")),
session: AsyncSession = Depends(get_async_session),
):
"""Get a specific equipment group with items."""
account_id = current_user.account_id
result = await session.execute(
select(EquipmentGroup)
.options(
joinedload(EquipmentGroup.default_location),
selectinload(EquipmentGroup.items),
)
.where(EquipmentGroup.id == group_id, EquipmentGroup.account_id == account_id)
)
group = result.unique().scalars().first()
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Equipment group not found"
)
return EquipmentGroupResponse.model_validate(group)
@router.put("/{group_id}", response_model=EquipmentGroupResponse)
async def update_equipment_group(
group_id: str,
body: EquipmentGroupUpdateRequest,
current_user: User = Depends(require_permission("equipment:write")),
session: AsyncSession = Depends(get_async_session),
):
"""Update an equipment group."""
account_id = current_user.account_id
result = await session.execute(
select(EquipmentGroup)
.options(
joinedload(EquipmentGroup.default_location),
selectinload(EquipmentGroup.items),
)
.where(EquipmentGroup.id == group_id, EquipmentGroup.account_id == account_id)
)
group = result.unique().scalars().first()
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Equipment group not found"
)
update_data = body.model_dump(exclude_unset=True, exclude={"items"})
for field, value in update_data.items():
setattr(group, field, value)
# Handle items replacement
if body.items is not None:
equipment_ids = [i.equipment_id for i in body.items]
eq_result = await session.execute(
select(Equipment).where(
Equipment.id.in_(equipment_ids),
Equipment.account_id == account_id,
)
)
group.items = eq_result.scalars().all()
# Update quantities
for item_schema in body.items:
await session.execute(
equipment_group_items.update()
.where(
equipment_group_items.c.group_id == group.id,
equipment_group_items.c.equipment_id == item_schema.equipment_id,
)
.values(quantity=item_schema.quantity)
)
await session.commit()
await session.refresh(group)
return EquipmentGroupResponse.model_validate(group)
@router.delete("/{group_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_equipment_group(
group_id: str,
current_user: User = Depends(require_permission("equipment:delete")),
session: AsyncSession = Depends(get_async_session),
):
"""Delete an equipment group."""
account_id = current_user.account_id
result = await session.execute(
select(EquipmentGroup).where(
EquipmentGroup.id == group_id,
EquipmentGroup.account_id == account_id,
)
)
group = result.scalars().first()
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Equipment group not found"
)
await session.delete(group)
await session.commit()
return None