""" @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}