129 lines
4.3 KiB
Python
129 lines
4.3 KiB
Python
"""Orders router.
|
|
|
|
POST /api/orders (body: {customer_email, items:[{product_id,qty}]})
|
|
-> creates order, routes to highest-scoring supplier, records profit
|
|
GET /api/orders
|
|
GET /api/orders/{id}
|
|
POST /api/orders/{id}/tracking (simulate supplier tracking push)
|
|
"""
|
|
import uuid
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
from typing import List, Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.database import get_db
|
|
from app.engines.payments import btcpay
|
|
from app.routers.auth import require_admin
|
|
from app.engines.order_router import route_order
|
|
from app.models import AuditLog, Customer, Order, Product
|
|
from app.schemas import CheckoutResponse, CustomerOrderOut, OrderCreate, OrderOut, TrackingIn
|
|
|
|
router = APIRouter()
|
|
|
|
TRACKING_STATUSES = {"new", "paid", "fraud_check", "supplier_order", "confirmed"}
|
|
|
|
|
|
def _get_order_or_404(db, order_id):
|
|
order = db.query(Order).filter(Order.id == order_id).first()
|
|
if order is None:
|
|
raise HTTPException(status_code=404, detail="order not found")
|
|
return order
|
|
|
|
|
|
def _new_order_number() -> str:
|
|
return f"POL-{datetime.utcnow():%Y%m%d}-{uuid.uuid4().hex[:6].upper()}"
|
|
|
|
|
|
@router.post("", response_model=CheckoutResponse)
|
|
def create_order(body: OrderCreate, db: Session = Depends(get_db)):
|
|
email = body.customer_email.strip().lower()
|
|
customer = db.query(Customer).filter(Customer.email == email).first()
|
|
if customer is None:
|
|
customer = Customer(email=email, name=email.split("@")[0])
|
|
db.add(customer)
|
|
db.flush()
|
|
|
|
items = []
|
|
retail_total = Decimal("0")
|
|
for item in body.items:
|
|
product = db.query(Product).filter(Product.id == item.product_id).first()
|
|
if product is None:
|
|
raise HTTPException(status_code=404, detail=f"product {item.product_id} not found")
|
|
price = product.retail_price
|
|
if price is None or Decimal(str(price)) <= 0:
|
|
raise HTTPException(status_code=400, detail=f"product {product.sku} has no retail price")
|
|
items.append(
|
|
{
|
|
"product_id": str(product.id),
|
|
"sku": product.sku,
|
|
"qty": item.qty,
|
|
"unit_price": float(price),
|
|
}
|
|
)
|
|
retail_total += Decimal(str(price)) * item.qty
|
|
|
|
order = Order(
|
|
order_number=_new_order_number(),
|
|
customer_id=customer.id,
|
|
items=items,
|
|
retail_total=retail_total,
|
|
status="pending_payment",
|
|
)
|
|
db.add(order)
|
|
db.flush()
|
|
|
|
result = route_order(db, order)
|
|
if not result.get("routed"):
|
|
raise HTTPException(status_code=400, detail="no eligible supplier for this order")
|
|
|
|
checkout_url = None
|
|
try:
|
|
inv = btcpay.create_invoice(float(order.retail_total), order.order_number)
|
|
order.btcpay_invoice_id = inv.get("invoice_id")
|
|
checkout_url = inv.get("checkout_url")
|
|
db.commit()
|
|
except Exception as e:
|
|
db.rollback()
|
|
raise HTTPException(status_code=502, detail=f"payment provider unavailable: {e}")
|
|
|
|
db.refresh(order)
|
|
return CheckoutResponse(order=order, checkout_url=checkout_url)
|
|
|
|
|
|
@router.get("", response_model=List[OrderOut])
|
|
def list_orders(status: Optional[str] = None, db: Session = Depends(get_db), _auth: dict = Depends(require_admin)):
|
|
q = db.query(Order)
|
|
if status:
|
|
q = q.filter(Order.status == status)
|
|
return q.order_by(Order.created_at.desc()).all()
|
|
|
|
|
|
@router.get("/{order_id}", response_model=CustomerOrderOut)
|
|
def get_order(order_id, db: Session = Depends(get_db)):
|
|
return _get_order_or_404(db, order_id)
|
|
|
|
|
|
@router.post("/{order_id}/tracking", response_model=OrderOut)
|
|
def add_tracking(order_id, body: TrackingIn, db: Session = Depends(get_db), _auth: dict = Depends(require_admin)):
|
|
order = _get_order_or_404(db, order_id)
|
|
order.tracking = body.tracking
|
|
if body.carrier:
|
|
order.carrier = body.carrier
|
|
if order.status in TRACKING_STATUSES:
|
|
order.status = "shipped"
|
|
db.add(
|
|
AuditLog(
|
|
actor="api",
|
|
action="tracking_received",
|
|
entity="order",
|
|
entity_id=order.order_number,
|
|
detail={"tracking": body.tracking, "carrier": body.carrier},
|
|
)
|
|
)
|
|
db.commit()
|
|
db.refresh(order)
|
|
return order
|