from uuid import UUID import asyncpg from app.exceptions import NotFoundError, OutOfStockError from app.models.orders import OrderCreate async def create_order(db: asyncpg.Connection, user_id: str, data: OrderCreate) -> dict: async with db.transaction(): total = 0.0 line_items = [] for item in data.items: product = await db.fetchrow( "SELECT id, name, price, stock_quantity FROM products WHERE id = $1 AND is_hidden = false", str(item.product_id), ) if not product: raise NotFoundError("product") if product["stock_quantity"] < item.quantity: raise OutOfStockError(product["name"]) line_items.append({ "product_id": str(item.product_id), "product_name": product["name"], "quantity": item.quantity, "unit_price": float(product["price"]), }) total += float(product["price"]) * item.quantity await db.execute( "UPDATE products SET stock_quantity = stock_quantity - $2 WHERE id = $1", str(item.product_id), item.quantity, ) total = round(total, 2) order = await db.fetchrow( """ INSERT INTO orders (user_id, status, total_amount, shipping_address, notes) VALUES ($1, 'pending', $2, $3, $4) RETURNING * """, user_id, total, data.shipping_address, data.notes, ) order_id = str(order["id"]) for li in line_items: await db.execute( """ INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES ($1, $2, $3, $4) """, order_id, li["product_id"], li["quantity"], li["unit_price"], ) return {"order_id": order_id, "amount": total} async def get_order(db: asyncpg.Connection, order_id: UUID, user_id: str | None = None) -> dict: query = "SELECT o.* FROM orders o WHERE o.id = $1" params: list = [str(order_id)] if user_id: query += " AND o.user_id = $2" params.append(user_id) row = await db.fetchrow(query, *params) if not row: raise NotFoundError("order") items = await db.fetch( """ SELECT oi.id, oi.product_id, p.name as product_name, oi.quantity, oi.unit_price FROM order_items oi JOIN products p ON p.id = oi.product_id WHERE oi.order_id = $1 """, str(order_id), ) result = dict(row) result["items"] = [dict(i) for i in items] return result async def list_orders( db: asyncpg.Connection, page: int, per_page: int, offset: int, user_id: str | None = None, status: str | None = None, ) -> tuple[list[dict], int]: conditions: list[str] = [] params: list = [] if user_id: params.append(user_id) conditions.append(f"o.user_id = ${len(params)}") if status: params.append(status) conditions.append(f"o.status = ${len(params)}") where = f"WHERE {' AND '.join(conditions)}" if conditions else "" total = await db.fetchval(f"SELECT COUNT(*) FROM orders o {where}", *params) params.extend([per_page, offset]) rows = await db.fetch( f""" SELECT o.id, o.user_id, o.status, o.total_amount, o.shipping_address, o.notes, o.created_at, o.updated_at, p.full_name AS client_name, p.email AS client_email, p.phone AS client_phone FROM orders o LEFT JOIN profiles p ON p.id = o.user_id {where} ORDER BY o.created_at DESC LIMIT ${len(params) - 1} OFFSET ${len(params)} """, *params, ) return [dict(r) for r in rows], total async def update_order_status(db: asyncpg.Connection, order_id: UUID, status: str, actor_id: str) -> dict: row = await db.fetchrow( "UPDATE orders SET status = $2, updated_at = now() WHERE id = $1 RETURNING *", str(order_id), status, ) if not row: raise NotFoundError("order") await _log(db, actor_id, "order.status_updated", "order", str(order_id), {"status": status}) return dict(row) async def refund_order(db: asyncpg.Connection, order_id: UUID, actor_id: str, amount: float | None, reason: str | None) -> dict: order = await db.fetchrow("SELECT * FROM orders WHERE id = $1", str(order_id)) if not order: raise NotFoundError("order") if not order["stripe_payment_intent_id"]: from app.exceptions import AppError raise AppError("NO_PAYMENT", "No payment found for this order", 400) refund = await stripe_service.create_refund( order["stripe_payment_intent_id"], amount=amount, reason=reason, ) new_status = "refunded" row = await db.fetchrow( "UPDATE orders SET status = $2, stripe_refund_id = $3, updated_at = now() WHERE id = $1 RETURNING *", str(order_id), new_status, refund.id, ) await _log(db, actor_id, "order.refunded", "order", str(order_id), {"refund_id": refund.id}) return dict(row) async def handle_payment_succeeded(db: asyncpg.Connection, payment_intent_id: str, entity_id: str): row = await db.fetchrow( "UPDATE orders SET status = 'paid', updated_at = now() WHERE id = $1 AND stripe_payment_intent_id = $2 RETURNING *", entity_id, payment_intent_id, ) if row: user = await db.fetchrow("SELECT email FROM profiles WHERE id = $1", str(row["user_id"])) if user: from app.services.email_service import send_order_confirmed await send_order_confirmed(user["email"], entity_id, float(row["total_amount"])) async def handle_payment_failed(db: asyncpg.Connection, payment_intent_id: str, entity_id: str): order = await db.fetchrow("SELECT * FROM orders WHERE id = $1", entity_id) if not order or order["status"] != "pending": return await db.execute( "UPDATE orders SET status = 'cancelled', updated_at = now() WHERE id = $1", entity_id, ) # Restore stock items = await db.fetch("SELECT product_id, quantity FROM order_items WHERE order_id = $1", entity_id) for item in items: await db.execute( "UPDATE products SET stock_quantity = stock_quantity + $2 WHERE id = $1", str(item["product_id"]), item["quantity"], ) async def _log(db, actor_id, action, entity_type, entity_id, metadata=None): await db.execute( "INSERT INTO activity_log (actor_id, action, entity_type, entity_id, metadata) VALUES ($1, $2, $3, $4, $5)", actor_id, action, entity_type, entity_id, metadata, )