Add configurable CC cost for banner vehicle PDF/description view

- Add cc_per_banner_view setting to system_settings (default 0.1 CC)
- Update car detail page to use dynamic CC value from settings
- Add CC per Banner View field in admin settings page
- Replace hardcoded 0.1 CC with configurable value

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
AutonetSellCar Deploy
2026-01-01 09:41:18 +09:00
parent 0d807eccc6
commit 7f45de7a89
6 changed files with 40 additions and 9 deletions

View File

@@ -146,7 +146,12 @@ def check_car_view(
} }
PERFORMANCE_CHECK_COST = 0.1 # 0.1 CC for performance check view def get_performance_check_cost(db: Session) -> float:
"""Get performance check cost from system settings"""
system_settings = db.query(SystemSettings).first()
if system_settings and system_settings.cc_per_banner_view is not None:
return system_settings.cc_per_banner_view
return 0.1 # Default fallback
@router.post("/purchase-performance-check") @router.post("/purchase-performance-check")
@@ -155,8 +160,9 @@ async def purchase_performance_check_view(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
"""Purchase access to view performance check (costs 0.1 CC)""" """Purchase access to view performance check (costs CC based on system settings)"""
car_id = request.car_id car_id = request.car_id
PERFORMANCE_CHECK_COST = get_performance_check_cost(db)
# Check if car exists # Check if car exists
car = db.query(Car).filter(Car.id == car_id).first() car = db.query(Car).filter(Car.id == car_id).first()

View File

@@ -26,6 +26,7 @@ def get_or_create_settings(db: Session) -> SystemSettings:
cc_per_usdc=1, # 1 USD = 1 CC cc_per_usdc=1, # 1 USD = 1 CC
cc_per_view=1, # 차량 상세 조회 시 1 CC cc_per_view=1, # 차량 상세 조회 시 1 CC
cars_per_cc=3, # 1 CC = 3 recommended vehicles per request cars_per_cc=3, # 1 CC = 3 recommended vehicles per request
cc_per_banner_view=0.1, # 배너 차량 PDF/상세 보기 비용
cc_signup_bonus=3, # 3 CC free on signup cc_signup_bonus=3, # 3 CC free on signup
cache_ttl_hours=2, cache_ttl_hours=2,
container_logistics_usd=3600, container_logistics_usd=3600,

View File

@@ -21,6 +21,7 @@ class SystemSettings(Base):
cc_per_view = Column(Integer, default=1) # 차량 상세 조회 시 1 CC cc_per_view = Column(Integer, default=1) # 차량 상세 조회 시 1 CC
cc_signup_bonus = Column(Integer, default=3) # 신규 가입 시 3 CC cc_signup_bonus = Column(Integer, default=3) # 신규 가입 시 3 CC
cars_per_cc = Column(Integer, default=3) # 1 CC당 추천 차량 수 (기본 3대) cars_per_cc = Column(Integer, default=3) # 1 CC당 추천 차량 수 (기본 3대)
cc_per_banner_view = Column(Float, default=0.1) # 배너 차량 PDF/상세 보기 비용 (기본 0.1 CC)
# 캐시 TTL (시간) # 캐시 TTL (시간)
cache_ttl_hours = Column(Integer, default=2) cache_ttl_hours = Column(Integer, default=2)

View File

@@ -12,6 +12,7 @@ class SystemSettingsUpdate(BaseModel):
cc_per_view: Optional[int] = None cc_per_view: Optional[int] = None
cc_signup_bonus: Optional[int] = None cc_signup_bonus: Optional[int] = None
cars_per_cc: Optional[int] = None cars_per_cc: Optional[int] = None
cc_per_banner_view: Optional[float] = None
cache_ttl_hours: Optional[int] = None cache_ttl_hours: Optional[int] = None
container_logistics_usd: Optional[int] = None container_logistics_usd: Optional[int] = None
shoring_cost_usd: Optional[int] = None shoring_cost_usd: Optional[int] = None
@@ -32,6 +33,7 @@ class SystemSettingsResponse(BaseModel):
cc_per_view: int cc_per_view: int
cc_signup_bonus: int cc_signup_bonus: int
cars_per_cc: int cars_per_cc: int
cc_per_banner_view: float = 0.1
cache_ttl_hours: int cache_ttl_hours: int
container_logistics_usd: int container_logistics_usd: int
shoring_cost_usd: int shoring_cost_usd: int

View File

@@ -11,6 +11,7 @@ interface SystemSettings {
cc_per_view: number; cc_per_view: number;
cc_signup_bonus: number; cc_signup_bonus: number;
cars_per_cc: number; cars_per_cc: number;
cc_per_banner_view: number;
cache_ttl_hours: number; cache_ttl_hours: number;
container_logistics_usd: number; container_logistics_usd: number;
shoring_cost_usd: number; shoring_cost_usd: number;
@@ -56,6 +57,7 @@ export default function SettingsPage() {
cc_per_view: 1, cc_per_view: 1,
cc_signup_bonus: 3, cc_signup_bonus: 3,
cars_per_cc: 3, cars_per_cc: 3,
cc_per_banner_view: 0.1,
cache_ttl_hours: 2, cache_ttl_hours: 2,
container_logistics_usd: 3600, container_logistics_usd: 3600,
shoring_cost_usd: 300, shoring_cost_usd: 300,
@@ -85,6 +87,7 @@ export default function SettingsPage() {
cc_per_view: data.cc_per_view, cc_per_view: data.cc_per_view,
cc_signup_bonus: data.cc_signup_bonus, cc_signup_bonus: data.cc_signup_bonus,
cars_per_cc: data.cars_per_cc || 3, cars_per_cc: data.cars_per_cc || 3,
cc_per_banner_view: data.cc_per_banner_view ?? 0.1,
cache_ttl_hours: data.cache_ttl_hours, cache_ttl_hours: data.cache_ttl_hours,
container_logistics_usd: data.container_logistics_usd || 3600, container_logistics_usd: data.container_logistics_usd || 3600,
shoring_cost_usd: data.shoring_cost_usd || 300, shoring_cost_usd: data.shoring_cost_usd || 300,
@@ -372,6 +375,22 @@ export default function SettingsPage() {
/> />
<p className="mt-1 text-sm text-gray-500">Free CC for new users</p> <p className="mt-1 text-sm text-gray-500">Free CC for new users</p>
</div> </div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
CC per Banner View (PDF/Description)
</label>
<input
type="number"
min="0"
max="10"
step="0.1"
value={formData.cc_per_banner_view}
onChange={(e) => setFormData(prev => ({ ...prev, cc_per_banner_view: parseFloat(e.target.value) || 0.1 }))}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
<p className="mt-1 text-sm text-gray-500"> PDF/ (기본값: 0.1 CC)</p>
</div>
</div> </div>
<div className="mt-4 p-4 bg-blue-50 rounded-lg"> <div className="mt-4 p-4 bg-blue-50 rounded-lg">

View File

@@ -53,18 +53,20 @@ export default function CarDetailPage() {
// System settings // System settings
const [showDealerCommentSetting, setShowDealerCommentSetting] = useState(true); const [showDealerCommentSetting, setShowDealerCommentSetting] = useState(true);
const [ccPerBannerView, setCcPerBannerView] = useState(0.1);
useEffect(() => { useEffect(() => {
if (params.id) { if (params.id) {
loadCar(Number(params.id)); loadCar(Number(params.id));
} }
// Fetch system settings for dealer comment visibility // Fetch system settings for dealer comment visibility and CC cost
const fetchSettings = async () => { const fetchSettings = async () => {
try { try {
const response = await fetch(`${API_BASE_URL}/api/settings/`); const response = await fetch(`${API_BASE_URL}/api/settings/`);
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
setShowDealerCommentSetting(data.show_dealer_comment ?? true); setShowDealerCommentSetting(data.show_dealer_comment ?? true);
setCcPerBannerView(data.cc_per_banner_view ?? 0.1);
} }
} catch (error) { } catch (error) {
console.error('Failed to fetch settings:', error); console.error('Failed to fetch settings:', error);
@@ -172,7 +174,7 @@ export default function CarDetailPage() {
return; return;
} }
if (ccBalance < 0.1) { if (ccBalance < ccPerBannerView) {
alert(t.insufficientCC); alert(t.insufficientCC);
return; return;
} }
@@ -356,7 +358,7 @@ export default function CarDetailPage() {
</div> </div>
<div> <div>
<p className="text-lg font-bold">{t.promotedVehicle}</p> <p className="text-lg font-bold">{t.promotedVehicle}</p>
<p className="text-sm opacity-90">{t.promotedVehicleDesc}</p> <p className="text-sm opacity-90">{t.promotedVehicleDesc.replace('0.1', String(ccPerBannerView))}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -915,7 +917,7 @@ export default function CarDetailPage() {
</ul> </ul>
</div> </div>
<div className="text-right"> <div className="text-right">
<span className="text-2xl font-bold text-blue-600">0.1 CC</span> <span className="text-2xl font-bold text-blue-600">{ccPerBannerView} CC</span>
</div> </div>
</div> </div>
<button <button
@@ -927,7 +929,7 @@ export default function CarDetailPage() {
? 'Login to Unlock' ? 'Login to Unlock'
: purchasingPerformanceCheck : purchasingPerformanceCheck
? 'Purchasing...' ? 'Purchasing...'
: 'Pay 0.1 CC to Unlock' : `Pay ${ccPerBannerView} CC to Unlock`
} }
</button> </button>
</div> </div>
@@ -959,7 +961,7 @@ export default function CarDetailPage() {
</ul> </ul>
</div> </div>
<div className="text-right"> <div className="text-right">
<span className="text-2xl font-bold text-blue-600">0.1 CC</span> <span className="text-2xl font-bold text-blue-600">{ccPerBannerView} CC</span>
</div> </div>
</div> </div>
<button <button
@@ -971,7 +973,7 @@ export default function CarDetailPage() {
? 'Login to Unlock' ? 'Login to Unlock'
: purchasingPerformanceCheck : purchasingPerformanceCheck
? 'Purchasing...' ? 'Purchasing...'
: 'Pay 0.1 CC to Unlock' : `Pay ${ccPerBannerView} CC to Unlock`
} }
</button> </button>
</div> </div>