Files
AutonetSellCar/backend/app/services/soldout_service.py
AutonetSellCar Deploy c9fd7611a7 feat: Add banner toggle and soldout tracking to Cars page
- Add is_banner, soldout fields to Car model
- Add banner toggle API (POST /hero-banners/admin/toggle/{car_id})
- Add soldout APIs (POST/DELETE /cars/{car_id}/soldout)
- Add nightly soldout checker in agent (runs at 3:00 AM)
- Update Local Cars UI with banner checkbox and status column
- Remove hero-banners admin page (functionality moved to Cars page)
- Banner cars sorted to top with purple background
- Soldout cars displayed with gray overlay

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-31 12:50:40 +09:00

122 lines
3.9 KiB
Python

"""
Soldout Check Service - Checks if cars are still available on Carmodoo
"""
import logging
from datetime import datetime
from typing import List, Tuple
from sqlalchemy.orm import Session
import httpx
from ..models.car import Car
logger = logging.getLogger(__name__)
class SoldoutChecker:
"""Carmodoo에서 차량 판매 여부를 확인하는 서비스"""
def __init__(self, db: Session, carmodoo_session_cookie: str = None):
self.db = db
self.carmodoo_session_cookie = carmodoo_session_cookie
self.carmodoo_base_url = "https://dealer.carmodoo.com"
async def check_car_availability(self, source_id: str) -> bool:
"""
Carmodoo에서 차량이 아직 판매 중인지 확인
Returns:
True if car is still available, False if sold/removed
"""
try:
async with httpx.AsyncClient(timeout=30.0) as client:
# Carmodoo 차량 상세 페이지 접근
url = f"{self.carmodoo_base_url}/car/carPopView.html"
params = {"carNo": source_id}
headers = {}
if self.carmodoo_session_cookie:
headers["Cookie"] = self.carmodoo_session_cookie
response = await client.get(url, params=params, headers=headers)
if response.status_code == 404:
return False
# 페이지 내용에서 "판매완료", "삭제", "없는 차량" 등 확인
content = response.text.lower()
sold_keywords = [
"판매완료", "판매 완료", "sold", "삭제된", "없는 차량",
"존재하지 않", "찾을 수 없", "not found"
]
for keyword in sold_keywords:
if keyword in content:
return False
return True
except Exception as e:
logger.error(f"Error checking car {source_id}: {e}")
# 에러 발생 시 available로 간주 (안전하게)
return True
async def check_all_cars(self) -> Tuple[int, int, List[int]]:
"""
모든 활성 차량의 판매 여부 확인
Returns:
(checked_count, soldout_count, soldout_car_ids)
"""
# soldout=False인 활성 차량만 조회
cars = self.db.query(Car).filter(
Car.status == "active",
Car.soldout == False,
Car.source == "carmodoo"
).all()
logger.info(f"Checking {len(cars)} cars for soldout status...")
checked = 0
soldout_count = 0
soldout_ids = []
for car in cars:
is_available = await self.check_car_availability(car.source_id)
checked += 1
if not is_available:
car.soldout = True
soldout_count += 1
soldout_ids.append(car.id)
logger.info(f"Car {car.id} ({car.car_name}) marked as SOLD OUT")
# Rate limiting
if checked % 10 == 0:
logger.info(f"Progress: {checked}/{len(cars)} checked, {soldout_count} sold out")
import asyncio
await asyncio.sleep(1)
self.db.commit()
logger.info(f"Soldout check completed: {checked} checked, {soldout_count} sold out")
return checked, soldout_count, soldout_ids
def mark_soldout(self, car_id: int) -> bool:
"""수동으로 차량을 soldout 처리"""
car = self.db.query(Car).filter(Car.id == car_id).first()
if car:
car.soldout = True
self.db.commit()
return True
return False
def mark_available(self, car_id: int) -> bool:
"""수동으로 차량을 available 처리"""
car = self.db.query(Car).filter(Car.id == car_id).first()
if car:
car.soldout = False
self.db.commit()
return True
return False