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:
AutonetSellCar Deploy
2025-12-30 13:24:39 +09:00
commit 1f0dcb1ddb
224 changed files with 55119 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
from .car import (
CarMakerCreate, CarMakerResponse,
CarModelCreate, CarModelResponse,
CarCreate, CarUpdate, CarResponse, CarListResponse,
CarImageCreate, CarImageResponse,
)
from .user import UserCreate, UserUpdate, UserResponse, Token, CarViewResponse, PurchaseViewRequest
from .inquiry import (
InquiryCreate, InquiryResponse,
InquiryMessageCreate, InquiryMessageResponse,
InquiryWithMessages, InquiryListResponse,
AdminInquiryRespond, AdminInquiryUpdateStatus,
)
from .hero_banner import (
HeroBannerCreate, HeroBannerUpdate, HeroBannerResponse,
HeroBannerListResponse, HeroBannerLocalizedResponse,
HeroBannerSettingsUpdate, HeroBannerSettingsResponse,
)
from .translation import (
TranslationCreate, TranslationUpdate, TranslationResponse,
TranslationListResponse, TranslationBulkRequest, TranslationBulkResponse,
)
from .vehicle_request import (
VehicleRequestCreate, VehicleRequestResponse,
RequestVehicleCreate, RequestVehicleResponse, RequestVehicleApprove,
PurchasedVehicleCreate, PurchasedVehicleResponse, PurchasedVehicleUpdateStatus,
VehicleRequestWithVehicles,
)
from .dealer import (
DealerApplicationCreate, DealerApplicationResponse,
DealerApplicationApprove, DealerApplicationReject,
DealerInfoResponse, DealerPublicInfo,
)
from .vehicle_share import (
VehicleShareCreate, VehicleShareResponse, VehicleSharePublic,
ShareRewardResponse, ShareRewardSummary,
)
from .withdrawal import (
WithdrawalRequestCreate, WithdrawalRequestResponse,
WithdrawalProcess, WithdrawalBalance,
)
from .referral import (
ReferralRewardResponse, ReferralStats,
ReferralSettingsResponse, ReferralSettingsUpdate,
)
from .notification import (
NotificationCreate, NotificationResponse,
NotificationListResponse, NotificationMarkRead,
)
__all__ = [
"CarMakerCreate", "CarMakerResponse",
"CarModelCreate", "CarModelResponse",
"CarCreate", "CarUpdate", "CarResponse", "CarListResponse",
"CarImageCreate", "CarImageResponse",
"UserCreate", "UserUpdate", "UserResponse", "Token", "CarViewResponse", "PurchaseViewRequest",
"InquiryCreate", "InquiryResponse",
"InquiryMessageCreate", "InquiryMessageResponse",
"InquiryWithMessages", "InquiryListResponse",
"AdminInquiryRespond", "AdminInquiryUpdateStatus",
"HeroBannerCreate", "HeroBannerUpdate", "HeroBannerResponse",
"HeroBannerListResponse", "HeroBannerLocalizedResponse",
"HeroBannerSettingsUpdate", "HeroBannerSettingsResponse",
"TranslationCreate", "TranslationUpdate", "TranslationResponse",
"TranslationListResponse", "TranslationBulkRequest", "TranslationBulkResponse",
"VehicleRequestCreate", "VehicleRequestResponse",
"RequestVehicleCreate", "RequestVehicleResponse", "RequestVehicleApprove",
"PurchasedVehicleCreate", "PurchasedVehicleResponse", "PurchasedVehicleUpdateStatus",
"VehicleRequestWithVehicles",
"DealerApplicationCreate", "DealerApplicationResponse",
"DealerApplicationApprove", "DealerApplicationReject",
"DealerInfoResponse", "DealerPublicInfo",
"VehicleShareCreate", "VehicleShareResponse", "VehicleSharePublic",
"ShareRewardResponse", "ShareRewardSummary",
"WithdrawalRequestCreate", "WithdrawalRequestResponse",
"WithdrawalProcess", "WithdrawalBalance",
"ReferralRewardResponse", "ReferralStats",
"ReferralSettingsResponse", "ReferralSettingsUpdate",
"NotificationCreate", "NotificationResponse",
"NotificationListResponse", "NotificationMarkRead",
]

