refactor: convert legacy models/schemas to re-exports
Legacy model and schema files now re-export from module locations for backwards compatibility: models/database/: - letzshop.py -> app.modules.marketplace.models - marketplace_import_job.py -> app.modules.marketplace.models - marketplace_product.py -> app.modules.marketplace.models - marketplace_product_translation.py -> app.modules.marketplace.models - subscription.py -> app.modules.billing.models - architecture_scan.py -> app.modules.dev_tools.models - test_run.py -> app.modules.dev_tools.models models/schema/: - marketplace_import_job.py -> app.modules.marketplace.schemas - marketplace_product.py -> app.modules.marketplace.schemas - subscription.py -> app.modules.billing.schemas - stats.py -> app.modules.analytics.schemas This maintains import compatibility while moving actual code to self-contained modules. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,167 +1,39 @@
|
||||
from datetime import datetime
|
||||
# models/schema/marketplace_import_job.py
|
||||
"""
|
||||
Legacy location for marketplace import job schemas.
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
MIGRATED: All schemas have been moved to app.modules.marketplace.schemas.marketplace_import_job.
|
||||
|
||||
|
||||
class MarketplaceImportJobRequest(BaseModel):
|
||||
"""Request schema for triggering marketplace import.
|
||||
|
||||
Note: vendor_id is injected by middleware, not from request body.
|
||||
"""
|
||||
|
||||
source_url: str = Field(..., description="URL to CSV file from marketplace")
|
||||
marketplace: str = Field(default="Letzshop", description="Marketplace name")
|
||||
batch_size: int | None = Field(
|
||||
1000, description="Processing batch size", ge=100, le=10000
|
||||
)
|
||||
language: str = Field(
|
||||
default="en",
|
||||
description="Language code for product translations (e.g., 'en', 'fr', 'de')",
|
||||
New location:
|
||||
from app.modules.marketplace.schemas import (
|
||||
MarketplaceImportJobRequest,
|
||||
MarketplaceImportJobResponse,
|
||||
)
|
||||
|
||||
@field_validator("source_url")
|
||||
@classmethod
|
||||
def validate_url(cls, v):
|
||||
if not v.startswith(("http://", "https://")): # noqa: SEC-034
|
||||
raise ValueError("URL must start with http:// or https://") # noqa: SEC-034
|
||||
return v.strip()
|
||||
This file re-exports from the new location for backward compatibility.
|
||||
"""
|
||||
|
||||
@field_validator("marketplace")
|
||||
@classmethod
|
||||
def validate_marketplace(cls, v):
|
||||
return v.strip()
|
||||
# Re-export everything from the new canonical location
|
||||
from app.modules.marketplace.schemas.marketplace_import_job import (
|
||||
MarketplaceImportJobRequest,
|
||||
AdminMarketplaceImportJobRequest,
|
||||
MarketplaceImportJobResponse,
|
||||
MarketplaceImportJobListResponse,
|
||||
MarketplaceImportErrorResponse,
|
||||
MarketplaceImportErrorListResponse,
|
||||
AdminMarketplaceImportJobResponse,
|
||||
AdminMarketplaceImportJobListResponse,
|
||||
MarketplaceImportJobStatusUpdate,
|
||||
)
|
||||
|
||||
@field_validator("language")
|
||||
@classmethod
|
||||
def validate_language(cls, v):
|
||||
# Basic language code validation (2-5 chars)
|
||||
v = v.strip().lower()
|
||||
if not 2 <= len(v) <= 5:
|
||||
raise ValueError("Language code must be 2-5 characters (e.g., 'en', 'fr')")
|
||||
return v
|
||||
|
||||
|
||||
class AdminMarketplaceImportJobRequest(BaseModel):
|
||||
"""Request schema for admin-triggered marketplace import.
|
||||
|
||||
Includes vendor_id since admin can import for any vendor.
|
||||
"""
|
||||
|
||||
vendor_id: int = Field(..., description="Vendor ID to import products for")
|
||||
source_url: str = Field(..., description="URL to CSV file from marketplace")
|
||||
marketplace: str = Field(default="Letzshop", description="Marketplace name")
|
||||
batch_size: int | None = Field(
|
||||
1000, description="Processing batch size", ge=100, le=10000
|
||||
)
|
||||
language: str = Field(
|
||||
default="en",
|
||||
description="Language code for product translations (e.g., 'en', 'fr', 'de')",
|
||||
)
|
||||
|
||||
@field_validator("source_url")
|
||||
@classmethod
|
||||
def validate_url(cls, v):
|
||||
if not v.startswith(("http://", "https://")): # noqa: SEC-034
|
||||
raise ValueError("URL must start with http:// or https://") # noqa: SEC-034
|
||||
return v.strip()
|
||||
|
||||
@field_validator("marketplace")
|
||||
@classmethod
|
||||
def validate_marketplace(cls, v):
|
||||
return v.strip()
|
||||
|
||||
@field_validator("language")
|
||||
@classmethod
|
||||
def validate_language(cls, v):
|
||||
v = v.strip().lower()
|
||||
if not 2 <= len(v) <= 5:
|
||||
raise ValueError("Language code must be 2-5 characters (e.g., 'en', 'fr')")
|
||||
return v
|
||||
|
||||
|
||||
class MarketplaceImportErrorResponse(BaseModel):
|
||||
"""Response schema for individual import error."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
row_number: int
|
||||
identifier: str | None = None
|
||||
error_type: str
|
||||
error_message: str
|
||||
row_data: dict | None = None
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class MarketplaceImportErrorListResponse(BaseModel):
|
||||
"""Response schema for list of import errors."""
|
||||
|
||||
errors: list[MarketplaceImportErrorResponse]
|
||||
total: int
|
||||
import_job_id: int
|
||||
|
||||
|
||||
class MarketplaceImportJobResponse(BaseModel):
|
||||
"""Response schema for marketplace import job."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
job_id: int
|
||||
vendor_id: int
|
||||
vendor_code: str | None = None # Populated from vendor relationship
|
||||
vendor_name: str | None = None # Populated from vendor relationship
|
||||
marketplace: str
|
||||
source_url: str
|
||||
status: str
|
||||
language: str | None = None # Language used for translations
|
||||
|
||||
# Counts
|
||||
imported: int = 0
|
||||
updated: int = 0
|
||||
total_processed: int = 0
|
||||
error_count: int = 0
|
||||
|
||||
# Error details
|
||||
error_message: str | None = None
|
||||
|
||||
# Timestamps
|
||||
created_at: datetime
|
||||
started_at: datetime | None = None
|
||||
completed_at: datetime | None = None
|
||||
|
||||
|
||||
class MarketplaceImportJobListResponse(BaseModel):
|
||||
"""Response schema for list of import jobs."""
|
||||
|
||||
jobs: list[MarketplaceImportJobResponse]
|
||||
total: int
|
||||
skip: int
|
||||
limit: int
|
||||
|
||||
|
||||
class AdminMarketplaceImportJobResponse(MarketplaceImportJobResponse):
|
||||
"""Extended response schema for admin with additional fields."""
|
||||
|
||||
id: int # Alias for job_id (frontend compatibility)
|
||||
error_details: list = [] # Placeholder for future error details
|
||||
created_by_name: str | None = None # Username of who created the job
|
||||
|
||||
|
||||
class AdminMarketplaceImportJobListResponse(BaseModel):
|
||||
"""Response schema for paginated list of import jobs (admin)."""
|
||||
|
||||
items: list[AdminMarketplaceImportJobResponse]
|
||||
total: int
|
||||
page: int
|
||||
limit: int
|
||||
|
||||
|
||||
class MarketplaceImportJobStatusUpdate(BaseModel):
|
||||
"""Schema for updating import job status (internal use)."""
|
||||
|
||||
status: str
|
||||
imported_count: int | None = None
|
||||
updated_count: int | None = None
|
||||
error_count: int | None = None
|
||||
total_processed: int | None = None
|
||||
error_message: str | None = None
|
||||
__all__ = [
|
||||
"MarketplaceImportJobRequest",
|
||||
"AdminMarketplaceImportJobRequest",
|
||||
"MarketplaceImportJobResponse",
|
||||
"MarketplaceImportJobListResponse",
|
||||
"MarketplaceImportErrorResponse",
|
||||
"MarketplaceImportErrorListResponse",
|
||||
"AdminMarketplaceImportJobResponse",
|
||||
"AdminMarketplaceImportJobListResponse",
|
||||
"MarketplaceImportJobStatusUpdate",
|
||||
]
|
||||
|
||||
@@ -1,225 +1,45 @@
|
||||
# models/schema/marketplace_product.py
|
||||
"""Pydantic schemas for MarketplaceProduct API validation.
|
||||
"""
|
||||
Legacy location for marketplace product schemas.
|
||||
|
||||
Note: title and description are stored in MarketplaceProductTranslation table,
|
||||
but we keep them in the API schemas for convenience. The service layer
|
||||
handles creating/updating translations separately.
|
||||
MIGRATED: All schemas have been moved to app.modules.marketplace.schemas.marketplace_product.
|
||||
|
||||
New location:
|
||||
from app.modules.marketplace.schemas import (
|
||||
MarketplaceProductCreate,
|
||||
MarketplaceProductResponse,
|
||||
MarketplaceProductTranslationSchema,
|
||||
)
|
||||
|
||||
This file re-exports from the new location for backward compatibility.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
# Re-export everything from the new canonical location
|
||||
from app.modules.marketplace.schemas.marketplace_product import (
|
||||
# Translation schemas
|
||||
MarketplaceProductTranslationSchema,
|
||||
# Base schemas
|
||||
MarketplaceProductBase,
|
||||
# CRUD schemas
|
||||
MarketplaceProductCreate,
|
||||
MarketplaceProductUpdate,
|
||||
# Response schemas
|
||||
MarketplaceProductResponse,
|
||||
MarketplaceProductListResponse,
|
||||
MarketplaceProductDetailResponse,
|
||||
# Import schemas
|
||||
MarketplaceImportRequest,
|
||||
MarketplaceImportResponse,
|
||||
)
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from models.schema.inventory import ProductInventorySummary
|
||||
|
||||
|
||||
class MarketplaceProductTranslationSchema(BaseModel):
|
||||
"""Schema for product translation."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
language: str
|
||||
title: str
|
||||
description: str | None = None
|
||||
short_description: str | None = None
|
||||
meta_title: str | None = None
|
||||
meta_description: str | None = None
|
||||
url_slug: str | None = None
|
||||
|
||||
|
||||
class MarketplaceProductBase(BaseModel):
|
||||
"""Base schema for marketplace products."""
|
||||
|
||||
marketplace_product_id: str | None = None
|
||||
|
||||
# Localized fields (passed to translations)
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
# Links and media
|
||||
link: str | None = None
|
||||
image_link: str | None = None
|
||||
additional_image_link: str | None = None
|
||||
|
||||
# Status
|
||||
availability: str | None = None
|
||||
is_active: bool | None = None
|
||||
|
||||
# Pricing
|
||||
price: str | None = None
|
||||
sale_price: str | None = None
|
||||
currency: str | None = None
|
||||
|
||||
# Product identifiers
|
||||
brand: str | None = None
|
||||
gtin: str | None = None
|
||||
mpn: str | None = None
|
||||
sku: str | None = None
|
||||
|
||||
# Product attributes
|
||||
condition: str | None = None
|
||||
adult: str | None = None
|
||||
multipack: int | None = None
|
||||
is_bundle: str | None = None
|
||||
age_group: str | None = None
|
||||
color: str | None = None
|
||||
gender: str | None = None
|
||||
material: str | None = None
|
||||
pattern: str | None = None
|
||||
size: str | None = None
|
||||
size_type: str | None = None
|
||||
size_system: str | None = None
|
||||
item_group_id: str | None = None
|
||||
|
||||
# Categories
|
||||
google_product_category: str | None = None
|
||||
product_type_raw: str | None = (
|
||||
None # Original feed value (renamed from product_type)
|
||||
)
|
||||
category_path: str | None = None
|
||||
|
||||
# Custom labels
|
||||
custom_label_0: str | None = None
|
||||
custom_label_1: str | None = None
|
||||
custom_label_2: str | None = None
|
||||
custom_label_3: str | None = None
|
||||
custom_label_4: str | None = None
|
||||
|
||||
# Unit pricing
|
||||
unit_pricing_measure: str | None = None
|
||||
unit_pricing_base_measure: str | None = None
|
||||
identifier_exists: str | None = None
|
||||
shipping: str | None = None
|
||||
|
||||
# Source tracking
|
||||
marketplace: str | None = None
|
||||
vendor_name: str | None = None
|
||||
source_url: str | None = None
|
||||
|
||||
# Product type classification
|
||||
product_type_enum: str | None = (
|
||||
None # 'physical', 'digital', 'service', 'subscription'
|
||||
)
|
||||
is_digital: bool | None = None
|
||||
|
||||
# Digital product fields
|
||||
digital_delivery_method: str | None = None
|
||||
platform: str | None = None
|
||||
license_type: str | None = None
|
||||
|
||||
# Physical product fields
|
||||
weight: float | None = None
|
||||
weight_unit: str | None = None
|
||||
|
||||
|
||||
class MarketplaceProductCreate(MarketplaceProductBase):
|
||||
"""Schema for creating a marketplace product."""
|
||||
|
||||
marketplace_product_id: str = Field(
|
||||
..., description="Unique product identifier from marketplace"
|
||||
)
|
||||
# Title is required for API creation (will be stored in translations)
|
||||
title: str = Field(..., description="Product title")
|
||||
|
||||
|
||||
class MarketplaceProductUpdate(MarketplaceProductBase):
|
||||
"""Schema for updating a marketplace product.
|
||||
|
||||
All fields are optional - only provided fields will be updated.
|
||||
"""
|
||||
|
||||
|
||||
class MarketplaceProductResponse(BaseModel):
|
||||
"""Schema for marketplace product API response."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
marketplace_product_id: str
|
||||
|
||||
# These will be populated from translations
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
# Links and media
|
||||
link: str | None = None
|
||||
image_link: str | None = None
|
||||
additional_image_link: str | None = None
|
||||
|
||||
# Status
|
||||
availability: str | None = None
|
||||
is_active: bool | None = None
|
||||
|
||||
# Pricing
|
||||
price: str | None = None
|
||||
price_numeric: float | None = None
|
||||
sale_price: str | None = None
|
||||
sale_price_numeric: float | None = None
|
||||
currency: str | None = None
|
||||
|
||||
# Product identifiers
|
||||
brand: str | None = None
|
||||
gtin: str | None = None
|
||||
mpn: str | None = None
|
||||
sku: str | None = None
|
||||
|
||||
# Product attributes
|
||||
condition: str | None = None
|
||||
color: str | None = None
|
||||
size: str | None = None
|
||||
|
||||
# Categories
|
||||
google_product_category: str | None = None
|
||||
product_type_raw: str | None = None
|
||||
category_path: str | None = None
|
||||
|
||||
# Source tracking
|
||||
marketplace: str | None = None
|
||||
vendor_name: str | None = None
|
||||
|
||||
# Product type
|
||||
product_type_enum: str | None = None
|
||||
is_digital: bool | None = None
|
||||
platform: str | None = None
|
||||
|
||||
# Timestamps
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Translations (optional - included when requested)
|
||||
translations: list[MarketplaceProductTranslationSchema] | None = None
|
||||
|
||||
|
||||
class MarketplaceProductListResponse(BaseModel):
|
||||
"""Schema for paginated product list response."""
|
||||
|
||||
products: list[MarketplaceProductResponse]
|
||||
total: int
|
||||
skip: int
|
||||
limit: int
|
||||
|
||||
|
||||
class MarketplaceProductDetailResponse(BaseModel):
|
||||
"""Schema for detailed product response with inventory."""
|
||||
|
||||
product: MarketplaceProductResponse
|
||||
inventory_info: ProductInventorySummary | None = None
|
||||
translations: list[MarketplaceProductTranslationSchema] | None = None
|
||||
|
||||
|
||||
class MarketplaceImportRequest(BaseModel):
|
||||
"""Schema for marketplace import request."""
|
||||
|
||||
url: str = Field(..., description="URL to CSV file")
|
||||
marketplace: str = Field(default="Letzshop", description="Marketplace name")
|
||||
vendor_name: str | None = Field(default=None, description="Vendor name")
|
||||
language: str = Field(default="en", description="Language code for translations")
|
||||
batch_size: int = Field(default=100, ge=1, le=1000, description="Batch size")
|
||||
|
||||
|
||||
class MarketplaceImportResponse(BaseModel):
|
||||
"""Schema for marketplace import response."""
|
||||
|
||||
job_id: int
|
||||
status: str
|
||||
message: str
|
||||
__all__ = [
|
||||
"MarketplaceProductTranslationSchema",
|
||||
"MarketplaceProductBase",
|
||||
"MarketplaceProductCreate",
|
||||
"MarketplaceProductUpdate",
|
||||
"MarketplaceProductResponse",
|
||||
"MarketplaceProductListResponse",
|
||||
"MarketplaceProductDetailResponse",
|
||||
"MarketplaceImportRequest",
|
||||
"MarketplaceImportResponse",
|
||||
]
|
||||
|
||||
@@ -1,319 +1,63 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class StatsResponse(BaseModel):
|
||||
"""Comprehensive platform statistics response schema."""
|
||||
|
||||
total_products: int
|
||||
unique_brands: int
|
||||
unique_categories: int
|
||||
unique_marketplaces: int = 0
|
||||
unique_vendors: int = 0
|
||||
total_inventory_entries: int = 0
|
||||
total_inventory_quantity: int = 0
|
||||
|
||||
|
||||
class MarketplaceStatsResponse(BaseModel):
|
||||
"""Statistics per marketplace response schema."""
|
||||
|
||||
marketplace: str
|
||||
total_products: int
|
||||
unique_vendors: int
|
||||
unique_brands: int
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Import Statistics
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class ImportStatsResponse(BaseModel):
|
||||
"""Import job statistics response schema.
|
||||
|
||||
Used by: GET /api/v1/admin/marketplace-import-jobs/stats
|
||||
"""
|
||||
|
||||
total: int = Field(..., description="Total number of import jobs")
|
||||
pending: int = Field(..., description="Jobs waiting to start")
|
||||
processing: int = Field(..., description="Jobs currently running")
|
||||
completed: int = Field(..., description="Successfully completed jobs")
|
||||
failed: int = Field(..., description="Failed jobs")
|
||||
success_rate: float = Field(..., description="Percentage of successful imports")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# User Statistics
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class UserStatsResponse(BaseModel):
|
||||
"""User statistics response schema.
|
||||
|
||||
Used by: Platform statistics endpoints
|
||||
"""
|
||||
|
||||
total_users: int = Field(..., description="Total number of users")
|
||||
active_users: int = Field(..., description="Number of active users")
|
||||
inactive_users: int = Field(..., description="Number of inactive users")
|
||||
admin_users: int = Field(..., description="Number of admin users")
|
||||
activation_rate: float = Field(..., description="Percentage of active users")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Vendor Statistics (Admin)
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class VendorStatsResponse(BaseModel):
|
||||
"""Vendor statistics response schema for admin dashboard.
|
||||
|
||||
Used by: GET /api/v1/admin/vendors/stats
|
||||
"""
|
||||
|
||||
total: int = Field(..., description="Total number of vendors")
|
||||
verified: int = Field(..., description="Number of verified vendors")
|
||||
pending: int = Field(..., description="Number of pending verification vendors")
|
||||
inactive: int = Field(..., description="Number of inactive vendors")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Product Statistics
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class ProductStatsResponse(BaseModel):
|
||||
"""Product statistics response schema.
|
||||
|
||||
Used by: Platform statistics endpoints
|
||||
"""
|
||||
|
||||
total_products: int = Field(0, description="Total number of products")
|
||||
active_products: int = Field(0, description="Number of active products")
|
||||
out_of_stock: int = Field(0, description="Number of out-of-stock products")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Platform Statistics (Combined)
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class PlatformStatsResponse(BaseModel):
|
||||
"""Combined platform statistics response schema.
|
||||
|
||||
Used by: GET /api/v1/admin/dashboard/stats/platform
|
||||
"""
|
||||
|
||||
users: UserStatsResponse
|
||||
vendors: VendorStatsResponse
|
||||
products: ProductStatsResponse
|
||||
orders: "OrderStatsBasicResponse"
|
||||
imports: ImportStatsResponse
|
||||
|
||||
|
||||
class OrderStatsBasicResponse(BaseModel):
|
||||
"""Basic order statistics (stub until Order model is fully implemented).
|
||||
|
||||
Used by: Platform statistics endpoints
|
||||
"""
|
||||
|
||||
total_orders: int = Field(0, description="Total number of orders")
|
||||
pending_orders: int = Field(0, description="Number of pending orders")
|
||||
completed_orders: int = Field(0, description="Number of completed orders")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Admin Dashboard Response
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class AdminDashboardResponse(BaseModel):
|
||||
"""Admin dashboard response schema.
|
||||
|
||||
Used by: GET /api/v1/admin/dashboard
|
||||
"""
|
||||
|
||||
platform: dict[str, Any] = Field(..., description="Platform information")
|
||||
users: UserStatsResponse
|
||||
vendors: VendorStatsResponse
|
||||
recent_vendors: list[dict[str, Any]] = Field(
|
||||
default_factory=list, description="Recent vendors"
|
||||
)
|
||||
recent_imports: list[dict[str, Any]] = Field(
|
||||
default_factory=list, description="Recent import jobs"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Vendor Dashboard Statistics
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class VendorProductStats(BaseModel):
|
||||
"""Vendor product statistics."""
|
||||
|
||||
total: int = Field(0, description="Total products in catalog")
|
||||
active: int = Field(0, description="Active products")
|
||||
|
||||
|
||||
class VendorOrderStats(BaseModel):
|
||||
"""Vendor order statistics."""
|
||||
|
||||
total: int = Field(0, description="Total orders")
|
||||
pending: int = Field(0, description="Pending orders")
|
||||
completed: int = Field(0, description="Completed orders")
|
||||
|
||||
|
||||
class VendorCustomerStats(BaseModel):
|
||||
"""Vendor customer statistics."""
|
||||
|
||||
total: int = Field(0, description="Total customers")
|
||||
active: int = Field(0, description="Active customers")
|
||||
|
||||
|
||||
class VendorRevenueStats(BaseModel):
|
||||
"""Vendor revenue statistics."""
|
||||
|
||||
total: float = Field(0, description="Total revenue")
|
||||
this_month: float = Field(0, description="Revenue this month")
|
||||
|
||||
|
||||
class VendorInfo(BaseModel):
|
||||
"""Vendor basic info for dashboard."""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
vendor_code: str
|
||||
|
||||
|
||||
class VendorDashboardStatsResponse(BaseModel):
|
||||
"""Vendor dashboard statistics response schema.
|
||||
|
||||
Used by: GET /api/v1/vendor/dashboard/stats
|
||||
"""
|
||||
|
||||
vendor: VendorInfo
|
||||
products: VendorProductStats
|
||||
orders: VendorOrderStats
|
||||
customers: VendorCustomerStats
|
||||
revenue: VendorRevenueStats
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Vendor Analytics
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class VendorAnalyticsImports(BaseModel):
|
||||
"""Vendor import analytics."""
|
||||
|
||||
count: int = Field(0, description="Number of imports in period")
|
||||
|
||||
|
||||
class VendorAnalyticsCatalog(BaseModel):
|
||||
"""Vendor catalog analytics."""
|
||||
|
||||
products_added: int = Field(0, description="Products added in period")
|
||||
|
||||
|
||||
class VendorAnalyticsInventory(BaseModel):
|
||||
"""Vendor inventory analytics."""
|
||||
|
||||
total_locations: int = Field(0, description="Total inventory locations")
|
||||
|
||||
|
||||
class VendorAnalyticsResponse(BaseModel):
|
||||
"""Vendor analytics response schema.
|
||||
|
||||
Used by: GET /api/v1/vendor/analytics
|
||||
"""
|
||||
|
||||
period: str = Field(..., description="Analytics period (e.g., '30d')")
|
||||
start_date: str = Field(..., description="Period start date")
|
||||
imports: VendorAnalyticsImports
|
||||
catalog: VendorAnalyticsCatalog
|
||||
inventory: VendorAnalyticsInventory
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Code Quality Dashboard Statistics
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class ValidatorStats(BaseModel):
|
||||
"""Statistics for a single validator type."""
|
||||
|
||||
total_violations: int = 0
|
||||
errors: int = 0
|
||||
warnings: int = 0
|
||||
last_scan: str | None = None
|
||||
|
||||
|
||||
class CodeQualityDashboardStatsResponse(BaseModel):
|
||||
"""Code quality dashboard statistics response schema.
|
||||
|
||||
Used by: GET /api/v1/admin/code-quality/stats
|
||||
|
||||
Supports multiple validator types: architecture, security, performance.
|
||||
When validator_type is specified, returns stats for that type only.
|
||||
When not specified, returns combined stats with per-validator breakdown.
|
||||
"""
|
||||
|
||||
total_violations: int
|
||||
errors: int
|
||||
warnings: int
|
||||
info: int = 0
|
||||
open: int
|
||||
assigned: int
|
||||
resolved: int
|
||||
ignored: int
|
||||
technical_debt_score: int
|
||||
trend: list[dict[str, Any]] = Field(default_factory=list)
|
||||
by_severity: dict[str, Any] = Field(default_factory=dict)
|
||||
by_rule: dict[str, Any] = Field(default_factory=dict)
|
||||
by_module: dict[str, Any] = Field(default_factory=dict)
|
||||
top_files: list[dict[str, Any]] = Field(default_factory=list)
|
||||
last_scan: str | None = None
|
||||
validator_type: str | None = None # Set when filtering by type
|
||||
by_validator: dict[str, ValidatorStats] = Field(
|
||||
default_factory=dict,
|
||||
description="Per-validator breakdown (architecture, security, performance)",
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Customer Statistics (Coming Soon)
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class CustomerStatsResponse(BaseModel):
|
||||
"""Schema for customer statistics."""
|
||||
|
||||
customer_id: int
|
||||
total_orders: int
|
||||
total_spent: Decimal
|
||||
average_order_value: Decimal
|
||||
last_order_date: datetime | None
|
||||
first_order_date: datetime | None
|
||||
lifetime_value: Decimal
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Order Statistics (Coming Soon)
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class OrderStatsResponse(BaseModel):
|
||||
"""Schema for order statistics."""
|
||||
|
||||
total_orders: int
|
||||
pending_orders: int
|
||||
processing_orders: int
|
||||
shipped_orders: int
|
||||
delivered_orders: int
|
||||
cancelled_orders: int
|
||||
total_revenue: Decimal
|
||||
average_order_value: Decimal
|
||||
# models/schema/stats.py
|
||||
"""
|
||||
Statistics schemas - LEGACY LOCATION
|
||||
|
||||
This file exists for backward compatibility.
|
||||
The canonical location is now: app/modules/analytics/schemas/stats.py
|
||||
|
||||
All imports should use the new location:
|
||||
from app.modules.analytics.schemas import StatsResponse, ...
|
||||
"""
|
||||
|
||||
# Re-export from canonical location for backward compatibility
|
||||
from app.modules.analytics.schemas.stats import (
|
||||
StatsResponse,
|
||||
MarketplaceStatsResponse,
|
||||
ImportStatsResponse,
|
||||
UserStatsResponse,
|
||||
VendorStatsResponse,
|
||||
ProductStatsResponse,
|
||||
PlatformStatsResponse,
|
||||
OrderStatsBasicResponse,
|
||||
AdminDashboardResponse,
|
||||
VendorProductStats,
|
||||
VendorOrderStats,
|
||||
VendorCustomerStats,
|
||||
VendorRevenueStats,
|
||||
VendorInfo,
|
||||
VendorDashboardStatsResponse,
|
||||
VendorAnalyticsImports,
|
||||
VendorAnalyticsCatalog,
|
||||
VendorAnalyticsInventory,
|
||||
VendorAnalyticsResponse,
|
||||
ValidatorStats,
|
||||
CodeQualityDashboardStatsResponse,
|
||||
CustomerStatsResponse,
|
||||
OrderStatsResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"StatsResponse",
|
||||
"MarketplaceStatsResponse",
|
||||
"ImportStatsResponse",
|
||||
"UserStatsResponse",
|
||||
"VendorStatsResponse",
|
||||
"ProductStatsResponse",
|
||||
"PlatformStatsResponse",
|
||||
"OrderStatsBasicResponse",
|
||||
"AdminDashboardResponse",
|
||||
"VendorProductStats",
|
||||
"VendorOrderStats",
|
||||
"VendorCustomerStats",
|
||||
"VendorRevenueStats",
|
||||
"VendorInfo",
|
||||
"VendorDashboardStatsResponse",
|
||||
"VendorAnalyticsImports",
|
||||
"VendorAnalyticsCatalog",
|
||||
"VendorAnalyticsInventory",
|
||||
"VendorAnalyticsResponse",
|
||||
"ValidatorStats",
|
||||
"CodeQualityDashboardStatsResponse",
|
||||
"CustomerStatsResponse",
|
||||
"OrderStatsResponse",
|
||||
]
|
||||
|
||||
@@ -1,209 +1,58 @@
|
||||
# models/schema/subscription.py
|
||||
"""
|
||||
Pydantic schemas for subscription operations.
|
||||
Legacy location for subscription schemas.
|
||||
|
||||
Supports subscription management and tier limit checks.
|
||||
MIGRATED: All schemas have been moved to app.modules.billing.schemas.subscription.
|
||||
|
||||
New location:
|
||||
from app.modules.billing.schemas import (
|
||||
SubscriptionCreate,
|
||||
SubscriptionResponse,
|
||||
TierInfo,
|
||||
)
|
||||
|
||||
This file re-exports from the new location for backward compatibility.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
# Re-export everything from the new canonical location
|
||||
from app.modules.billing.schemas.subscription import (
|
||||
# Tier schemas
|
||||
TierFeatures,
|
||||
TierLimits,
|
||||
TierInfo,
|
||||
# Subscription CRUD schemas
|
||||
SubscriptionCreate,
|
||||
SubscriptionUpdate,
|
||||
SubscriptionResponse,
|
||||
# Usage schemas
|
||||
SubscriptionUsage,
|
||||
UsageSummary,
|
||||
SubscriptionStatusResponse,
|
||||
# Limit check schemas
|
||||
LimitCheckResult,
|
||||
CanCreateOrderResponse,
|
||||
CanAddProductResponse,
|
||||
CanAddTeamMemberResponse,
|
||||
FeatureCheckResponse,
|
||||
)
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Tier Information Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TierFeatures(BaseModel):
|
||||
"""Features included in a tier."""
|
||||
|
||||
letzshop_sync: bool = True
|
||||
inventory_basic: bool = True
|
||||
inventory_locations: bool = False
|
||||
inventory_purchase_orders: bool = False
|
||||
invoice_lu: bool = True
|
||||
invoice_eu_vat: bool = False
|
||||
invoice_bulk: bool = False
|
||||
customer_view: bool = True
|
||||
customer_export: bool = False
|
||||
analytics_dashboard: bool = False
|
||||
accounting_export: bool = False
|
||||
api_access: bool = False
|
||||
automation_rules: bool = False
|
||||
team_roles: bool = False
|
||||
white_label: bool = False
|
||||
multi_vendor: bool = False
|
||||
custom_integrations: bool = False
|
||||
sla_guarantee: bool = False
|
||||
dedicated_support: bool = False
|
||||
|
||||
|
||||
class TierLimits(BaseModel):
|
||||
"""Limits for a subscription tier."""
|
||||
|
||||
orders_per_month: int | None = Field(None, description="None = unlimited")
|
||||
products_limit: int | None = Field(None, description="None = unlimited")
|
||||
team_members: int | None = Field(None, description="None = unlimited")
|
||||
order_history_months: int | None = Field(None, description="None = unlimited")
|
||||
|
||||
|
||||
class TierInfo(BaseModel):
|
||||
"""Full tier information."""
|
||||
|
||||
code: str
|
||||
name: str
|
||||
price_monthly_cents: int
|
||||
price_annual_cents: int | None
|
||||
limits: TierLimits
|
||||
features: list[str]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Subscription Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class SubscriptionCreate(BaseModel):
|
||||
"""Schema for creating a subscription (admin/internal use)."""
|
||||
|
||||
tier: str = Field(default="essential", pattern="^(essential|professional|business|enterprise)$")
|
||||
is_annual: bool = False
|
||||
trial_days: int = Field(default=14, ge=0, le=30)
|
||||
|
||||
|
||||
class SubscriptionUpdate(BaseModel):
|
||||
"""Schema for updating a subscription."""
|
||||
|
||||
tier: str | None = Field(None, pattern="^(essential|professional|business|enterprise)$")
|
||||
status: str | None = Field(None, pattern="^(trial|active|past_due|cancelled|expired)$")
|
||||
is_annual: bool | None = None
|
||||
custom_orders_limit: int | None = None
|
||||
custom_products_limit: int | None = None
|
||||
custom_team_limit: int | None = None
|
||||
|
||||
|
||||
class SubscriptionResponse(BaseModel):
|
||||
"""Schema for subscription response."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
vendor_id: int
|
||||
tier: str
|
||||
status: str
|
||||
|
||||
period_start: datetime
|
||||
period_end: datetime
|
||||
is_annual: bool
|
||||
|
||||
trial_ends_at: datetime | None
|
||||
orders_this_period: int
|
||||
orders_limit_reached_at: datetime | None
|
||||
|
||||
# Effective limits (with custom overrides applied)
|
||||
orders_limit: int | None
|
||||
products_limit: int | None
|
||||
team_members_limit: int | None
|
||||
|
||||
# Computed properties
|
||||
is_active: bool
|
||||
is_trial: bool
|
||||
trial_days_remaining: int | None
|
||||
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class SubscriptionUsage(BaseModel):
|
||||
"""Current subscription usage statistics."""
|
||||
|
||||
orders_used: int
|
||||
orders_limit: int | None
|
||||
orders_remaining: int | None
|
||||
orders_percent_used: float | None
|
||||
|
||||
products_used: int
|
||||
products_limit: int | None
|
||||
products_remaining: int | None
|
||||
products_percent_used: float | None
|
||||
|
||||
team_members_used: int
|
||||
team_members_limit: int | None
|
||||
team_members_remaining: int | None
|
||||
team_members_percent_used: float | None
|
||||
|
||||
|
||||
class UsageSummary(BaseModel):
|
||||
"""Usage summary for billing page display."""
|
||||
|
||||
orders_this_period: int
|
||||
orders_limit: int | None
|
||||
orders_remaining: int | None
|
||||
|
||||
products_count: int
|
||||
products_limit: int | None
|
||||
products_remaining: int | None
|
||||
|
||||
team_count: int
|
||||
team_limit: int | None
|
||||
team_remaining: int | None
|
||||
|
||||
|
||||
class SubscriptionStatusResponse(BaseModel):
|
||||
"""Subscription status with usage and limits."""
|
||||
|
||||
subscription: SubscriptionResponse
|
||||
usage: SubscriptionUsage
|
||||
tier_info: TierInfo
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Limit Check Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class LimitCheckResult(BaseModel):
|
||||
"""Result of a limit check."""
|
||||
|
||||
allowed: bool
|
||||
limit: int | None
|
||||
current: int
|
||||
remaining: int | None
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class CanCreateOrderResponse(BaseModel):
|
||||
"""Response for order creation check."""
|
||||
|
||||
allowed: bool
|
||||
orders_this_period: int
|
||||
orders_limit: int | None
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class CanAddProductResponse(BaseModel):
|
||||
"""Response for product addition check."""
|
||||
|
||||
allowed: bool
|
||||
products_count: int
|
||||
products_limit: int | None
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class CanAddTeamMemberResponse(BaseModel):
|
||||
"""Response for team member addition check."""
|
||||
|
||||
allowed: bool
|
||||
team_count: int
|
||||
team_limit: int | None
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class FeatureCheckResponse(BaseModel):
|
||||
"""Response for feature check."""
|
||||
|
||||
feature: str
|
||||
enabled: bool
|
||||
tier_required: str | None = None
|
||||
message: str | None = None
|
||||
__all__ = [
|
||||
# Tier schemas
|
||||
"TierFeatures",
|
||||
"TierLimits",
|
||||
"TierInfo",
|
||||
# Subscription CRUD schemas
|
||||
"SubscriptionCreate",
|
||||
"SubscriptionUpdate",
|
||||
"SubscriptionResponse",
|
||||
# Usage schemas
|
||||
"SubscriptionUsage",
|
||||
"UsageSummary",
|
||||
"SubscriptionStatusResponse",
|
||||
# Limit check schemas
|
||||
"LimitCheckResult",
|
||||
"CanCreateOrderResponse",
|
||||
"CanAddProductResponse",
|
||||
"CanAddTeamMemberResponse",
|
||||
"FeatureCheckResponse",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user