Files
secure-online-shop/app/services/order_service.py
T

105 lines
3.6 KiB
Python

from __future__ import annotations
import logging
from decimal import Decimal, ROUND_HALF_UP
from sqlalchemy import select, update
from sqlalchemy.orm import Session, selectinload
from app.core.exceptions import BadRequestError, NotFoundError
from app.models.order import Order, OrderStatusEnum
from app.models.order_item import OrderItem
from app.models.product import Product
from app.models.user import User
from app.schemas.order import OrderCreate
audit_logger = logging.getLogger("app.audit")
MONEY_QUANTIZER = Decimal("0.01")
def _to_money(value: Decimal) -> Decimal:
return value.quantize(MONEY_QUANTIZER, rounding=ROUND_HALF_UP)
class OrderService:
def __init__(self, db: Session) -> None:
self.db = db
def create_order(self, user: User, payload: OrderCreate) -> Order:
product_ids = [item.product_id for item in payload.items]
products = self.db.scalars(
select(Product).where(Product.id.in_(product_ids), Product.is_active.is_(True))
).all()
if len(products) != len(product_ids):
raise NotFoundError("One or more requested products are unavailable")
product_map = {product.id: product for product in products}
order = Order(user_id=user.id, total_amount=Decimal("0.00"), status=OrderStatusEnum.pending)
try:
self.db.add(order)
self.db.flush()
total_amount = Decimal("0.00")
for item in payload.items:
product = product_map[item.product_id]
update_result = self.db.execute(
update(Product)
.where(
Product.id == product.id,
Product.is_active.is_(True),
Product.stock >= item.quantity,
)
.values(stock=Product.stock - item.quantity)
)
if update_result.rowcount != 1:
raise BadRequestError(
f"Insufficient stock for product '{product.name}'"
)
unit_price = _to_money(Decimal(product.price))
subtotal = _to_money(unit_price * item.quantity)
order_item = OrderItem(
order_id=order.id,
product_id=product.id,
quantity=item.quantity,
unit_price=unit_price,
subtotal=subtotal,
)
self.db.add(order_item)
total_amount += subtotal
order.total_amount = _to_money(total_amount)
self.db.commit()
except Exception:
self.db.rollback()
raise
audit_logger.info(
"order_created order_id=%s user_id=%s total_amount=%s",
order.id,
user.id,
str(order.total_amount),
)
return self.get_order_for_user(order.id, user)
def get_orders_for_user(self, user: User) -> list[Order]:
statement = (
select(Order)
.where(Order.user_id == user.id)
.options(selectinload(Order.items).selectinload(OrderItem.product))
.order_by(Order.created_at.desc())
)
return list(self.db.scalars(statement).all())
def get_order_for_user(self, order_id: int, user: User) -> Order:
statement = (
select(Order)
.where(Order.id == order_id, Order.user_id == user.id)
.options(selectinload(Order.items).selectinload(OrderItem.product))
)
order = self.db.scalar(statement)
if order is None:
raise NotFoundError("Order not found")
return order