185
backend/app/schemas/car.py Normal file
View File

@@ -0,0 +1,185 @@
from pydantic import BaseModel
from typing import Optional, List, Any
from datetime import datetime
from decimal import Decimal
# CarSpecification Schema
class CarSpecificationResponse(BaseModel):
id: int
car_id: int
manufacturer: Optional[str] = None
model_name: Optional[str] = None
grade: Optional[str] = None
model_year: Optional[str] = None
displacement: Optional[int] = None
fuel_type: Optional[str] = None
transmission: Optional[str] = None
drive_type: Optional[str] = None
max_power: Optional[str] = None
max_torque: Optional[str] = None
fuel_efficiency: Optional[str] = None
body_type: Optional[str] = None
door_count: Optional[int] = None
seating_capacity: Optional[int] = None
length: Optional[int] = None
width: Optional[int] = None
height: Optional[int] = None
wheelbase: Optional[int] = None
curb_weight: Optional[int] = None
safety_options: Optional[List[str]] = None
comfort_options: Optional[List[str]] = None
exterior_options: Optional[List[str]] = None
interior_options: Optional[List[str]] = None
raw_data: Optional[Any] = None
class Config:
from_attributes = True
# CarMaker Schemas
class CarMakerCreate(BaseModel):
code: str
name: str
name_en: Optional[str] = None
class CarMakerResponse(BaseModel):
id: int
code: str
name: str
name_en: Optional[str] = None
class Config:
from_attributes = True
# CarModel Schemas
class CarModelCreate(BaseModel):
code: str
maker_id: int
name: str
name_en: Optional[str] = None
class CarModelResponse(BaseModel):
id: int
code: str
maker_id: int
name: str
name_en: Optional[str] = None
class Config:
from_attributes = True
# CarImage Schemas
class CarImageCreate(BaseModel):
url: Optional[str] = None
local_path: Optional[str] = None
is_main: bool = False
sort_order: int = 0
class CarImageResponse(BaseModel):
id: int
url: Optional[str] = None
local_path: Optional[str] = None
is_main: bool
sort_order: int
class Config:
from_attributes = True
# Car Schemas
class CarCreate(BaseModel):
source: str = "carmodoo"
source_id: str
source_key: Optional[str] = None
maker_code: Optional[str] = None
model_code: Optional[str] = None
car_name: Optional[str] = None
year: Optional[int] = None
month: Optional[int] = None
mileage: Optional[int] = None
price_krw: Optional[int] = None
price_usd: Optional[Decimal] = None
fuel: Optional[str] = None
transmission: Optional[str] = None
color: Optional[str] = None
displacement: Optional[int] = None
car_number: Optional[str] = None
seize_count: int = 0
collateral_count: int = 0
check_num: Optional[str] = None
dealer_name: Optional[str] = None
dealer_phone: Optional[str] = None
shop_name: Optional[str] = None
memo: Optional[str] = None
images: List[CarImageCreate] = []
options: List[str] = []
class CarUpdate(BaseModel):
car_name: Optional[str] = None
year: Optional[int] = None
month: Optional[int] = None
mileage: Optional[int] = None
price_krw: Optional[int] = None
margin_krw: Optional[int] = None
margin_mn: Optional[int] = None
price_usd: Optional[Decimal] = None
fuel: Optional[str] = None
transmission: Optional[str] = None
color: Optional[str] = None
status: Optional[str] = None
is_displayed: Optional[bool] = None
class CarResponse(BaseModel):
id: int
source: str
source_id: str
car_name: Optional[str] = None
year: Optional[int] = None
month: Optional[int] = None
mileage: Optional[int] = None
price_krw: Optional[int] = None
margin_krw: Optional[int] = 0
margin_mn: Optional[int] = 0
final_price_krw: Optional[int] = None # Computed: price_krw + margin_krw (for Korean users)
final_price_mn: Optional[int] = None # Computed: price_krw + margin_mn (for Mongolian users)
price_usd: Optional[Decimal] = None
is_displayed: bool = False
fuel: Optional[str] = None
transmission: Optional[str] = None
color: Optional[str] = None
displacement: Optional[int] = None
car_number: Optional[str] = None
seize_count: int
collateral_count: int
check_num: Optional[str] = None
dealer_name: Optional[str] = None
dealer_description: Optional[str] = None
dealer_description_en: Optional[str] = None
dealer_description_mn: Optional[str] = None
dealer_description_ru: Optional[str] = None
status: str
created_at: datetime
updated_at: datetime
maker: Optional[CarMakerResponse] = None
model: Optional[CarModelResponse] = None
images: List[CarImageResponse] = []
specification: Optional[CarSpecificationResponse] = None
class Config:
from_attributes = True
class CarListResponse(BaseModel):
total: int
page: int
page_size: int
cars: List[CarResponse]

