from datetime import datetime from decimal import Decimal from pydantic import BaseModel, Field, ConfigDict from typing import Optional class ProductCreate(BaseModel): name: str = Field(..., min_length=1, max_length=255) sku: str = Field( ..., min_length=1, max_length=100, description="Stock-keeping unit — must be unique", ) quantity_in_stock: int = Field( ..., ge=0, description="Initial stock level, cannot be negative" ) class ProductUpdate(BaseModel): name: Optional[str] = Field(None, min_length=1, max_length=255) quantity_in_stock: Optional[int] = Field(None, ge=0) class ProductOut(BaseModel): id: int name: str sku: str quantity_in_stock: int created_at: datetime updated_at: datetime model_config = ConfigDict(from_attributes=True) class StockMovementOut(BaseModel): id: int product_id: int order_id: int movement_type: str quantity_change: int created_at: datetime model_config = ConfigDict(from_attributes=True) class ReservedItem(BaseModel): product_id: int quantity: int class FailedItem(BaseModel): product_id: int quantity: int class StockReservedEvent(BaseModel): """ Emitted on the 'inventory' topic when stock is successfully reserved. Example payload: { "order_id": 123, "status": "SUCCESS", "reserved_items": [ {"product_id": 101, "quantity": 2}, {"product_id": 102, "quantity": 1} ] } """ order_id: int status: str = "SUCCESS" reserved_items: list[ReservedItem] class StockReservationFailedEvent(BaseModel): """ Emitted on the 'inventory' topic when stock cannot be reserved. Example payload: { "order_id": 123, "status": "FAILED", "reason": "INSUFFICIENT_STOCK", "failed_items": [{"product_id": 102, "quantity": 5}] } """ order_id: int status: str = "FAILED" reason: str = "INSUFFICIENT_STOCK" failed_items: list[FailedItem]