"""stock — reserve goods for an order. A test brick for erp_platform.scenarios: the neighbour that can be down."""
from contextlib import contextmanager
from datetime import datetime, timezone
from erp_platform.bricks import Brick, Refused
STOCK = Brick("stock", version="0.1.0", instructions="Reservations of stock for orders.")
@contextmanager
def _repository_default():
from erp_platform import db
from scn_stock.infrastructure.tables import StockRepository
with db.engine.begin() as conn:
yield StockRepository(conn)
repository = _repository_default
def _view(row: dict) -> dict:
return {"reservation_key": row["reservation_key"], "sku": row["sku"], "quantity": int(row["quantity"])}
@STOCK.use_case(kind="command", route="POST /reservations", permission="stock:reservation:write", idempotent=True)
def reserve(caller: dict, reservation_key: str, sku: str, quantity: int) -> dict:
"""Reserve a quantity of a SKU under the caller's key. The same key again returns the same reservation."""
if quantity < 1:
raise Refused(422, "reserve at least one")
try:
with repository() as repo:
existing = repo.get(reservation_key)
if existing is None:
now = datetime.now(timezone.utc)
repo.add({"reservation_key": reservation_key, "sku": sku, "quantity": quantity,
"reserved_by": caller.get("email") or caller["sub"], "created_at": now, "updated_at": now})
existing = {"reservation_key": reservation_key, "sku": sku, "quantity": quantity}
except Exception as exc: # a racer inserted the key first: read what it wrote
if "unique" not in str(exc).lower() and "duplicate" not in str(exc).lower():
raise
with repository() as repo:
existing = repo.get(reservation_key)
if (existing["sku"], int(existing["quantity"])) != (sku, quantity):
raise Refused(409, f"reservation {reservation_key} is for {existing['quantity']} × {existing['sku']}")
return _view(existing)
@STOCK.use_case(kind="query", route="GET /skus/{sku}/reservations", permission="stock:reservation:read")
def sku_reservations(caller: dict, sku: str) -> dict:
"""How many reservations, and how much, a SKU carries."""
with repository() as repo:
count, quantity = repo.totals(sku)
return {"sku": sku, "count": count, "quantity": quantity}