sync: migrate secure-online-shop to Gitea (2026-08-10)
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
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
|
||||
Reference in New Issue
Block a user