dodil/erp-platform

tests/scenario_bricks/scn_stock/application/use_cases.py

"""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}