ifixkart-backend/Backend/app/api/v1/routers/OrderRouter.py

188 lines
5.7 KiB
Python

"""
@router OrderRouter (Backend/app/api/v1/routers/OrderRouter.py)
@purpose Database-driven order history and detail tracking for storefront customers, protected by JWT authentication.
"""
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel
from typing import List, Optional
from sqlalchemy.orm import Session
from datetime import datetime
import ulid
from app.core.database.db_session import get_db
from app.api.v1.routers.CustomerProfileRouter import get_current_customer
from app.models.EcomCustomerModel import EcomCustomer
from app.models.OrderModel import Order, OrderItem, OrderStatusHistory
from app.services.InventoryService import record_ledger_entry
router = APIRouter(prefix="/api/v1/orders", tags=["Order Management"])
# --- Schemas ---
class OrderItemSchema(BaseModel):
product_name: str
sku: str
quantity: int
unit_price: float
total_price: float
class OrderDetailResponse(BaseModel):
order_id: str
order_no: str
total_amount: float
discount_amount: float
tax_amount: float
shipping_cost: float
final_amount: float
status: str
payment_status: str
fulfillment_status: str
shipping_address_json: Optional[str] = None
billing_address_json: Optional[str] = None
tracking_number: Optional[str] = None
courier_name: Optional[str] = None
created_at: datetime
items: List[OrderItemSchema]
class OrderListResponse(BaseModel):
order_id: str
order_no: str
final_amount: float
status: str
payment_status: str
created_at: datetime
# --- Endpoints ---
@router.get("", response_model=List[OrderListResponse])
def list_customer_orders(
customer: EcomCustomer = Depends(get_current_customer),
db: Session = Depends(get_db)
):
"""
List order history for the logged-in customer.
"""
orders = (
db.query(Order)
.filter(Order.customer_id == customer.customer_id)
.order_by(Order.created_at.desc())
.all()
)
return [
OrderListResponse(
order_id=o.order_id,
order_no=o.order_no,
final_amount=float(o.final_amount),
status=o.status,
payment_status=o.payment_status,
created_at=o.created_at
) for o in orders
]
@router.get("/{order_id}", response_model=OrderDetailResponse)
def get_order_details(
order_id: str,
customer: EcomCustomer = Depends(get_current_customer),
db: Session = Depends(get_db)
):
"""
Retrieve full details for a specific order. Enforces customer ownership.
"""
order = (
db.query(Order)
.filter(Order.order_id == order_id, Order.customer_id == customer.customer_id)
.first()
)
if not order:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Order not found"
)
return OrderDetailResponse(
order_id=order.order_id,
order_no=order.order_no,
total_amount=float(order.total_amount),
discount_amount=float(order.discount_amount),
tax_amount=float(order.tax_amount),
shipping_cost=float(order.shipping_cost),
final_amount=float(order.final_amount),
status=order.status,
payment_status=order.payment_status,
fulfillment_status=order.fulfillment_status,
shipping_address_json=order.shipping_address_json,
billing_address_json=order.billing_address_json,
tracking_number=order.tracking_number,
courier_name=order.courier_name,
created_at=order.created_at,
items=[
OrderItemSchema(
product_name=item.product_name,
sku=item.sku,
quantity=item.quantity,
unit_price=float(item.unit_price),
total_price=float(item.total_price)
) for item in order.items
]
)
@router.post("/{order_id}/cancel")
def cancel_order(
order_id: str,
customer: EcomCustomer = Depends(get_current_customer),
db: Session = Depends(get_db)
):
"""
Cancel an order if it is in an editable state (ORDER_CREATED or PAYMENT_PENDING).
Releases ONLINE_RESERVE inventory reservations.
"""
order = (
db.query(Order)
.filter(Order.order_id == order_id, Order.customer_id == customer.customer_id)
.first()
)
if not order:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Order not found"
)
if order.status not in ["ORDER_CREATED", "PAYMENT_PENDING"]:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Order cannot be cancelled. Current status is {order.status}."
)
previous_status = order.status
for item in order.items:
record_ledger_entry(
variant_id=item.variant_id,
event_type="ONLINE_RESERVE_RELEASE",
qty=item.quantity,
reference_id=order.order_id,
db=db,
notes=f"Released reservation for customer-cancelled order {order.order_no}",
commit=False,
)
history = OrderStatusHistory(
history_id=str(ulid.ULID()),
order_id=order.order_id,
previous_status=previous_status,
new_status="CANCELLED",
changed_by=customer.email,
reason="Cancelled by customer from profile portal"
)
db.add(history)
order.status = "CANCELLED"
order.fulfillment_status = "CANCELLED"
if order.payment_status == "PAYMENT_CAPTURED":
order.payment_status = "REFUNDED"
else:
order.payment_status = "PAYMENT_FAILED"
db.commit()
return {"message": "Order cancelled successfully", "order_id": order_id}