View File

@@ -0,0 +1,80 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class DealerApplicationCreate(BaseModel):
"""Schema for creating a dealer application"""
business_name: str
business_number: Optional[str] = None
real_name: str
id_number: Optional[str] = None # Will be encrypted before storage
phone: str
bank_name: str
bank_account: str
account_holder: str
photo_url: Optional[str] = None
class DealerApplicationResponse(BaseModel):
"""Schema for dealer application response"""
id: int
user_id: int
business_name: str
business_number: Optional[str] = None
real_name: str
phone: str
bank_name: str
bank_account: str
account_holder: str
photo_url: Optional[str] = None
status: str
rejected_reason: Optional[str] = None
applied_at: datetime
approved_at: Optional[datetime] = None
class Config:
from_attributes = True
class DealerApplicationApprove(BaseModel):
"""Schema for approving a dealer application"""
pass # No additional fields needed
class DealerApplicationReject(BaseModel):
"""Schema for rejecting a dealer application"""
reason: str
class DealerInfoResponse(BaseModel):
"""Schema for dealer info response"""
id: int
user_id: int
dealer_code: str
dealer_card_url: Optional[str] = None
business_name: str
real_name: str
phone: str
photo_url: Optional[str] = None
total_commission_earned: float
total_withdrawn: float
pending_withdrawal: float
is_active: bool
created_at: datetime
class Config:
from_attributes = True
class DealerPublicInfo(BaseModel):
"""Public dealer info for displaying in lists"""
id: int
dealer_code: str
business_name: str
real_name: str
photo_url: Optional[str] = None
is_active: bool
class Config:
from_attributes = True

View File

@@ -0,0 +1,101 @@
from pydantic import BaseModel, HttpUrl
from typing import Optional
from datetime import datetime
# ==================== Hero Banner Settings ====================
class HeroBannerSettingsBase(BaseModel):
slide_interval: int = 3000
animation_type: str = "film-strip"
image_width: int = 500
image_height: int = 300
auto_play: bool = True
class HeroBannerSettingsUpdate(BaseModel):
slide_interval: Optional[int] = None
animation_type: Optional[str] = None
image_width: Optional[int] = None
image_height: Optional[int] = None
auto_play: Optional[bool] = None
class HeroBannerSettingsResponse(HeroBannerSettingsBase):
id: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
# ==================== Hero Banner ====================
class HeroBannerBase(BaseModel):
title_ko: Optional[str] = None
title_en: Optional[str] = None
title_mn: Optional[str] = None
subtitle_ko: Optional[str] = None
subtitle_en: Optional[str] = None
subtitle_mn: Optional[str] = None
image_url: str
link_url: Optional[str] = None
car_id: Optional[int] = None
is_active: bool = True
display_order: int = 0
class HeroBannerCreate(HeroBannerBase):
pass
class HeroBannerUpdate(BaseModel):
title_ko: Optional[str] = None
title_en: Optional[str] = None
title_mn: Optional[str] = None
subtitle_ko: Optional[str] = None
subtitle_en: Optional[str] = None
subtitle_mn: Optional[str] = None
image_url: Optional[str] = None
link_url: Optional[str] = None
car_id: Optional[int] = None
is_active: Optional[bool] = None
display_order: Optional[int] = None
class HeroBannerResponse(HeroBannerBase):
id: int
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class HeroBannerListResponse(BaseModel):
id: int
title_ko: Optional[str] = None
title_en: Optional[str] = None
image_url: str
link_url: Optional[str] = None
car_id: Optional[int] = None
is_active: bool
display_order: int
created_at: datetime
class Config:
from_attributes = True
# 다국어 지원 응답 (Public API용)
class HeroBannerLocalizedResponse(BaseModel):
id: int
title: Optional[str] = None
subtitle: Optional[str] = None
image_url: str
link_url: Optional[str] = None
car_id: Optional[int] = None
class Config:
from_attributes = True

