mirror of
http://88.130.71.182:3000/BlitTech/badoHair_be.git
synced 2026-06-13 08:34:29 +00:00
195 lines
6.7 KiB
Python
195 lines
6.7 KiB
Python
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,
|
|
)
|