Files
FarmaFinder/pip-platform/src/api/v1/endpoints/inventory.py
T
Antoni Nuñez Romeu c5df0ec0b2
Run Tests on Branches / Backend Tests (push) Successful in 3m32s
Run Tests on Branches / Frontend Tests (push) Successful in 3m27s
First implementation API for PIP (Proyecto: Pharmacy Integration Platform)
2026-07-01 16:46:52 +02:00

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
]