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, Car, CarView from ..schemas import ( VehicleRequestCreate, VehicleRequestResponse, RequestVehicleCreate, RequestVehicleResponse, RequestVehicleApprove, PurchasedVehicleCreate, PurchasedVehicleResponse, PurchasedVehicleUpdateStatus, VehicleRequestWithVehicles, DirectPurchasedCarResponse, MyVehiclesResponse, ) 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 = [] # Enrich approved vehicles with latest soldout status from cars table enriched_vehicles = [] for v in approved_vehicles: vehicle_response = RequestVehicleResponse.model_validate(v) # Get latest soldout status from cars table if v.car_id: car = db.query(Car).filter(Car.id == v.car_id).first() if car: # Add soldout status to car_data vehicle_response.car_data = {**vehicle_response.car_data, "soldout": car.soldout} enriched_vehicles.append(vehicle_response) result.append(VehicleRequestWithVehicles( request=VehicleRequestResponse.model_validate(req), approved_vehicles=enriched_vehicles )) return result @router.get("/my-vehicles", response_model=MyVehiclesResponse) def get_my_vehicles( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """Get all vehicles accessible to user: recommended + directly purchased from banners""" # 1. Get vehicle requests with recommended vehicles (existing logic) requests = db.query(VehicleRequest).filter( VehicleRequest.user_id == current_user.id ).order_by(VehicleRequest.created_at.desc()).all() vehicle_requests = [] for req in requests: 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 = [] enriched_vehicles = [] for v in approved_vehicles: vehicle_response = RequestVehicleResponse.model_validate(v) if v.car_id: car = db.query(Car).filter(Car.id == v.car_id).first() if car: vehicle_response.car_data = {**vehicle_response.car_data, "soldout": car.soldout} enriched_vehicles.append(vehicle_response) vehicle_requests.append(VehicleRequestWithVehicles( request=VehicleRequestResponse.model_validate(req), approved_vehicles=enriched_vehicles )) # 2. Get directly purchased cars (from CarView - paid 1CC on banner) car_views = db.query(CarView).filter( CarView.user_id == current_user.id ).order_by(CarView.created_at.desc()).all() # Get the car_ids that are already in recommended vehicles (to avoid duplicates) recommended_car_ids = set() for vr in vehicle_requests: for v in vr.approved_vehicles: if v.car_id: recommended_car_ids.add(v.car_id) direct_purchases = [] for cv in car_views: # Skip if already in recommended (user got recommendation first, then it's not a "direct" purchase) if cv.car_id in recommended_car_ids: continue car = db.query(Car).filter(Car.id == cv.car_id).first() if car: # Build car_data similar to recommended vehicles main_image = None if car.images: main_img = next((img for img in car.images if img.is_main), None) if main_img: main_image = main_img.url elif car.images: main_image = car.images[0].url car_data = { "id": str(car.source_id) if car.source_id else str(car.id), "car_name": car.car_name, "maker_name": car.maker.name if car.maker else None, "year": car.year, "mileage": car.mileage, "final_price": car.final_price_krw, "fuel": car.fuel, "transmission": car.transmission, "color": car.color, "main_image": main_image, "soldout": car.soldout, "local_car_id": car.id, } direct_purchases.append(DirectPurchasedCarResponse( id=cv.id, car_id=cv.car_id, car_data=car_data, cc_paid=cv.cc_paid, purchased_at=cv.created_at )) return MyVehiclesResponse( vehicle_requests=vehicle_requests, direct_purchases=direct_purchases ) # ===================== # 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") # Import CarPerformanceCheck for PDF status from ..models import CarPerformanceCheck # Enrich with PDF status and soldout enriched_vehicles = [] for v in request.recommended_vehicles: vehicle_response = RequestVehicleResponse.model_validate(v) # Get PDF status and soldout from car if v.car_id: car = db.query(Car).filter(Car.id == v.car_id).first() perf_check = db.query(CarPerformanceCheck).filter(CarPerformanceCheck.car_id == v.car_id).first() vehicle_response.car_data = { **vehicle_response.car_data, "soldout": car.soldout if car else False, "has_pdf": bool(perf_check and perf_check.pdf_path), "check_num": perf_check.check_number if perf_check else None, } enriched_vehicles.append(vehicle_response) return VehicleRequestWithVehicles( request=VehicleRequestResponse.model_validate(request), approved_vehicles=enriched_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 (also imports to cars table)""" 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") # Extract car data car_data = vehicle_data.car_data source_id = str(car_data.get("id", "")) # Check if car already exists in cars table existing_car = None if source_id: existing_car = db.query(Car).filter( Car.source == "carmodoo", Car.source_id == source_id ).first() car_id = None if existing_car: car_id = existing_car.id elif source_id: # Create new car record from car_data new_car = Car( source="carmodoo", source_id=source_id, car_name=car_data.get("car_name", ""), year=car_data.get("year"), mileage=car_data.get("mileage"), price_krw=car_data.get("original_price"), fuel=car_data.get("fuel"), transmission=car_data.get("transmission"), color=car_data.get("color"), displacement=car_data.get("displacement"), margin_krw=car_data.get("korea_margin"), margin_mn=car_data.get("mongolia_margin"), check_num=car_data.get("check_num"), is_displayed=True, # Displayed so user can view recommended car status="active" ) db.add(new_car) db.flush() car_id = new_car.id # Update car_data with local car_id for frontend car_data["local_car_id"] = car_id vehicle = RequestVehicle( request_id=request_id, car_id=car_id, car_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