View File

@@ -0,0 +1,67 @@
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
class InquiryCreate(BaseModel):
category: str = "general"
subject: Optional[str] = None
message: str
contact_email: Optional[str] = None
contact_phone: Optional[str] = None
car_id: Optional[int] = None # For backward compatibility
class InquiryResponse(BaseModel):
id: int
user_id: Optional[int] = None
car_id: Optional[int] = None
category: Optional[str] = "general"
subject: Optional[str] = None
message: str
contact_email: Optional[str] = None
contact_phone: Optional[str] = None
status: str
admin_response: Optional[str] = None
responded_at: Optional[datetime] = None
responded_by: Optional[int] = None
created_at: datetime
updated_at: Optional[datetime] = None
class Config:
from_attributes = True
class InquiryMessageCreate(BaseModel):
message: str
class InquiryMessageResponse(BaseModel):
id: int
inquiry_id: int
user_id: int
message: str
is_admin: bool
created_at: datetime
class Config:
from_attributes = True
class InquiryWithMessages(BaseModel):
inquiry: InquiryResponse
messages: List[InquiryMessageResponse]
class InquiryListResponse(BaseModel):
inquiries: List[InquiryResponse]
total: int
class AdminInquiryRespond(BaseModel):
message: str
status: Optional[str] = None # Can update status with response
class AdminInquiryUpdateStatus(BaseModel):
status: str

View File

@@ -0,0 +1,44 @@
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
class NotificationCreate(BaseModel):
"""Create notification schema"""
user_id: int
notification_type: str
title: str
message: str
link: Optional[str] = None
related_id: Optional[int] = None
related_type: Optional[str] = None
class NotificationResponse(BaseModel):
"""Notification response schema"""
id: int
user_id: int
notification_type: str
title: str
message: str
link: Optional[str] = None
related_id: Optional[int] = None
related_type: Optional[str] = None
is_read: bool
read_at: Optional[datetime] = None
created_at: datetime
class Config:
from_attributes = True
class NotificationListResponse(BaseModel):
"""Notification list with unread count"""
notifications: List[NotificationResponse]
unread_count: int
total: int
class NotificationMarkRead(BaseModel):
"""Mark notifications as read"""
notification_ids: List[int]

View File

@@ -0,0 +1,41 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class ReferralRewardResponse(BaseModel):
"""레퍼럴 보상 응답 스키마"""
id: int
referrer_id: int
referred_user_id: int
payment_amount: float
reward_amount: float
status: str
created_at: datetime
credited_at: Optional[datetime] = None
class Config:
from_attributes = True
class ReferralStats(BaseModel):
"""레퍼럴 통계 스키마"""
total_referrals: int # 총 추천한 회원 수
total_rewards_earned: float # 총 보상 금액
total_rewards_credited: float # 적립된 보상 금액
total_rewards_pending: float # 대기 중인 보상 금액
available_for_withdrawal: float # 출금 가능 금액
class ReferralSettingsResponse(BaseModel):
"""레퍼럴 설정 응답 스키마"""
referral_reward_enabled: bool
referral_reward_percent: float
referral_reward_type: str # one_time / recurring
class ReferralSettingsUpdate(BaseModel):
"""레퍼럴 설정 업데이트 스키마"""
referral_reward_enabled: Optional[bool] = None
referral_reward_percent: Optional[float] = None
referral_reward_type: Optional[str] = None

View File

@@ -0,0 +1,37 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class SystemSettingsUpdate(BaseModel):
"""시스템 설정 수정용 스키마"""
search_page_size: Optional[int] = None
korea_margin_percent: Optional[float] = None
mongolia_margin_percent: Optional[float] = None
cc_per_usdc: Optional[int] = None
cc_per_view: Optional[int] = None
cc_signup_bonus: Optional[int] = None
cars_per_cc: Optional[int] = None
cache_ttl_hours: Optional[int] = None
container_logistics_usd: Optional[int] = None
shoring_cost_usd: Optional[int] = None
class SystemSettingsResponse(BaseModel):
"""시스템 설정 응답 스키마"""
id: int
search_page_size: int
korea_margin_percent: float
mongolia_margin_percent: float
cc_per_usdc: int
cc_per_view: int
cc_signup_bonus: int
cars_per_cc: int
cache_ttl_hours: int
container_logistics_usd: int
shoring_cost_usd: int
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
class Config:
from_attributes = True

