# -*- 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