Initial Commit

This commit is contained in:
belviskhoremk
2026-05-12 00:34:21 +00:00
commit d2dc43b16f
57 changed files with 6056 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
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,
)