from datetime import datetime from typing import Any from uuid import UUID from fastapi import APIRouter, Depends, Query from pydantic import BaseModel, Field from sqlalchemy.ext.asyncio import AsyncSession from src.api.v1.dependencies.auth import get_current_user, require_role from src.core.exceptions import NotFoundException from src.domain.entities import InventoryRecordEntity, UserRole from src.infrastructure.database.repositories import InventoryRepository, ReservationRepository from src.infrastructure.database.session import get_db_session from src.services.auth.security import TokenData from src.services.inventory.inventory_service import InventoryService router = APIRouter(prefix="/pharmacies/{pharmacy_id}/inventory", tags=["Inventory"]) class InventoryUpsertRequest(BaseModel): medication_id: UUID stock: int = Field(..., ge=0) reserved_stock: int = Field(0, ge=0) available_stock: int = Field(0, ge=0) min_stock: int | None = Field(None, ge=0) max_stock: int | None = Field(None, ge=0) last_updated_at: datetime | None = None source: str | None = Field(None, max_length=100) batch_id: str | None = Field(None, max_length=100) metadata: dict[str, Any] | None = None class InventoryRecordResponse(BaseModel): id: UUID pharmacy_id: UUID medication_id: UUID stock: int reserved_stock: int available_stock: int min_stock: int | None max_stock: int | None last_updated_at: datetime | None source: str | None batch_id: str | None metadata: dict[str, Any] | None created_at: datetime | None updated_at: datetime | None class InventoryListResponse(BaseModel): items: list[InventoryRecordResponse] total: int offset: int limit: int class InventoryHistoryResponse(BaseModel): id: UUID pharmacy_id: UUID medication_id: UUID previous_stock: int new_stock: int delta: int reason: str | None source: str | None created_at: datetime | None def _record_entity_to_response(e: InventoryRecordEntity) -> InventoryRecordResponse: return InventoryRecordResponse( id=e.id, pharmacy_id=e.pharmacy_id, medication_id=e.medication_id, stock=e.stock, reserved_stock=e.reserved_stock, available_stock=e.available_stock, min_stock=e.min_stock, max_stock=e.max_stock, last_updated_at=e.last_updated_at, source=e.source, batch_id=e.batch_id, metadata=e.metadata_, created_at=e.created_at, updated_at=e.updated_at, ) def _get_service(session: AsyncSession) -> InventoryService: return InventoryService( inventory_repo=InventoryRepository(session), reservation_repo=ReservationRepository(session), ) @router.get("", response_model=InventoryListResponse) async def list_inventory( pharmacy_id: UUID, offset: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=200), session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(get_current_user), ): service = _get_service(session) entities, total = await service.list_inventory(pharmacy_id, offset=offset, limit=limit) return InventoryListResponse(items=[_record_entity_to_response(e) for e in entities], total=total, offset=offset, limit=limit) @router.get("/{medication_id}", response_model=InventoryRecordResponse) async def get_inventory_record( pharmacy_id: UUID, medication_id: UUID, session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(get_current_user), ): service = _get_service(session) entity = await service.get_inventory_record(pharmacy_id, medication_id) if not entity: raise NotFoundException("InventoryRecord", f"{pharmacy_id}/{medication_id}") return _record_entity_to_response(entity) @router.put("", response_model=InventoryRecordResponse) async def upsert_inventory_record( pharmacy_id: UUID, body: InventoryUpsertRequest, session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(require_role(UserRole.OPERATOR)), ): service = _get_service(session) entity = InventoryRecordEntity( pharmacy_id=pharmacy_id, medication_id=body.medication_id, stock=body.stock, reserved_stock=body.reserved_stock, available_stock=body.available_stock, min_stock=body.min_stock, max_stock=body.max_stock, last_updated_at=body.last_updated_at, source=body.source, batch_id=body.batch_id, metadata_=body.metadata, ) upserted = await service.upsert_inventory_record(entity) return _record_entity_to_response(upserted) @router.get("/{medication_id}/history", response_model=list[InventoryHistoryResponse]) async def get_inventory_history( pharmacy_id: UUID, medication_id: UUID, offset: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=200), session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(get_current_user), ): service = _get_service(session) history = await service.get_inventory_history(pharmacy_id, medication_id, offset=offset, limit=limit) return [ InventoryHistoryResponse( id=h.id, pharmacy_id=h.pharmacy_id, medication_id=h.medication_id, previous_stock=h.previous_stock, new_stock=h.new_stock, delta=h.delta, reason=h.reason, source=h.source, created_at=h.created_at, ) for h in history ]