J1K / models.py
jas1k1's picture
Upload 4 files
737707b verified
# -*- coding: utf-8 -*-
"""
J1K OS v2.2 Final Master - Pydantic Models
UUID Edition with JSONB Support
"""
from pydantic import BaseModel, Field, field_validator
from typing import Optional, List, Dict, Any
from uuid import UUID
import uuid
from enum import Enum
from datetime import datetime
# ==========================================
# Helper: Hybrid UUID Validator
# ==========================================
def validate_hybrid_biz_id(v):
"""
๐Ÿง  ุงู„ู…ุญุฑูƒ ุงู„ุณุญุฑูŠ: ุจูŠุชุฑุฌู… ุฃูŠ Slug ุฃูˆ UUID String ู„ู€ UUID Object
"""
if not v:
raise ValueError("Business ID is missing")
# ู„ูˆ ุฌุงูŠ UUID Object ุฎู„ุงุต
if isinstance(v, uuid.UUID):
return v
# ู„ูˆ ุฌุงูŠ StringุŒ ุญุงูˆู„ ุชุญูˆู„ู‡ ู„ู€ UUID
try:
# ู…ุญุงูˆู„ุฉ UUID ู…ุจุงุดุฑุฉ
return uuid.UUID(str(v))
except ValueError:
# ู…ุด UUID ุตุงู„ุญุŒ ูŠุจู‚ู‰ Slug - ุงู„ู€ Backend ู‡ูŠุฑุฌุนู‡ ู…ู† ุงู„ุฏุงุชุงุจูŠุฒ
# Note: ุงู„ู€ resolve_biz_id_cached ู‡ุชุชู†ูุฐ ููŠ ุงู„ู€ Endpoint
pass
# ุฑุฌุน String ุนุดุงู† ุงู„ู€ Endpoint ูŠุญู„ู‡
return str(v)
# ==========================================
# 1. Business Models
# ==========================================
class Business(BaseModel):
id: Optional[UUID] = None
name: str
sector: str
whatsapp_number: Optional[str] = None
slug: Optional[str] = None
logo_url: Optional[str] = None
gps_location: Optional[str] = None
contacts_json: Optional[str] = None
is_active: Optional[bool] = True
is_open: Optional[int] = 1
is_premium: Optional[bool] = False
ai_enabled: Optional[bool] = False
offers_access: Optional[bool] = False
delivery_fee: Optional[float] = 0.0
subscription_expires_at: Optional[str] = None
theme_primary_color: Optional[str] = None
theme_secondary_color: Optional[str] = None
# ๐Ÿ‘‡ ุญู…ุงูŠุฉ ุงู„ุจูŠุงู†ุงุช ุงู„ุณูŠุงุฏูŠุฉ
username: Optional[str] = None
password: Optional[str] = None
class BusinessCreate(BaseModel):
name: str
sector: str
slug: str
username: str
password: str
whatsapp_number: Optional[str] = None
logo_url: Optional[str] = None
gps_location: Optional[str] = None
is_active: bool = True
is_premium: bool = False
ai_enabled: bool = False
offers_access: bool = False
class BusinessUpdate(BaseModel):
whatsapp_number: Optional[str] = None
delivery_fee: Optional[float] = None
logo_url: Optional[str] = None
gps_location: Optional[str] = None
contacts_json: Optional[str] = None
ai_enabled: Optional[bool] = None
is_premium: Optional[bool] = None
is_open: Optional[int] = None
is_active: Optional[bool] = None
class ThemeUpdateRequest(BaseModel):
theme_primary_color: Optional[str] = None
theme_secondary_color: Optional[str] = None
# ==========================================
# 2. Inventory Item Models
# ==========================================
class InventoryItem(BaseModel):
id: Optional[UUID] = None
business_id: UUID # โ† UUID ุนุดุงู† RLS
name: str
description: Optional[str] = ""
price: float
old_price: Optional[float] = 0.0
category: str
image_data: Optional[str] = ""
image_url: Optional[str] = None
is_available: Optional[int] = 1
is_bogo: Optional[bool] = False
extra_info: Optional[Dict[str, Any]] = {}
created_at: Optional[datetime] = None
class InventoryItemCreate(BaseModel):
business_id: UUID
name: str
description: Optional[str] = ""
price: float
old_price: Optional[float] = 0.0
category: str
image_data: Optional[str] = ""
is_bogo: Optional[bool] = False
@field_validator('business_id', mode='before')
@classmethod
def resolve_id(cls, v):
return validate_hybrid_biz_id(v)
class InventoryItemExt(BaseModel):
"""ู†ุณุฎุฉ ู…ูˆุณุนุฉ ู…ุน Validator"""
business_id: UUID
name: str
description: Optional[str] = ""
price: float
old_price: Optional[float] = 0.0
is_bogo: Optional[bool] = False
category: str
image_data: Optional[str] = ""
@field_validator('business_id', mode='before')
@classmethod
def resolve_id(cls, v):
return validate_hybrid_biz_id(v)
class ItemUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
category: Optional[str] = None
price: Optional[float] = None
old_price: Optional[float] = None
is_bogo: Optional[bool] = None
image_data: Optional[str] = None
is_available: Optional[int] = None
# ==========================================
# 3. Order Item (JSONB Structure)
# ==========================================
class OrderItem(BaseModel):
"""Structure ู„ูƒู„ item ููŠ ุงู„ุฃูˆุฑุฏุฑ - ู„ู„ู€ JSONB"""
id: str # item ID ู…ู† inventory
name: str
price: float
quantity: int
category: Optional[str] = None
notes: Optional[str] = None # ุชุนุฏูŠู„ุงุช ุฒูŠ "ุจุฏูˆู† ุจุตู„"
# ==========================================
# 4. Order Models (JSONB Edition)
# ==========================================
class OrderType(str, Enum):
delivery = "delivery"
takeaway = "takeaway"
dine_in = "dine_in"
manual = "manual"
class OrderSubmit(BaseModel):
"""
๐Ÿ”„ JSONB Edition: items ูƒู€ List of Dicts
ู…ุด String ู…ุนู…ูˆู„ู‡ุง json.dumps()
"""
business_id: UUID
customer_name: str
customer_phone: str
customer_address: Optional[str] = "ุงุณุชู„ุงู… ู…ู† ุงู„ูุฑุน"
notes: str = ""
status: str = "pending"
gps_link: str = ""
# ๐ŸŽฏ JSONB: List of OrderItem objects
items: List[OrderItem] # โ† ู…ุด str!
total_price: float
order_type: OrderType = OrderType.delivery
table_number: Optional[str] = None
discount_amount: float = 0.0
used_points: int = 0
device_fingerprint: Optional[str] = "Unknown"
delivery_name: Optional[str] = None
delivery_phone: Optional[str] = None
delivery_fee: Optional[float] = 0.0
@field_validator('business_id', mode='before')
@classmethod
def resolve_id(cls, v):
return validate_hybrid_biz_id(v)
@field_validator('items', mode='before')
@classmethod
def validate_items(cls, v):
"""ุชุฃูƒุฏ ุฅู† ุงู„ู€ items List ู…ุด ูุงุถูŠุฉ"""
if isinstance(v, str):
# ๐Ÿ›ก๏ธ ู„ูˆ ุงู„ู€ Frontend ู„ุณู‡ ุจูŠุจุนุช StringุŒ ุญูˆู„ู‡
import json
try:
v = json.loads(v)
except json.JSONDecodeError:
raise ValueError("items must be valid JSON array")
if not isinstance(v, list):
raise ValueError("items must be a list")
if len(v) == 0:
raise ValueError("items cannot be empty")
return v
class OrderStatusUpdate(BaseModel):
status: str
# ==========================================
# 5. Coupon Models
# ==========================================
class CouponCreate(BaseModel):
business_id: UUID
code: str
discount_percent: float
@field_validator('business_id', mode='before')
@classmethod
def resolve_id(cls, v):
return validate_hybrid_biz_id(v)
class CouponValidate(BaseModel):
business_id: UUID
code: str
@field_validator('business_id', mode='before')
@classmethod
def resolve_id(cls, v):
return validate_hybrid_biz_id(v)
# ==========================================
# 6. Shift Models
# ==========================================
class ShiftAction(BaseModel):
action: str # 'open' or 'close'
cashier_name: Optional[str] = None
opening_balance: Optional[float] = 0.0
class ExpenseCreate(BaseModel):
shift_id: UUID
amount: float
description: str
created_by: str
# ==========================================
# 7. Settlement Models
# ==========================================
class SettlementCreate(BaseModel):
pilot_name: str
total_amount: float
orders_count: int = 1
notes: Optional[str] = None
# ==========================================
# 8. AI Models
# ==========================================
class AIRequest(BaseModel):
business_id: UUID
item_name: str
sector: str = "FOOD_HUB"
@field_validator('business_id', mode='before')
@classmethod
def resolve_id(cls, v):
return validate_hybrid_biz_id(v)
class ImageRequest(BaseModel):
item_name: str
business_id: UUID
description: str
sector: str
forced_keywords: Optional[str] = None
@field_validator('business_id', mode='before')
@classmethod
def resolve_id(cls, v):
return validate_hybrid_biz_id(v)
class AIChatRequest(BaseModel):
prompt: str
menuContext: Any
business_id: Optional[UUID] = None
history: List[Dict[str, str]] = []
cartContext: Any = []
businessName: Optional[str] = "ู…ุทุนู…ู†ุง"
location: Optional[str] = "ู…ุตุฑ"
currentTime: Optional[str] = ""
weather: Optional[str] = "ู…ุนุชุฏู„"
@field_validator('business_id', mode='before')
@classmethod
def resolve_id(cls, v):
if v and str(v) != "default":
return validate_hybrid_biz_id(v)
return v
class AIStatusUpdate(BaseModel):
business_id: UUID
ai_enabled: bool = False
is_premium: bool = False
@field_validator('business_id', mode='before')
@classmethod
def resolve_id(cls, v):
return validate_hybrid_biz_id(v)
# ==========================================
# 9. Auth Models
# ==========================================
class LoginRequest(BaseModel):
username: str
password: str
class OTPRequest(BaseModel):
phone: str
class PasswordReset(BaseModel):
phone: str
otp: str
new_password: str
# ==========================================
# 10. Analytics Models
# ==========================================
class AnalyticsResponse(BaseModel):
peak_hours: Dict[str, int]
top_selling: Dict[str, int]
dead_stock: List[str]
total_revenue: float
order_split: Dict[str, int]
# ==========================================
# 11. Master Admin Models
# ==========================================
class MasterBizUpdate(BaseModel):
whatsapp_number: Optional[str] = None
delivery_fee: Optional[float] = 0.0
logo_url: Optional[str] = None
gps_location: Optional[str] = None
contacts_json: Optional[Dict[str, Any]] = None
ai_enabled: Optional[bool] = None
is_premium: Optional[bool] = None
offers_access: Optional[bool] = None
sector: Optional[str] = None
subscription_expires_at: Optional[str] = None
is_active: Optional[bool] = None
class MasterGenesis(BaseModel):
name: str
slug: str
sector: str
username: str
password: str
is_active: bool = True
whatsapp_number: Optional[str] = None
logo_url: str
gps_location: Optional[str] = None
offers_access: bool = False
ai_enabled: bool = False
is_premium: bool = False
contacts_json: Optional[Dict[str, Any]] = None
subscription_expires_at: Optional[str] = None
class BranchStatusUpdate(BaseModel):
is_open: int