View File

@@ -0,0 +1,52 @@
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
class TranslationCreate(BaseModel):
source_text: str
category: str
text_en: Optional[str] = None
text_mn: Optional[str] = None
text_ru: Optional[str] = None
class TranslationUpdate(BaseModel):
source_text: Optional[str] = None
category: Optional[str] = None
text_en: Optional[str] = None
text_mn: Optional[str] = None
text_ru: Optional[str] = None
class TranslationResponse(BaseModel):
id: int
source_text: str
category: str
text_en: Optional[str] = None
text_mn: Optional[str] = None
text_ru: Optional[str] = None
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class TranslationListResponse(BaseModel):
total: int
page: int
page_size: int
translations: List[TranslationResponse]
class TranslationBulkRequest(BaseModel):
"""Bulk translation lookup request"""
texts: List[str]
category: Optional[str] = None
lang: str = "en"
class TranslationBulkResponse(BaseModel):
"""Returns a dictionary mapping source text to translated text"""
translations: dict # {source_text: translated_text}

View File

@@ -0,0 +1,62 @@
from pydantic import BaseModel, EmailStr
from typing import Optional
from datetime import datetime
class UserCreate(BaseModel):
email: EmailStr
password: str
name: Optional[str] = None
phone: Optional[str] = None
country: str = "Mongolia"
referred_by: Optional[str] = None # Referral code of the user who referred
class UserUpdate(BaseModel):
"""Schema for updating user profile"""
name: Optional[str] = None
phone: Optional[str] = None
country: Optional[str] = None
class UserResponse(BaseModel):
id: int
email: str
name: Optional[str] = None
phone: Optional[str] = None
country: str
is_active: bool
is_admin: bool = False
is_dealer: bool = False
cc_balance: float = 0.0 # Float to support fractional CC (e.g., 0.1 CC)
referral_code: Optional[str] = None # User's unique referral code
email_verified: bool = False
phone_verified: bool = False
created_at: datetime
class Config:
from_attributes = True
class CarViewResponse(BaseModel):
id: int
user_id: int
car_id: int
cc_paid: int
created_at: datetime
class Config:
from_attributes = True
class PurchaseViewRequest(BaseModel):
car_id: int
class Token(BaseModel):
access_token: str
token_type: str = "bearer"
class TokenData(BaseModel):
email: Optional[str] = None

View File

