from typing import Any, List, Optional, Union, Generic, TypeVar from pydantic import BaseModel, EmailStr, Field from enum import Enum from datetime import datetime, date T = TypeVar('T') class FilterItem(BaseModel): property: str = Field(..., description="نام فیلد مورد نظر برای اعمال فیلتر") operator: str = Field(..., description="نوع عملگر: =, >, >=, <, <=, !=, *, ?*, *?, in") value: Any = Field(..., description="مقدار مورد نظر") class QueryInfo(BaseModel): sort_by: Optional[str] = Field(default=None, description="نام فیلد مورد نظر برای مرتب سازی") sort_desc: bool = Field(default=False, description="false = مرتب سازی صعودی، true = مرتب سازی نزولی") take: int = Field(default=10, ge=1, le=1000, description="حداکثر تعداد رکورد بازگشتی") skip: int = Field(default=0, ge=0, description="تعداد رکوردی که از ابتدای لیست صرف نظر می شود") search: Optional[str] = Field(default=None, description="عبارت جستجو") search_fields: Optional[List[str]] = Field(default=None, description="آرایه ای از فیلدهایی که جستجو در آن انجام می گیرد") filters: Optional[List[FilterItem]] = Field(default=None, description="آرایه ای از اشیا برای اعمال فیلتر بر روی لیست") class CaptchaSolve(BaseModel): captcha_id: str = Field(..., min_length=8) captcha_code: str = Field(..., min_length=3, max_length=8) class RegisterRequest(CaptchaSolve): first_name: Optional[str] = Field(default=None, max_length=100) last_name: Optional[str] = Field(default=None, max_length=100) email: Optional[EmailStr] = None mobile: Optional[str] = Field(default=None, max_length=32) password: str = Field(..., min_length=8, max_length=128) device_id: Optional[str] = Field(default=None, max_length=100) referrer_code: Optional[str] = Field(default=None, min_length=4, max_length=32) class LoginRequest(CaptchaSolve): identifier: str = Field(..., min_length=3, max_length=255) password: str = Field(..., min_length=8, max_length=128) device_id: Optional[str] = Field(default=None, max_length=100) class ForgotPasswordRequest(CaptchaSolve): identifier: str = Field(..., min_length=3, max_length=255) class ResetPasswordRequest(CaptchaSolve): token: str = Field(..., min_length=16) new_password: str = Field(..., min_length=8, max_length=128) class ChangePasswordRequest(BaseModel): current_password: str = Field(..., min_length=8, max_length=128) new_password: str = Field(..., min_length=8, max_length=128) confirm_password: str = Field(..., min_length=8, max_length=128) class CreateApiKeyRequest(BaseModel): name: Optional[str] = Field(default=None, max_length=100) scopes: Optional[str] = Field(default=None, max_length=500) expires_at: Optional[str] = None # ISO string; parse server-side if provided # Response Models class SuccessResponse(BaseModel): success: bool = Field(default=True, description="وضعیت موفقیت عملیات") message: Optional[str] = Field(default=None, description="پیام توضیحی") data: Optional[Union[dict, list]] = Field(default=None, description="داده‌های بازگشتی") class ErrorResponse(BaseModel): success: bool = Field(default=False, description="وضعیت موفقیت عملیات") message: str = Field(..., description="پیام خطا") error_code: Optional[str] = Field(default=None, description="کد خطا") details: Optional[dict] = Field(default=None, description="جزئیات خطا") class UserResponse(BaseModel): id: int = Field(..., description="شناسه کاربر") email: Optional[str] = Field(default=None, description="ایمیل کاربر") mobile: Optional[str] = Field(default=None, description="شماره موبایل") first_name: Optional[str] = Field(default=None, description="نام") last_name: Optional[str] = Field(default=None, description="نام خانوادگی") is_active: bool = Field(..., description="وضعیت فعال بودن") referral_code: str = Field(..., description="کد معرفی") referred_by_user_id: Optional[int] = Field(default=None, description="شناسه کاربر معرف") app_permissions: Optional[dict] = Field(default=None, description="مجوزهای اپلیکیشن") created_at: str = Field(..., description="تاریخ ایجاد") updated_at: str = Field(..., description="تاریخ آخرین بروزرسانی") class CaptchaResponse(BaseModel): captcha_id: str = Field(..., description="شناسه کپچا") image_base64: str = Field(..., description="تصویر کپچا به صورت base64") ttl_seconds: int = Field(..., description="زمان انقضا به ثانیه") class LoginResponse(BaseModel): api_key: str = Field(..., description="کلید API") expires_at: Optional[str] = Field(default=None, description="تاریخ انقضا") user: UserResponse = Field(..., description="اطلاعات کاربر") class ApiKeyResponse(BaseModel): id: int = Field(..., description="شناسه کلید") name: Optional[str] = Field(default=None, description="نام کلید") scopes: Optional[str] = Field(default=None, description="محدوده دسترسی") device_id: Optional[str] = Field(default=None, description="شناسه دستگاه") user_agent: Optional[str] = Field(default=None, description="اطلاعات مرورگر") ip: Optional[str] = Field(default=None, description="آدرس IP") expires_at: Optional[str] = Field(default=None, description="تاریخ انقضا") last_used_at: Optional[str] = Field(default=None, description="آخرین استفاده") created_at: str = Field(..., description="تاریخ ایجاد") class ReferralStatsResponse(BaseModel): total_referrals: int = Field(..., description="تعداد کل معرفی‌ها") active_referrals: int = Field(..., description="تعداد معرفی‌های فعال") recent_referrals: int = Field(..., description="تعداد معرفی‌های اخیر") referral_rate: float = Field(..., description="نرخ معرفی") class PaginationInfo(BaseModel): total: int = Field(..., description="تعداد کل رکوردها") page: int = Field(..., description="شماره صفحه فعلی") per_page: int = Field(..., description="تعداد رکورد در هر صفحه") total_pages: int = Field(..., description="تعداد کل صفحات") has_next: bool = Field(..., description="آیا صفحه بعدی وجود دارد") has_prev: bool = Field(..., description="آیا صفحه قبلی وجود دارد") class UsersListResponse(BaseModel): items: List[UserResponse] = Field(..., description="لیست کاربران") pagination: PaginationInfo = Field(..., description="اطلاعات صفحه‌بندی") query_info: dict = Field(..., description="اطلاعات جستجو و فیلتر") class UsersSummaryResponse(BaseModel): total_users: int = Field(..., description="تعداد کل کاربران") active_users: int = Field(..., description="تعداد کاربران فعال") inactive_users: int = Field(..., description="تعداد کاربران غیرفعال") active_percentage: float = Field(..., description="درصد کاربران فعال") # Business Schemas class BusinessType(str, Enum): COMPANY = "شرکت" SHOP = "مغازه" STORE = "فروشگاه" UNION = "اتحادیه" CLUB = "باشگاه" INSTITUTE = "موسسه" INDIVIDUAL = "شخصی" class BusinessField(str, Enum): MANUFACTURING = "تولیدی" COMMERCIAL = "بازرگانی" SERVICE = "خدماتی" OTHER = "سایر" class BusinessCreateRequest(BaseModel): name: str = Field(..., min_length=1, max_length=255, description="نام کسب و کار") business_type: BusinessType = Field(..., description="نوع کسب و کار") business_field: BusinessField = Field(..., description="زمینه فعالیت") address: Optional[str] = Field(default=None, max_length=1000, description="آدرس") phone: Optional[str] = Field(default=None, max_length=20, description="تلفن ثابت") mobile: Optional[str] = Field(default=None, max_length=20, description="موبایل") national_id: Optional[str] = Field(default=None, max_length=20, description="کد ملی") registration_number: Optional[str] = Field(default=None, max_length=50, description="شماره ثبت") economic_id: Optional[str] = Field(default=None, max_length=50, description="شناسه اقتصادی") country: Optional[str] = Field(default=None, max_length=100, description="کشور") province: Optional[str] = Field(default=None, max_length=100, description="استان") city: Optional[str] = Field(default=None, max_length=100, description="شهر") postal_code: Optional[str] = Field(default=None, max_length=20, description="کد پستی") fiscal_years: Optional[List["FiscalYearCreate"]] = Field(default=None, description="آرایه سال‌های مالی برای ایجاد اولیه") class BusinessUpdateRequest(BaseModel): name: Optional[str] = Field(default=None, min_length=1, max_length=255, description="نام کسب و کار") business_type: Optional[BusinessType] = Field(default=None, description="نوع کسب و کار") business_field: Optional[BusinessField] = Field(default=None, description="زمینه فعالیت") address: Optional[str] = Field(default=None, max_length=1000, description="آدرس") phone: Optional[str] = Field(default=None, max_length=20, description="تلفن ثابت") mobile: Optional[str] = Field(default=None, max_length=20, description="موبایل") national_id: Optional[str] = Field(default=None, max_length=20, description="کد ملی") registration_number: Optional[str] = Field(default=None, max_length=50, description="شماره ثبت") economic_id: Optional[str] = Field(default=None, max_length=50, description="شناسه اقتصادی") country: Optional[str] = Field(default=None, max_length=100, description="کشور") province: Optional[str] = Field(default=None, max_length=100, description="استان") city: Optional[str] = Field(default=None, max_length=100, description="شهر") postal_code: Optional[str] = Field(default=None, max_length=20, description="کد پستی") class BusinessResponse(BaseModel): id: int = Field(..., description="شناسه کسب و کار") name: str = Field(..., description="نام کسب و کار") business_type: str = Field(..., description="نوع کسب و کار") business_field: str = Field(..., description="زمینه فعالیت") owner_id: int = Field(..., description="شناسه مالک") address: Optional[str] = Field(default=None, description="آدرس") phone: Optional[str] = Field(default=None, description="تلفن ثابت") mobile: Optional[str] = Field(default=None, description="موبایل") national_id: Optional[str] = Field(default=None, description="کد ملی") registration_number: Optional[str] = Field(default=None, description="شماره ثبت") economic_id: Optional[str] = Field(default=None, description="شناسه اقتصادی") country: Optional[str] = Field(default=None, description="کشور") province: Optional[str] = Field(default=None, description="استان") city: Optional[str] = Field(default=None, description="شهر") postal_code: Optional[str] = Field(default=None, description="کد پستی") created_at: str = Field(..., description="تاریخ ایجاد") updated_at: str = Field(..., description="تاریخ آخرین بروزرسانی") class BusinessListResponse(BaseModel): items: List[BusinessResponse] = Field(..., description="لیست کسب و کارها") pagination: PaginationInfo = Field(..., description="اطلاعات صفحه‌بندی") query_info: dict = Field(..., description="اطلاعات جستجو و فیلتر") class BusinessSummaryResponse(BaseModel): total_businesses: int = Field(..., description="تعداد کل کسب و کارها") by_type: dict = Field(..., description="تعداد بر اساس نوع") by_field: dict = Field(..., description="تعداد بر اساس زمینه فعالیت") class PaginatedResponse(BaseModel, Generic[T]): """پاسخ صفحه‌بندی شده برای لیست‌ها""" items: List[T] = Field(..., description="آیتم‌های صفحه") total: int = Field(..., description="تعداد کل آیتم‌ها") page: int = Field(..., description="شماره صفحه فعلی") limit: int = Field(..., description="تعداد آیتم در هر صفحه") total_pages: int = Field(..., description="تعداد کل صفحات") @classmethod def create(cls, items: List[T], total: int, page: int, limit: int) -> 'PaginatedResponse[T]': """ایجاد پاسخ صفحه‌بندی شده""" total_pages = (total + limit - 1) // limit return cls( items=items, total=total, page=page, limit=limit, total_pages=total_pages ) # Fiscal Year Schemas class FiscalYearCreate(BaseModel): title: str = Field(..., min_length=1, max_length=255, description="عنوان سال مالی") start_date: date = Field(..., description="تاریخ شروع سال مالی") end_date: date = Field(..., description="تاریخ پایان سال مالی") is_last: bool = Field(default=True, description="آیا آخرین سال مالی فعال است؟") # Business User Schemas class BusinessUserSchema(BaseModel): id: int business_id: int user_id: int user_name: str user_email: str user_phone: Optional[str] = None role: str status: str added_at: datetime last_active: Optional[datetime] = None permissions: dict class Config: from_attributes = True class AddUserRequest(BaseModel): email_or_phone: str class Config: json_schema_extra = { "example": { "email_or_phone": "user@example.com" } } class AddUserResponse(BaseModel): success: bool message: str user: Optional[BusinessUserSchema] = None class UpdatePermissionsRequest(BaseModel): permissions: dict class Config: json_schema_extra = { "example": { "permissions": { "sales": { "read": True, "write": True, "delete": False }, "reports": { "read": True, "export": True }, "settings": { "manage_users": True } } } } class UpdatePermissionsResponse(BaseModel): success: bool message: str class RemoveUserResponse(BaseModel): success: bool message: str class BusinessUsersListResponse(BaseModel): success: bool message: str data: dict calendar_type: Optional[str] = None