151 lines
5.3 KiB
Python
151 lines
5.3 KiB
Python
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
|
|
]
|