- Add Russian language support (title_ru, subtitle_ru) for hero banners - Add fuel/transmission translations for Mongolian (경유→Дизель, 오토→Автомат) - Improve Vehicle Requests admin page: - Display real request ID and user email - Show detailed request info (maker, grade, year, fuel, mileage) - Replace modal search with Cars page integration - Add "Add to Request" flow in Cars page for vehicle recommendations - Fix image URL handling in FilmStripSlider and car detail page 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
415 lines
14 KiB
Python
415 lines
14 KiB
Python
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()
|
|
|
|
# User 정보 추가
|
|
result = []
|
|
for req in requests:
|
|
user = db.query(User).filter(User.id == req.user_id).first()
|
|
req_dict = {
|
|
"id": req.id,
|
|
"user_id": req.user_id,
|
|
"user_email": user.email if user else None,
|
|
"user_name": user.name if user else None,
|
|
"maker_code": req.maker_code,
|
|
"maker_name": req.maker_name,
|
|
"model_code": req.model_code,
|
|
"model_name": req.model_name,
|
|
"grade_code": req.grade_code,
|
|
"grade_name": req.grade_name,
|
|
"year_from": req.year_from,
|
|
"year_to": req.year_to,
|
|
"mileage_min": req.mileage_min,
|
|
"mileage_max": req.mileage_max,
|
|
"fuel": req.fuel,
|
|
"displacement_min": req.displacement_min,
|
|
"displacement_max": req.displacement_max,
|
|
"status": req.status,
|
|
"admin_reviewed_at": req.admin_reviewed_at,
|
|
"created_at": req.created_at,
|
|
}
|
|
result.append(req_dict)
|
|
|
|
return result
|
|
|
|
|
|
@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
|