This commit is contained in:
Urtzi Alfaro
2026-01-01 19:01:33 +01:00
parent d6728b4bd8
commit 93c9475239
10 changed files with 286 additions and 84 deletions

View File

@@ -16,7 +16,9 @@ from app.schemas.inventory import (
StockUpdate,
StockResponse,
StockMovementCreate,
StockMovementResponse
StockMovementResponse,
BulkStockCreate,
BulkStockResponse
)
from shared.auth.decorators import get_current_user_dep
from shared.auth.access_control import require_user_role, admin_role_required
@@ -73,6 +75,37 @@ async def add_stock(
)
@router.post(
route_builder.build_base_route("stock/bulk"),
response_model=BulkStockResponse,
status_code=status.HTTP_201_CREATED
)
@require_user_role(['admin', 'owner', 'member'])
async def bulk_add_stock(
bulk_data: BulkStockCreate,
tenant_id: UUID = Path(..., description="Tenant ID"),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""Bulk add stock entries for efficient batch operations"""
try:
user_id = get_current_user_id(current_user)
service = InventoryService()
result = await service.bulk_add_stock(bulk_data, tenant_id, user_id)
return result
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
except Exception as e:
logger.error("Failed to bulk add stock", error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to bulk add stock"
)
@router.get(
route_builder.build_base_route("stock"),
response_model=List[StockResponse]

View File

@@ -366,6 +366,30 @@ class StockResponse(InventoryBaseSchema):
ingredient: Optional[IngredientResponse] = None
# ===== BULK STOCK SCHEMAS =====
class BulkStockCreate(InventoryBaseSchema):
"""Schema for bulk creating stock entries"""
stocks: List[StockCreate] = Field(..., description="List of stock entries to create")
class BulkStockResult(InventoryBaseSchema):
"""Schema for individual result in bulk stock operation"""
index: int = Field(..., description="Index of the stock in the original request")
success: bool = Field(..., description="Whether the creation succeeded")
stock: Optional[StockResponse] = Field(None, description="Created stock (if successful)")
error: Optional[str] = Field(None, description="Error message (if failed)")
class BulkStockResponse(InventoryBaseSchema):
"""Schema for bulk stock creation response"""
total_requested: int = Field(..., description="Total number of stock entries requested")
total_created: int = Field(..., description="Number of stock entries successfully created")
total_failed: int = Field(..., description="Number of stock entries that failed")
results: List[BulkStockResult] = Field(..., description="Detailed results for each stock entry")
transaction_id: str = Field(..., description="Transaction ID for audit trail")
# ===== STOCK MOVEMENT SCHEMAS =====
class StockMovementCreate(InventoryBaseSchema):

View File

@@ -342,11 +342,67 @@ class InventoryService:
logger.info("Stock added successfully", stock_id=stock.id, quantity=stock.current_quantity)
return response
except Exception as e:
logger.error("Failed to add stock", error=str(e), tenant_id=tenant_id)
raise
async def bulk_add_stock(
self,
bulk_data: 'BulkStockCreate',
tenant_id: UUID,
user_id: Optional[UUID] = None
) -> 'BulkStockResponse':
"""Bulk add stock entries for efficient batch operations"""
import uuid as uuid_lib
from app.schemas.inventory import BulkStockCreate, BulkStockResult, BulkStockResponse
transaction_id = str(uuid_lib.uuid4())
results = []
total_created = 0
total_failed = 0
for index, stock_data in enumerate(bulk_data.stocks):
try:
stock_response = await self.add_stock(stock_data, tenant_id, user_id)
results.append(BulkStockResult(
index=index,
success=True,
stock=stock_response,
error=None
))
total_created += 1
except Exception as e:
logger.warning(
"Failed to create stock in bulk operation",
index=index,
ingredient_id=stock_data.ingredient_id,
error=str(e)
)
results.append(BulkStockResult(
index=index,
success=False,
stock=None,
error=str(e)
))
total_failed += 1
logger.info(
"Bulk stock operation completed",
transaction_id=transaction_id,
total_requested=len(bulk_data.stocks),
total_created=total_created,
total_failed=total_failed
)
return BulkStockResponse(
total_requested=len(bulk_data.stocks),
total_created=total_created,
total_failed=total_failed,
results=results,
transaction_id=transaction_id
)
async def consume_stock(
self,
ingredient_id: UUID,