Initial commit: AutonetSellCar platform with deployment system
- Frontend: Next.js 14 with TypeScript - Backend: FastAPI with SQLAlchemy - Agent: Carmodoo sync agent - Deployment: Docker Compose based staging/production setup - Scripts: Automated deployment with rollback support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
385
backend/app/api/vehicle_requests.py
Normal file
385
backend/app/api/vehicle_requests.py
Normal file
@@ -0,0 +1,385 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import VehicleRequest, RequestVehicle, PurchasedVehicle, User, DealerInfo, SystemSettings
|
||||
from ..schemas import (
|
||||
VehicleRequestCreate, VehicleRequestResponse,
|
||||
RequestVehicleCreate, RequestVehicleResponse, RequestVehicleApprove,
|
||||
PurchasedVehicleCreate, PurchasedVehicleResponse, PurchasedVehicleUpdateStatus,
|
||||
VehicleRequestWithVehicles,
|
||||
)
|
||||
from .auth import get_current_user
|
||||
from .notification import notify_vehicle_recommended, notify_shipping_update
|
||||
|
||||
|
||||
def get_system_settings(db: Session) -> SystemSettings:
|
||||
"""Get or create system settings"""
|
||||
settings = db.query(SystemSettings).first()
|
||||
if not settings:
|
||||
settings = SystemSettings()
|
||||
db.add(settings)
|
||||
db.commit()
|
||||
db.refresh(settings)
|
||||
return settings
|
||||
|
||||
|
||||
def calculate_dealer_commission(vehicle_price_krw: int, db: Session) -> tuple:
|
||||
"""Calculate dealer and platform commission based on Mongolia margin"""
|
||||
settings = get_system_settings(db)
|
||||
|
||||
# Calculate Mongolia margin (vehicle price * margin percent)
|
||||
mongolia_margin = vehicle_price_krw * (settings.mongolia_margin_percent / 100)
|
||||
|
||||
# 50/50 split between dealer and platform
|
||||
dealer_commission = int(mongolia_margin * 0.5)
|
||||
platform_commission = int(mongolia_margin * 0.5)
|
||||
|
||||
return dealer_commission, platform_commission
|
||||
|
||||
router = APIRouter(prefix="/vehicle-requests", tags=["vehicle-requests"])
|
||||
|
||||
# Development mode - skip 24 hour wait
|
||||
DEV_MODE = True
|
||||
|
||||
|
||||
# =====================
|
||||
# User Endpoints
|
||||
# =====================
|
||||
|
||||
QUOTE_REQUEST_COST = 1.0 # 1 CC for quote request submission
|
||||
|
||||
|
||||
@router.post("/", response_model=VehicleRequestResponse)
|
||||
def create_request(
|
||||
request_data: VehicleRequestCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create a new vehicle search request (costs 1 CC)"""
|
||||
# Check if user has enough CC
|
||||
if (current_user.cc_balance or 0) < QUOTE_REQUEST_COST:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Insufficient CC balance. You need {QUOTE_REQUEST_COST} CC to submit a vehicle request. Current balance: {current_user.cc_balance or 0}"
|
||||
)
|
||||
|
||||
# Deduct CC from user's balance
|
||||
current_user.cc_balance = (current_user.cc_balance or 0) - QUOTE_REQUEST_COST
|
||||
|
||||
# Create the request
|
||||
request = VehicleRequest(
|
||||
user_id=current_user.id,
|
||||
cc_paid=QUOTE_REQUEST_COST,
|
||||
**request_data.model_dump()
|
||||
)
|
||||
db.add(request)
|
||||
db.commit()
|
||||
db.refresh(request)
|
||||
return request
|
||||
|
||||
|
||||
@router.get("/my-requests", response_model=List[VehicleRequestWithVehicles])
|
||||
def get_my_requests(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get current user's vehicle requests with approved vehicles"""
|
||||
requests = db.query(VehicleRequest).filter(
|
||||
VehicleRequest.user_id == current_user.id
|
||||
).order_by(VehicleRequest.created_at.desc()).all()
|
||||
|
||||
result = []
|
||||
for req in requests:
|
||||
# In dev mode, show all approved vehicles immediately
|
||||
# In production, only show after 24 hours
|
||||
if DEV_MODE or (req.created_at and datetime.utcnow() - req.created_at > timedelta(hours=24)):
|
||||
approved_vehicles = [v for v in req.recommended_vehicles if v.is_approved]
|
||||
else:
|
||||
approved_vehicles = []
|
||||
|
||||
result.append(VehicleRequestWithVehicles(
|
||||
request=VehicleRequestResponse.model_validate(req),
|
||||
approved_vehicles=[RequestVehicleResponse.model_validate(v) for v in approved_vehicles]
|
||||
))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# =====================
|
||||
# Purchased Vehicles (Find My Car)
|
||||
# =====================
|
||||
|
||||
@router.get("/purchased", response_model=List[PurchasedVehicleResponse])
|
||||
def get_purchased_vehicles(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get user's purchased vehicles with shipping status"""
|
||||
vehicles = db.query(PurchasedVehicle).filter(
|
||||
PurchasedVehicle.user_id == current_user.id
|
||||
).order_by(PurchasedVehicle.purchased_at.desc()).all()
|
||||
return vehicles
|
||||
|
||||
|
||||
# =====================
|
||||
# Admin Endpoints
|
||||
# =====================
|
||||
|
||||
@router.get("/admin/list", response_model=List[VehicleRequestResponse])
|
||||
def admin_get_all_requests(
|
||||
status: str = None,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin: Get all vehicle requests"""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
query = db.query(VehicleRequest)
|
||||
if status:
|
||||
query = query.filter(VehicleRequest.status == status)
|
||||
|
||||
requests = query.order_by(VehicleRequest.created_at.desc()).all()
|
||||
return requests
|
||||
|
||||
|
||||
@router.get("/admin/{request_id}", response_model=VehicleRequestWithVehicles)
|
||||
def admin_get_request_detail(
|
||||
request_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin: Get request detail with all recommended vehicles"""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
request = db.query(VehicleRequest).filter(VehicleRequest.id == request_id).first()
|
||||
if not request:
|
||||
raise HTTPException(status_code=404, detail="Request not found")
|
||||
|
||||
return VehicleRequestWithVehicles(
|
||||
request=VehicleRequestResponse.model_validate(request),
|
||||
approved_vehicles=[RequestVehicleResponse.model_validate(v) for v in request.recommended_vehicles]
|
||||
)
|
||||
|
||||
|
||||
@router.post("/admin/{request_id}/vehicles", response_model=RequestVehicleResponse)
|
||||
def admin_add_vehicle(
|
||||
request_id: int,
|
||||
vehicle_data: RequestVehicleCreate,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin: Add a vehicle to a request"""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
request = db.query(VehicleRequest).filter(VehicleRequest.id == request_id).first()
|
||||
if not request:
|
||||
raise HTTPException(status_code=404, detail="Request not found")
|
||||
|
||||
vehicle = RequestVehicle(
|
||||
request_id=request_id,
|
||||
car_data=vehicle_data.car_data,
|
||||
is_approved=vehicle_data.is_approved,
|
||||
approved_at=datetime.utcnow() if vehicle_data.is_approved else None
|
||||
)
|
||||
db.add(vehicle)
|
||||
|
||||
# Update request status
|
||||
request.status = "reviewed"
|
||||
request.admin_reviewed_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
db.refresh(vehicle)
|
||||
return vehicle
|
||||
|
||||
|
||||
@router.post("/admin/{request_id}/approve-vehicles")
|
||||
def admin_approve_vehicles(
|
||||
request_id: int,
|
||||
approval: RequestVehicleApprove,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin: Approve multiple vehicles for a request"""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
vehicles = db.query(RequestVehicle).filter(
|
||||
RequestVehicle.request_id == request_id,
|
||||
RequestVehicle.id.in_(approval.vehicle_ids)
|
||||
).all()
|
||||
|
||||
for vehicle in vehicles:
|
||||
vehicle.is_approved = True
|
||||
vehicle.approved_at = datetime.utcnow()
|
||||
|
||||
# Update request status
|
||||
request = db.query(VehicleRequest).filter(VehicleRequest.id == request_id).first()
|
||||
if request:
|
||||
request.status = "completed"
|
||||
request.admin_reviewed_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
|
||||
# Send notification to user
|
||||
if request and len(vehicles) > 0:
|
||||
notify_vehicle_recommended(db, request.user_id, request_id, len(vehicles))
|
||||
|
||||
return {"message": f"Approved {len(vehicles)} vehicles"}
|
||||
|
||||
|
||||
@router.put("/admin/{request_id}/status")
|
||||
def admin_update_request_status(
|
||||
request_id: int,
|
||||
new_status: str,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin: Update request status"""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
request = db.query(VehicleRequest).filter(VehicleRequest.id == request_id).first()
|
||||
if not request:
|
||||
raise HTTPException(status_code=404, detail="Request not found")
|
||||
|
||||
request.status = new_status
|
||||
request.admin_reviewed_at = datetime.utcnow()
|
||||
db.commit()
|
||||
|
||||
return {"message": "Status updated"}
|
||||
|
||||
|
||||
@router.delete("/admin/{request_id}/vehicles/{vehicle_id}")
|
||||
def admin_delete_vehicle(
|
||||
request_id: int,
|
||||
vehicle_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin: Delete a recommended vehicle from a request"""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
vehicle = db.query(RequestVehicle).filter(
|
||||
RequestVehicle.id == vehicle_id,
|
||||
RequestVehicle.request_id == request_id
|
||||
).first()
|
||||
|
||||
if not vehicle:
|
||||
raise HTTPException(status_code=404, detail="Vehicle not found")
|
||||
|
||||
db.delete(vehicle)
|
||||
db.commit()
|
||||
|
||||
return {"message": "Vehicle deleted successfully"}
|
||||
|
||||
|
||||
# =====================
|
||||
# Admin: Purchased Vehicles Management
|
||||
# =====================
|
||||
|
||||
@router.post("/admin/purchased", response_model=PurchasedVehicleResponse)
|
||||
def admin_create_purchased(
|
||||
vehicle_data: PurchasedVehicleCreate,
|
||||
user_id: int,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin: Create a purchased vehicle record"""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
# Calculate dealer commission if dealer is selected
|
||||
dealer_commission_krw = 0
|
||||
platform_commission_krw = 0
|
||||
|
||||
if vehicle_data.selected_dealer_id:
|
||||
# Verify dealer exists and is active
|
||||
dealer_info = db.query(DealerInfo).filter(
|
||||
DealerInfo.id == vehicle_data.selected_dealer_id,
|
||||
DealerInfo.is_active == True
|
||||
).first()
|
||||
|
||||
if not dealer_info:
|
||||
raise HTTPException(status_code=400, detail="Selected dealer not found or inactive")
|
||||
|
||||
# Calculate commissions
|
||||
dealer_commission_krw, platform_commission_krw = calculate_dealer_commission(
|
||||
vehicle_data.vehicle_price_krw, db
|
||||
)
|
||||
|
||||
# Credit commission to dealer's account
|
||||
dealer_info.total_commission_earned += dealer_commission_krw
|
||||
|
||||
vehicle = PurchasedVehicle(
|
||||
user_id=user_id,
|
||||
car_name=vehicle_data.car_name,
|
||||
car_data=vehicle_data.car_data,
|
||||
car_image=vehicle_data.car_image,
|
||||
vehicle_price_krw=vehicle_data.vehicle_price_krw,
|
||||
domestic_cost_krw=vehicle_data.domestic_cost_krw,
|
||||
shipping_cost_usd=vehicle_data.shipping_cost_usd,
|
||||
total_cost_krw=vehicle_data.total_cost_krw,
|
||||
car_type=vehicle_data.car_type,
|
||||
selected_dealer_id=vehicle_data.selected_dealer_id,
|
||||
dealer_commission_krw=dealer_commission_krw,
|
||||
platform_commission_krw=platform_commission_krw,
|
||||
)
|
||||
db.add(vehicle)
|
||||
db.commit()
|
||||
db.refresh(vehicle)
|
||||
return vehicle
|
||||
|
||||
|
||||
@router.get("/admin/purchased/all", response_model=List[PurchasedVehicleResponse])
|
||||
def admin_get_all_purchased(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin: Get all purchased vehicles"""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
vehicles = db.query(PurchasedVehicle).order_by(PurchasedVehicle.purchased_at.desc()).all()
|
||||
return vehicles
|
||||
|
||||
|
||||
@router.put("/admin/purchased/{vehicle_id}/status", response_model=PurchasedVehicleResponse)
|
||||
def admin_update_shipping_status(
|
||||
vehicle_id: int,
|
||||
status_update: PurchasedVehicleUpdateStatus,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin: Update shipping status of a purchased vehicle"""
|
||||
if not current_user.is_admin:
|
||||
raise HTTPException(status_code=403, detail="Admin access required")
|
||||
|
||||
vehicle = db.query(PurchasedVehicle).filter(PurchasedVehicle.id == vehicle_id).first()
|
||||
if not vehicle:
|
||||
raise HTTPException(status_code=404, detail="Vehicle not found")
|
||||
|
||||
vehicle.shipping_status = status_update.shipping_status
|
||||
vehicle.status_updated_at = datetime.utcnow()
|
||||
|
||||
if status_update.current_location:
|
||||
vehicle.current_location = status_update.current_location
|
||||
if status_update.estimated_arrival:
|
||||
vehicle.estimated_arrival = status_update.estimated_arrival
|
||||
|
||||
if status_update.shipping_status == 7: # Delivered (배송완료)
|
||||
vehicle.delivered_at = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
db.refresh(vehicle)
|
||||
|
||||
# Send notification to user about shipping update
|
||||
notify_shipping_update(db, vehicle.user_id, vehicle.id, status_update.shipping_status, vehicle.car_name)
|
||||
|
||||
return vehicle
|
||||
Reference in New Issue
Block a user