@@ -0,0 +1,122 @@
from pydantic import BaseModel
from typing import Optional, List, Any
from datetime import datetime
# Vehicle Request Schemas
class VehicleRequestCreate(BaseModel):
maker_code: str
maker_name: Optional[str] = None
model_code: str
model_name: Optional[str] = None
grade_code: Optional[str] = None
grade_name: Optional[str] = None
year_from: Optional[int] = None
year_to: Optional[int] = None
mileage_min: Optional[int] = None
mileage_max: Optional[int] = None
fuel: Optional[str] = None
displacement_min: Optional[int] = None
displacement_max: Optional[int] = None
class VehicleRequestResponse(BaseModel):
id: int
user_id: int
maker_code: Optional[str]
maker_name: Optional[str]
model_code: Optional[str]
model_name: Optional[str]
grade_code: Optional[str]
grade_name: Optional[str]
year_from: Optional[int]
year_to: Optional[int]
mileage_min: Optional[int]
mileage_max: Optional[int]
fuel: Optional[str]
displacement_min: Optional[int]
displacement_max: Optional[int]
status: str
admin_reviewed_at: Optional[datetime]
created_at: datetime
class Config:
from_attributes = True
# Request Vehicle (Admin recommended) Schemas
class RequestVehicleCreate(BaseModel):
request_id: int
car_data: dict
is_approved: bool = False
class RequestVehicleResponse(BaseModel):
id: int
request_id: int
car_data: dict
is_approved: bool
approved_at: Optional[datetime]
created_at: datetime
class Config:
from_attributes = True
class RequestVehicleApprove(BaseModel):
vehicle_ids: List[int]
# Purchased Vehicle Schemas
class PurchasedVehicleCreate(BaseModel):
car_name: str
car_data: Optional[dict] = None
car_image: Optional[str] = None
vehicle_price_krw: int
domestic_cost_krw: int
shipping_cost_usd: int
total_cost_krw: int
car_type: str # small, compact
selected_dealer_id: Optional[int] = None # Selected dealer for commission split
class PurchasedVehicleResponse(BaseModel):
id: int
user_id: int
car_name: Optional[str]
car_data: Optional[dict]
car_image: Optional[str]
vehicle_price_krw: Optional[int]
domestic_cost_krw: Optional[int]
shipping_cost_usd: Optional[int]
total_cost_krw: Optional[int]
car_type: Optional[str]
selected_dealer_id: Optional[int] = None
dealer_commission_krw: Optional[int] = 0
platform_commission_krw: Optional[int] = 0
commission_paid: bool = False
commission_paid_at: Optional[datetime] = None
shipping_status: int
status_updated_at: Optional[datetime]
current_location: Optional[str]
estimated_arrival: Optional[datetime]
purchased_at: datetime
delivered_at: Optional[datetime]
class Config:
from_attributes = True
class PurchasedVehicleUpdateStatus(BaseModel):
shipping_status: int # 1-7: 구매완료, 인천항, 텐진항, 자먼우드, 울란바토르, 통관, 배송완료
current_location: Optional[str] = None
estimated_arrival: Optional[datetime] = None
# List response with request and approved vehicles
class VehicleRequestWithVehicles(BaseModel):
request: VehicleRequestResponse
approved_vehicles: List[RequestVehicleResponse]
class Config:
from_attributes = True

View File

@@ -0,0 +1,69 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class VehicleShareCreate(BaseModel):
"""Schema for creating a vehicle share"""
request_vehicle_id: int
markup_amount_krw: float = 0
class VehicleShareResponse(BaseModel):
"""Schema for vehicle share response"""
id: int
user_id: int
request_vehicle_id: int
share_code: str
original_price_krw: float
markup_amount_krw: float
shared_price_krw: float
view_count: int
is_purchased: bool
purchased_by_user_id: Optional[int] = None
created_at: datetime
expires_at: Optional[datetime] = None
purchased_at: Optional[datetime] = None
class Config:
from_attributes = True
class VehicleSharePublic(BaseModel):
"""Public schema for shared vehicle (for viewing shared link)"""
id: int
share_code: str
shared_price_krw: float
view_count: int
is_purchased: bool
created_at: datetime
# Vehicle info will be added separately
class Config:
from_attributes = True
class ShareRewardResponse(BaseModel):
"""Schema for share reward response"""
id: int
user_id: int
vehicle_share_id: int
markup_amount: float
reward_amount: float
tax_amount: float
net_amount: float
status: str
withdrawn_at: Optional[datetime] = None
created_at: datetime
class Config:
from_attributes = True
class ShareRewardSummary(BaseModel):
"""Summary of user's share rewards"""
total_rewards: float
total_withdrawn: float
pending_amount: float
available_for_withdrawal: float
reward_count: int

View File

@@ -0,0 +1,44 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
class WithdrawalRequestCreate(BaseModel):
"""Schema for creating a withdrawal request"""
amount: float
bank_name: str
bank_account: str
account_holder: str
class WithdrawalRequestResponse(BaseModel):
"""Schema for withdrawal request response"""
id: int
user_id: int
amount: float
tax_withheld: float
net_amount: float
bank_name: str
bank_account: str
account_holder: str
status: str
admin_note: Optional[str] = None
requested_at: datetime
processed_at: Optional[datetime] = None
class Config:
from_attributes = True
class WithdrawalProcess(BaseModel):
"""Schema for processing a withdrawal (admin)"""
status: str # approved, completed, rejected
admin_note: Optional[str] = None
class WithdrawalBalance(BaseModel):
"""Schema for user's withdrawal balance"""
total_earned: float # Total earnings (dealer commission + share rewards)
total_withdrawn: float # Total already withdrawn
pending_withdrawal: float # Currently pending withdrawal requests
available_balance: float # Available for withdrawal