Source code for app.orders
from itertools import count
from app.pydantic_models import Order, CartItem
from app.state_machine import StateMachine
from app.cart import CartManager
from app.inventory import InventoryManager
from app.config import discount_strategy, shipping_strategy
# In-memory storage for orders: order_id -> (Order, StateMachine)
_ORDERS: dict[int, tuple[Order, StateMachine]] = {}
_order_seq = count(start=1)
def _calculate_order_breakdown(items: list[CartItem]) -> dict[str, float]:
"""
Calculate pricing breakdown for a list of order items.
Args:
items: List of CartItem objects
Returns:
dict: Breakdown with subtotal, discount, shipping, and total
"""
# Calculate subtotal (price before discounts and shipping)
subtotal = 0.0
for it in items:
p = InventoryManager.get_instance().get_product(it.product_id)
assert p is not None
subtotal += p.price * it.quantity
# Prepare items list for shipping calculation
items_for_shipping = [(it.product_id, it.quantity) for it in items]
# Apply discount and shipping strategies
discount = discount_strategy.calculate_discount(subtotal)
shipping = shipping_strategy.calculate_shipping(subtotal, items_for_shipping)
# Calculate final total
total = subtotal - discount + shipping
return {
"subtotal": round(subtotal, 2),
"discount": round(discount, 2),
"shipping": round(shipping, 2),
"total": round(total, 2)
}
[docs]
def create_order(cart_id: str = "default") -> Order:
"""
Create an order from a shopping cart.
Validates stock availability for every item in the cart, decrements
the inventory, computes the total price, and clears the cart.
Args:
cart_id: The identifier of the cart to order from. Defaults to "default".
Returns:
Order: The newly created order.
Raises:
ValueError: If any product in the cart has insufficient stock.
"""
cart = CartManager.get_instance().get_cart(cart_id)
items: list[CartItem] = []
for pid, qty in cart.items.items():
if not InventoryManager.get_instance().has_stock(pid, qty):
raise ValueError(f"Insufficient stock for product {pid}")
items.append(CartItem(product_id=pid, quantity=qty))
for it in items:
InventoryManager.get_instance().decrement_stock(it.product_id, it.quantity)
# Calculate subtotal (price before discounts and shipping)
subtotal = 0.0
for it in items:
p = InventoryManager.get_instance().get_product(it.product_id)
assert p is not None
subtotal += p.price * it.quantity
# Prepare items list for shipping calculation
items_for_shipping = [(it.product_id, it.quantity) for it in items]
# Apply discount and shipping strategies
discount = discount_strategy.calculate_discount(subtotal)
shipping = shipping_strategy.calculate_shipping(subtotal, items_for_shipping)
# Calculate final total
total = subtotal - discount + shipping
oid = next(_order_seq)
order = Order(id=oid, cart_id=cart_id, items=items, total=round(total, 2), status="created")
_ORDERS[oid] = (order, StateMachine())
cart.clear()
return order
[docs]
def cancel_order(order_id: int):
"""
Cancels the order `order_id`.
It restocks the ordered items into the inventory.
Args:
order_id: The ID of the order
Raises:
ValueError: If the order with the specified ID is not found in the system.
"""
order = get_order(order_id)
inventory_manager = InventoryManager.get_instance()
for item in order.items:
inventory_manager.increment_stock(item.product_id, item.quantity)
[docs]
def get_order_breakdown(order_id: int) -> dict[str, float]:
"""
Calculate the pricing breakdown for an order.
Args:
order_id: The ID of the order
Returns:
dict: Breakdown with subtotal, discount, shipping, and total
"""
order = get_order(order_id)
return _calculate_order_breakdown(order.items)
[docs]
def get_order(order_id: int) -> Order:
"""
Retrieves the order by its ID. Raises ValueError if the order is not found.
Args:
order_id: The unique identifier of the order to retrieve.
Returns:
The Order object corresponding to the given order_id.
Raises:
ValueError: If the order with the specified ID is not found in the system.
"""
if order_id not in _ORDERS:
raise ValueError("Order not found")
return _ORDERS[order_id][0]
[docs]
def get_machine(order_id: int) -> StateMachine:
"""
Retrieves the state machine for an order by its ID.
Raises ValueError if the order is not found.
Args:
order_id: The unique identifier of the state machine to retrieve.
Returns:
The StateMachine object corresponding to the given order_id.
Raises:
ValueError: If the state machine for the specified order ID is not found in the system.
"""
if order_id not in _ORDERS:
raise ValueError("Order not found")
return _ORDERS[order_id][1]
[docs]
def set_status(order_id: int, new_status: str) -> Order:
"""
Updates the status of an order and triggers the state machine transition.
Raises ValueError if the order is not found or if the status transition is invalid.
If the new state is 'cancelled', then it calls a function to restock items.
Args:
order_id: The unique identifier of the order to update.
new_status: The new status to set for the order.
Returns:
The updated Order object with the new status.
Raises:
ValueError: If the order with the specified ID is not found or if the status
transition is invalid according to the state machine rules.
"""
order = get_order(order_id)
machine = get_machine(order_id)
machine.update(new_status)
order.status = new_status
if new_status == "cancelled":
cancel_order(order_id)
return order