refactor: clean up legacy models and migrate remaining schemas
Delete empty stub files from models/database/: - audit.py, backup.py, configuration.py, monitoring.py - notification.py, payment.py, search.py, task.py Delete re-export files: - models/database/subscription.py → app.modules.billing.models - models/database/architecture_scan.py → app.modules.dev_tools.models - models/database/test_run.py → app.modules.dev_tools.models - models/schema/subscription.py → app.modules.billing.schemas - models/schema/marketplace.py (empty) - models/schema/monitoring.py (empty) Migrate schemas to canonical module locations: - billing.py → app/modules/billing/schemas/ - vendor_product.py → app/modules/catalog/schemas/ - homepage_sections.py → app/modules/cms/schemas/ Keep as CORE (framework-level, used everywhere): - models/schema/: admin, auth, base, company, email, image, media, team, vendor* - models/database/: admin*, base, company, email, feature, media, platform*, user, vendor* Update 30+ files to use canonical import locations. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,7 @@ from .admin import (
|
||||
from app.modules.messaging.models import AdminNotification
|
||||
from .admin_menu_config import AdminMenuConfig, FrontendType, MANDATORY_MENU_ITEMS
|
||||
from .admin_platform import AdminPlatform
|
||||
from .architecture_scan import (
|
||||
from app.modules.dev_tools.models import (
|
||||
ArchitectureScan,
|
||||
ArchitectureViolation,
|
||||
ViolationAssignment,
|
||||
@@ -82,7 +82,7 @@ from app.modules.marketplace.models import OnboardingStatus, OnboardingStep, Ven
|
||||
from app.modules.orders.models import Order, OrderItem
|
||||
from app.modules.orders.models import OrderItemException
|
||||
from app.modules.catalog.models import Product, ProductTranslation
|
||||
from .subscription import (
|
||||
from app.modules.billing.models import (
|
||||
AddOnCategory,
|
||||
AddOnProduct,
|
||||
BillingHistory,
|
||||
@@ -95,7 +95,7 @@ from .subscription import (
|
||||
VendorAddOn,
|
||||
VendorSubscription,
|
||||
)
|
||||
from .test_run import TestCollection, TestResult, TestRun
|
||||
from app.modules.dev_tools.models import TestCollection, TestResult, TestRun
|
||||
from .user import User
|
||||
from .vendor import Role, Vendor, VendorUser
|
||||
from .vendor_domain import VendorDomain
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# models/database/architecture_scan.py
|
||||
"""
|
||||
Architecture Scan Models - LEGACY LOCATION
|
||||
|
||||
This file exists for backward compatibility.
|
||||
The canonical location is now: app/modules/dev_tools/models/architecture_scan.py
|
||||
|
||||
All imports should use the new location:
|
||||
from app.modules.dev_tools.models import ArchitectureScan, ArchitectureViolation, ...
|
||||
"""
|
||||
|
||||
# Re-export from canonical location for backward compatibility
|
||||
from app.modules.dev_tools.models.architecture_scan import (
|
||||
ArchitectureScan,
|
||||
ArchitectureViolation,
|
||||
ArchitectureRule,
|
||||
ViolationAssignment,
|
||||
ViolationComment,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ArchitectureScan",
|
||||
"ArchitectureViolation",
|
||||
"ArchitectureRule",
|
||||
"ViolationAssignment",
|
||||
"ViolationComment",
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
# AuditLog, DataExportLog models
|
||||
@@ -1 +0,0 @@
|
||||
# BackupLog, RestoreLog models
|
||||
@@ -1 +0,0 @@
|
||||
# PlatformConfig, VendorConfig, FeatureFlag models
|
||||
@@ -1 +0,0 @@
|
||||
# PerformanceMetric, ErrorLog, SystemAlert models
|
||||
@@ -1 +0,0 @@
|
||||
# NotificationTemplate, NotificationQueue, NotificationLog models
|
||||
@@ -1 +0,0 @@
|
||||
# Payment, PaymentMethod, VendorPaymentConfig models
|
||||
@@ -1 +0,0 @@
|
||||
# SearchIndex, SearchQuery models
|
||||
@@ -1,53 +0,0 @@
|
||||
# models/database/subscription.py
|
||||
"""
|
||||
Legacy location for subscription models.
|
||||
|
||||
MIGRATED: All models have been moved to app.modules.billing.models.subscription.
|
||||
|
||||
New location:
|
||||
from app.modules.billing.models import (
|
||||
VendorSubscription,
|
||||
SubscriptionTier,
|
||||
TierCode,
|
||||
SubscriptionStatus,
|
||||
)
|
||||
|
||||
This file re-exports from the new location for backward compatibility.
|
||||
"""
|
||||
|
||||
# Re-export everything from the new canonical location
|
||||
from app.modules.billing.models.subscription import (
|
||||
# Enums
|
||||
TierCode,
|
||||
SubscriptionStatus,
|
||||
AddOnCategory,
|
||||
BillingPeriod,
|
||||
# Models
|
||||
SubscriptionTier,
|
||||
AddOnProduct,
|
||||
VendorAddOn,
|
||||
StripeWebhookEvent,
|
||||
BillingHistory,
|
||||
VendorSubscription,
|
||||
CapacitySnapshot,
|
||||
# Legacy constants
|
||||
TIER_LIMITS,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Enums
|
||||
"TierCode",
|
||||
"SubscriptionStatus",
|
||||
"AddOnCategory",
|
||||
"BillingPeriod",
|
||||
# Models
|
||||
"SubscriptionTier",
|
||||
"AddOnProduct",
|
||||
"VendorAddOn",
|
||||
"StripeWebhookEvent",
|
||||
"BillingHistory",
|
||||
"VendorSubscription",
|
||||
"CapacitySnapshot",
|
||||
# Legacy constants
|
||||
"TIER_LIMITS",
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
# TaskLog model
|
||||
@@ -1,23 +0,0 @@
|
||||
# models/database/test_run.py
|
||||
"""
|
||||
Test Run Models - LEGACY LOCATION
|
||||
|
||||
This file exists for backward compatibility.
|
||||
The canonical location is now: app/modules/dev_tools/models/test_run.py
|
||||
|
||||
All imports should use the new location:
|
||||
from app.modules.dev_tools.models import TestRun, TestResult, TestCollection
|
||||
"""
|
||||
|
||||
# Re-export from canonical location for backward compatibility
|
||||
from app.modules.dev_tools.models.test_run import (
|
||||
TestRun,
|
||||
TestResult,
|
||||
TestCollection,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"TestRun",
|
||||
"TestResult",
|
||||
"TestCollection",
|
||||
]
|
||||
@@ -1,300 +0,0 @@
|
||||
# models/schema/billing.py
|
||||
"""
|
||||
Pydantic schemas for billing and subscription operations.
|
||||
|
||||
Used for both vendor billing endpoints and admin subscription management.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Subscription Tier Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class SubscriptionTierBase(BaseModel):
|
||||
"""Base schema for subscription tier."""
|
||||
|
||||
code: str = Field(..., min_length=1, max_length=30)
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
description: str | None = None
|
||||
price_monthly_cents: int = Field(..., ge=0)
|
||||
price_annual_cents: int | None = Field(None, ge=0)
|
||||
orders_per_month: int | None = Field(None, ge=0)
|
||||
products_limit: int | None = Field(None, ge=0)
|
||||
team_members: int | None = Field(None, ge=0)
|
||||
order_history_months: int | None = Field(None, ge=0)
|
||||
features: list[str] = Field(default_factory=list)
|
||||
stripe_product_id: str | None = None
|
||||
stripe_price_monthly_id: str | None = None
|
||||
stripe_price_annual_id: str | None = None
|
||||
display_order: int = 0
|
||||
is_active: bool = True
|
||||
is_public: bool = True
|
||||
|
||||
|
||||
class SubscriptionTierCreate(SubscriptionTierBase):
|
||||
"""Schema for creating a subscription tier."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class SubscriptionTierUpdate(BaseModel):
|
||||
"""Schema for updating a subscription tier."""
|
||||
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
price_monthly_cents: int | None = Field(None, ge=0)
|
||||
price_annual_cents: int | None = Field(None, ge=0)
|
||||
orders_per_month: int | None = None
|
||||
products_limit: int | None = None
|
||||
team_members: int | None = None
|
||||
order_history_months: int | None = None
|
||||
features: list[str] | None = None
|
||||
stripe_product_id: str | None = None
|
||||
stripe_price_monthly_id: str | None = None
|
||||
stripe_price_annual_id: str | None = None
|
||||
display_order: int | None = None
|
||||
is_active: bool | None = None
|
||||
is_public: bool | None = None
|
||||
|
||||
|
||||
class SubscriptionTierResponse(SubscriptionTierBase):
|
||||
"""Schema for subscription tier response."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Computed fields for display
|
||||
@property
|
||||
def price_monthly_display(self) -> str:
|
||||
"""Format monthly price for display."""
|
||||
return f"€{self.price_monthly_cents / 100:.2f}"
|
||||
|
||||
@property
|
||||
def price_annual_display(self) -> str | None:
|
||||
"""Format annual price for display."""
|
||||
if self.price_annual_cents is None:
|
||||
return None
|
||||
return f"€{self.price_annual_cents / 100:.2f}"
|
||||
|
||||
|
||||
class SubscriptionTierListResponse(BaseModel):
|
||||
"""Response for listing subscription tiers."""
|
||||
|
||||
tiers: list[SubscriptionTierResponse]
|
||||
total: int
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Vendor Subscription Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class VendorSubscriptionResponse(BaseModel):
|
||||
"""Schema for vendor subscription response."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
vendor_id: int
|
||||
tier: str
|
||||
status: str
|
||||
|
||||
# Period info
|
||||
period_start: datetime
|
||||
period_end: datetime
|
||||
is_annual: bool
|
||||
trial_ends_at: datetime | None = None
|
||||
|
||||
# Usage
|
||||
orders_this_period: int
|
||||
orders_limit_reached_at: datetime | None = None
|
||||
|
||||
# Limits (effective)
|
||||
orders_limit: int | None = None
|
||||
products_limit: int | None = None
|
||||
team_members_limit: int | None = None
|
||||
|
||||
# Custom overrides
|
||||
custom_orders_limit: int | None = None
|
||||
custom_products_limit: int | None = None
|
||||
custom_team_limit: int | None = None
|
||||
|
||||
# Stripe
|
||||
stripe_customer_id: str | None = None
|
||||
stripe_subscription_id: str | None = None
|
||||
|
||||
# Cancellation
|
||||
cancelled_at: datetime | None = None
|
||||
cancellation_reason: str | None = None
|
||||
|
||||
# Timestamps
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class VendorSubscriptionWithVendor(VendorSubscriptionResponse):
|
||||
"""Subscription response with vendor info."""
|
||||
|
||||
vendor_name: str
|
||||
vendor_code: str
|
||||
|
||||
# Usage counts (for admin display)
|
||||
products_count: int | None = None
|
||||
team_count: int | None = None
|
||||
|
||||
|
||||
class VendorSubscriptionListResponse(BaseModel):
|
||||
"""Response for listing vendor subscriptions."""
|
||||
|
||||
subscriptions: list[VendorSubscriptionWithVendor]
|
||||
total: int
|
||||
page: int
|
||||
per_page: int
|
||||
pages: int
|
||||
|
||||
|
||||
class VendorSubscriptionCreate(BaseModel):
|
||||
"""Schema for admin creating a vendor subscription."""
|
||||
|
||||
tier: str = "essential"
|
||||
status: str = "trial"
|
||||
trial_days: int = 14
|
||||
is_annual: bool = False
|
||||
|
||||
|
||||
class VendorSubscriptionUpdate(BaseModel):
|
||||
"""Schema for admin updating a vendor subscription."""
|
||||
|
||||
tier: str | None = None
|
||||
status: str | None = None
|
||||
custom_orders_limit: int | None = None
|
||||
custom_products_limit: int | None = None
|
||||
custom_team_limit: int | None = None
|
||||
trial_ends_at: datetime | None = None
|
||||
cancellation_reason: str | None = None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Billing History Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class BillingHistoryResponse(BaseModel):
|
||||
"""Schema for billing history entry."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
vendor_id: int
|
||||
stripe_invoice_id: str | None = None
|
||||
invoice_number: str | None = None
|
||||
invoice_date: datetime
|
||||
due_date: datetime | None = None
|
||||
|
||||
# Amounts
|
||||
subtotal_cents: int
|
||||
tax_cents: int
|
||||
total_cents: int
|
||||
amount_paid_cents: int
|
||||
currency: str = "EUR"
|
||||
|
||||
# Status
|
||||
status: str
|
||||
|
||||
# URLs
|
||||
invoice_pdf_url: str | None = None
|
||||
hosted_invoice_url: str | None = None
|
||||
|
||||
# Description
|
||||
description: str | None = None
|
||||
|
||||
# Timestamps
|
||||
created_at: datetime
|
||||
|
||||
@property
|
||||
def total_display(self) -> str:
|
||||
"""Format total for display."""
|
||||
return f"€{self.total_cents / 100:.2f}"
|
||||
|
||||
|
||||
class BillingHistoryWithVendor(BillingHistoryResponse):
|
||||
"""Billing history with vendor info."""
|
||||
|
||||
vendor_name: str
|
||||
vendor_code: str
|
||||
|
||||
|
||||
class BillingHistoryListResponse(BaseModel):
|
||||
"""Response for listing billing history."""
|
||||
|
||||
invoices: list[BillingHistoryWithVendor]
|
||||
total: int
|
||||
page: int
|
||||
per_page: int
|
||||
pages: int
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Checkout & Portal Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class CheckoutRequest(BaseModel):
|
||||
"""Request for creating checkout session."""
|
||||
|
||||
tier_code: str
|
||||
is_annual: bool = False
|
||||
|
||||
|
||||
class CheckoutResponse(BaseModel):
|
||||
"""Response with checkout session URL."""
|
||||
|
||||
checkout_url: str
|
||||
session_id: str
|
||||
|
||||
|
||||
class PortalSessionResponse(BaseModel):
|
||||
"""Response with customer portal URL."""
|
||||
|
||||
portal_url: str
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Subscription Stats Schemas
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class SubscriptionStatsResponse(BaseModel):
|
||||
"""Subscription statistics for admin dashboard."""
|
||||
|
||||
total_subscriptions: int
|
||||
active_count: int
|
||||
trial_count: int
|
||||
past_due_count: int
|
||||
cancelled_count: int
|
||||
expired_count: int
|
||||
|
||||
# By tier
|
||||
tier_distribution: dict[str, int]
|
||||
|
||||
# Revenue
|
||||
mrr_cents: int # Monthly recurring revenue
|
||||
arr_cents: int # Annual recurring revenue
|
||||
|
||||
@property
|
||||
def mrr_display(self) -> str:
|
||||
"""Format MRR for display."""
|
||||
return f"€{self.mrr_cents / 100:,.2f}"
|
||||
|
||||
@property
|
||||
def arr_display(self) -> str:
|
||||
"""Format ARR for display."""
|
||||
return f"€{self.arr_cents / 100:,.2f}"
|
||||
@@ -1,174 +0,0 @@
|
||||
"""
|
||||
Homepage Section Schemas with Dynamic Multi-Language Support.
|
||||
|
||||
Language codes are NOT hardcoded - they come from platform.supported_languages.
|
||||
The TranslatableText class stores translations as a dict where keys are language codes.
|
||||
|
||||
Example JSON structure:
|
||||
{
|
||||
"hero": {
|
||||
"enabled": true,
|
||||
"title": {"translations": {"fr": "Bienvenue", "en": "Welcome"}},
|
||||
"subtitle": {"translations": {...}},
|
||||
"buttons": [...]
|
||||
},
|
||||
"features": {...},
|
||||
"pricing": {...},
|
||||
"cta": {...}
|
||||
}
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class TranslatableText(BaseModel):
|
||||
"""
|
||||
Text field with translations stored as language-keyed dict.
|
||||
|
||||
Languages come from platform.supported_languages (not hardcoded).
|
||||
Use .get(lang, default_lang) to retrieve translation with fallback.
|
||||
"""
|
||||
|
||||
translations: dict[str, str] = Field(
|
||||
default_factory=dict, description="Language code -> translated text mapping"
|
||||
)
|
||||
|
||||
def get(self, lang: str, default_lang: str = "fr") -> str:
|
||||
"""Get translation with fallback to default language."""
|
||||
return self.translations.get(lang) or self.translations.get(default_lang) or ""
|
||||
|
||||
def set(self, lang: str, text: str) -> None:
|
||||
"""Set translation for a language."""
|
||||
self.translations[lang] = text
|
||||
|
||||
def has_translation(self, lang: str) -> bool:
|
||||
"""Check if translation exists for language."""
|
||||
return bool(self.translations.get(lang))
|
||||
|
||||
|
||||
class HeroButton(BaseModel):
|
||||
"""Button in hero or CTA section."""
|
||||
|
||||
text: TranslatableText = Field(default_factory=TranslatableText)
|
||||
url: str = ""
|
||||
style: str = Field(default="primary", description="primary, secondary, outline")
|
||||
|
||||
|
||||
class HeroSection(BaseModel):
|
||||
"""Hero section configuration."""
|
||||
|
||||
enabled: bool = True
|
||||
badge_text: Optional[TranslatableText] = None
|
||||
title: TranslatableText = Field(default_factory=TranslatableText)
|
||||
subtitle: TranslatableText = Field(default_factory=TranslatableText)
|
||||
background_type: str = Field(
|
||||
default="gradient", description="gradient, image, solid"
|
||||
)
|
||||
background_image: Optional[str] = None
|
||||
buttons: list[HeroButton] = Field(default_factory=list)
|
||||
|
||||
|
||||
class FeatureCard(BaseModel):
|
||||
"""Single feature in features section."""
|
||||
|
||||
icon: str = ""
|
||||
title: TranslatableText = Field(default_factory=TranslatableText)
|
||||
description: TranslatableText = Field(default_factory=TranslatableText)
|
||||
|
||||
|
||||
class FeaturesSection(BaseModel):
|
||||
"""Features section configuration."""
|
||||
|
||||
enabled: bool = True
|
||||
title: TranslatableText = Field(default_factory=TranslatableText)
|
||||
subtitle: Optional[TranslatableText] = None
|
||||
features: list[FeatureCard] = Field(default_factory=list)
|
||||
layout: str = Field(default="grid", description="grid, list, cards")
|
||||
|
||||
|
||||
class PricingSection(BaseModel):
|
||||
"""Pricing section configuration."""
|
||||
|
||||
enabled: bool = True
|
||||
title: TranslatableText = Field(default_factory=TranslatableText)
|
||||
subtitle: Optional[TranslatableText] = None
|
||||
use_subscription_tiers: bool = Field(
|
||||
default=True, description="Pull pricing from subscription_tiers table dynamically"
|
||||
)
|
||||
|
||||
|
||||
class CTASection(BaseModel):
|
||||
"""Call-to-action section configuration."""
|
||||
|
||||
enabled: bool = True
|
||||
title: TranslatableText = Field(default_factory=TranslatableText)
|
||||
subtitle: Optional[TranslatableText] = None
|
||||
buttons: list[HeroButton] = Field(default_factory=list)
|
||||
background_type: str = Field(
|
||||
default="gradient", description="gradient, image, solid"
|
||||
)
|
||||
|
||||
|
||||
class HomepageSections(BaseModel):
|
||||
"""Complete homepage sections structure."""
|
||||
|
||||
hero: Optional[HeroSection] = None
|
||||
features: Optional[FeaturesSection] = None
|
||||
pricing: Optional[PricingSection] = None
|
||||
cta: Optional[CTASection] = None
|
||||
|
||||
@classmethod
|
||||
def get_empty_structure(cls, languages: list[str]) -> "HomepageSections":
|
||||
"""
|
||||
Create empty section structure with language placeholders.
|
||||
|
||||
Args:
|
||||
languages: List of language codes from platform.supported_languages
|
||||
|
||||
Returns:
|
||||
HomepageSections with empty translations for all languages
|
||||
"""
|
||||
|
||||
def make_translatable(langs: list[str]) -> TranslatableText:
|
||||
return TranslatableText(translations={lang: "" for lang in langs})
|
||||
|
||||
return cls(
|
||||
hero=HeroSection(
|
||||
title=make_translatable(languages),
|
||||
subtitle=make_translatable(languages),
|
||||
buttons=[],
|
||||
),
|
||||
features=FeaturesSection(
|
||||
title=make_translatable(languages),
|
||||
features=[],
|
||||
),
|
||||
pricing=PricingSection(
|
||||
title=make_translatable(languages),
|
||||
use_subscription_tiers=True,
|
||||
),
|
||||
cta=CTASection(
|
||||
title=make_translatable(languages),
|
||||
buttons=[],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# API Request/Response Schemas
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class SectionUpdateRequest(BaseModel):
|
||||
"""Request to update a single section."""
|
||||
|
||||
section_name: str = Field(..., description="hero, features, pricing, or cta")
|
||||
section_data: dict = Field(..., description="Section configuration")
|
||||
|
||||
|
||||
class HomepageSectionsResponse(BaseModel):
|
||||
"""Response containing all homepage sections with platform language info."""
|
||||
|
||||
sections: Optional[HomepageSections] = None
|
||||
supported_languages: list[str] = Field(default_factory=lambda: ["fr", "de", "en"])
|
||||
default_language: str = "fr"
|
||||
@@ -1 +0,0 @@
|
||||
# Marketplace import job models
|
||||
@@ -1 +0,0 @@
|
||||
# Monitoring models
|
||||
@@ -1,58 +0,0 @@
|
||||
# models/schema/subscription.py
|
||||
"""
|
||||
Legacy location for subscription schemas.
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
# 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,
|
||||
)
|
||||
|
||||
__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",
|
||||
]
|
||||
@@ -1,247 +0,0 @@
|
||||
# models/schema/vendor_product.py
|
||||
"""
|
||||
Pydantic schemas for vendor product catalog operations.
|
||||
|
||||
Used by admin vendor product endpoints for:
|
||||
- Product listing and filtering
|
||||
- Product statistics
|
||||
- Product detail views
|
||||
- Catalog vendor listings
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class VendorProductListItem(BaseModel):
|
||||
"""Product item for vendor catalog list view."""
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
vendor_id: int
|
||||
vendor_name: str | None = None
|
||||
vendor_code: str | None = None
|
||||
marketplace_product_id: int | None = None
|
||||
vendor_sku: str | None = None
|
||||
title: str | None = None
|
||||
brand: str | None = None
|
||||
price: float | None = None
|
||||
currency: str | None = None
|
||||
effective_price: float | None = None
|
||||
effective_currency: str | None = None
|
||||
is_active: bool | None = None
|
||||
is_featured: bool | None = None
|
||||
is_digital: bool | None = None
|
||||
image_url: str | None = None
|
||||
source_marketplace: str | None = None
|
||||
source_vendor: str | None = None
|
||||
created_at: str | None = None
|
||||
updated_at: str | None = None
|
||||
|
||||
|
||||
class VendorProductListResponse(BaseModel):
|
||||
"""Paginated vendor product list response."""
|
||||
|
||||
products: list[VendorProductListItem]
|
||||
total: int
|
||||
skip: int
|
||||
limit: int
|
||||
|
||||
|
||||
class VendorProductStats(BaseModel):
|
||||
"""Vendor product statistics."""
|
||||
|
||||
total: int
|
||||
active: int
|
||||
inactive: int
|
||||
featured: int
|
||||
digital: int
|
||||
physical: int
|
||||
by_vendor: dict[str, int]
|
||||
|
||||
|
||||
class CatalogVendor(BaseModel):
|
||||
"""Vendor with products in catalog."""
|
||||
|
||||
id: int
|
||||
name: str
|
||||
vendor_code: str
|
||||
|
||||
|
||||
class CatalogVendorsResponse(BaseModel):
|
||||
"""Response for catalog vendors list."""
|
||||
|
||||
vendors: list[CatalogVendor]
|
||||
|
||||
|
||||
class VendorProductDetail(BaseModel):
|
||||
"""Detailed vendor product information.
|
||||
|
||||
Products are independent entities - all fields are populated at creation.
|
||||
Source values are kept for "view original source" comparison only.
|
||||
"""
|
||||
|
||||
id: int
|
||||
vendor_id: int
|
||||
vendor_name: str | None = None
|
||||
vendor_code: str | None = None
|
||||
marketplace_product_id: int | None = None # Optional for direct product creation
|
||||
vendor_sku: str | None = None
|
||||
# Product identifiers
|
||||
gtin: str | None = None
|
||||
gtin_type: str | None = None # ean13, ean8, upc, isbn, etc.
|
||||
# Product fields with source comparison
|
||||
price: float | None = None
|
||||
price_cents: int | None = None
|
||||
price_source: float | None = None # For "view original source" feature
|
||||
sale_price: float | None = None
|
||||
sale_price_cents: int | None = None
|
||||
sale_price_source: float | None = None
|
||||
currency: str | None = None
|
||||
currency_source: str | None = None
|
||||
brand: str | None = None
|
||||
brand_source: str | None = None
|
||||
condition: str | None = None
|
||||
condition_source: str | None = None
|
||||
availability: str | None = None
|
||||
availability_source: str | None = None
|
||||
primary_image_url: str | None = None
|
||||
primary_image_url_source: str | None = None
|
||||
additional_images: list[str] | None = None
|
||||
is_digital: bool | None = None
|
||||
product_type: str | None = None
|
||||
# Vendor-specific fields
|
||||
is_featured: bool | None = None
|
||||
is_active: bool | None = None
|
||||
display_order: int | None = None
|
||||
min_quantity: int | None = None
|
||||
max_quantity: int | None = None
|
||||
# Supplier tracking
|
||||
supplier: str | None = None
|
||||
supplier_product_id: str | None = None
|
||||
cost: float | None = None # What vendor pays to acquire product
|
||||
margin_percent: float | None = None
|
||||
# Tax/profit info
|
||||
tax_rate_percent: int | None = None
|
||||
net_price: float | None = None
|
||||
vat_amount: float | None = None
|
||||
profit: float | None = None
|
||||
profit_margin_percent: float | None = None
|
||||
# Digital fulfillment
|
||||
download_url: str | None = None
|
||||
license_type: str | None = None
|
||||
fulfillment_email_template: str | None = None
|
||||
# Source info
|
||||
source_marketplace: str | None = None
|
||||
source_vendor: str | None = None
|
||||
source_gtin: str | None = None
|
||||
source_sku: str | None = None
|
||||
# Translations
|
||||
marketplace_translations: dict | None = None
|
||||
vendor_translations: dict | None = None
|
||||
# Convenience fields for UI display
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
image_url: str | None = None # Alias for primary_image_url
|
||||
# Timestamps
|
||||
created_at: str | None = None
|
||||
updated_at: str | None = None
|
||||
|
||||
|
||||
class RemoveProductResponse(BaseModel):
|
||||
"""Response from product removal."""
|
||||
|
||||
message: str
|
||||
|
||||
|
||||
class TranslationUpdate(BaseModel):
|
||||
"""Translation data for a single language."""
|
||||
|
||||
title: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class VendorProductCreate(BaseModel):
|
||||
"""Schema for creating a vendor product (admin use - includes vendor_id)."""
|
||||
|
||||
vendor_id: int
|
||||
|
||||
# Translations by language code (en, fr, de, lu)
|
||||
translations: dict[str, TranslationUpdate] | None = None
|
||||
|
||||
# Product identifiers
|
||||
brand: str | None = None
|
||||
vendor_sku: str | None = None
|
||||
gtin: str | None = None
|
||||
gtin_type: str | None = None # ean13, ean8, upc, isbn
|
||||
|
||||
# Pricing
|
||||
price: float | None = None
|
||||
sale_price: float | None = None
|
||||
currency: str = "EUR"
|
||||
tax_rate_percent: int | None = 17 # Default Luxembourg VAT
|
||||
availability: str | None = None
|
||||
|
||||
# Images
|
||||
primary_image_url: str | None = None
|
||||
additional_images: list[str] | None = None
|
||||
|
||||
# Status
|
||||
is_active: bool = True
|
||||
is_featured: bool = False
|
||||
is_digital: bool = False
|
||||
|
||||
|
||||
class VendorDirectProductCreate(BaseModel):
|
||||
"""Schema for vendor direct product creation (vendor_id from JWT token)."""
|
||||
|
||||
title: str
|
||||
brand: str | None = None
|
||||
vendor_sku: str | None = None
|
||||
gtin: str | None = None
|
||||
price: float | None = None
|
||||
currency: str = "EUR"
|
||||
availability: str | None = None
|
||||
is_active: bool = True
|
||||
is_featured: bool = False
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class VendorProductUpdate(BaseModel):
|
||||
"""Schema for updating a vendor product."""
|
||||
|
||||
# Translations by language code (en, fr, de, lu)
|
||||
translations: dict[str, TranslationUpdate] | None = None
|
||||
|
||||
# Product identifiers
|
||||
brand: str | None = None
|
||||
vendor_sku: str | None = None
|
||||
gtin: str | None = None
|
||||
gtin_type: str | None = None # ean13, ean8, upc, isbn, etc.
|
||||
|
||||
# Pricing
|
||||
price: float | None = None # Price incl. VAT in euros
|
||||
sale_price: float | None = None # Optional sale price
|
||||
currency: str | None = None
|
||||
tax_rate_percent: int | None = None # 3, 8, 14, 17
|
||||
availability: str | None = None # in_stock, out_of_stock, preorder, backorder
|
||||
|
||||
# Status
|
||||
is_digital: bool | None = None
|
||||
is_active: bool | None = None
|
||||
is_featured: bool | None = None
|
||||
|
||||
# Images
|
||||
primary_image_url: str | None = None
|
||||
additional_images: list[str] | None = None
|
||||
|
||||
# Optional supplier info
|
||||
supplier: str | None = None
|
||||
cost: float | None = None # Cost in euros
|
||||
|
||||
|
||||
class VendorProductCreateResponse(BaseModel):
|
||||
"""Response from product creation."""
|
||||
|
||||
id: int
|
||||
message: str
|
||||
Reference in New Issue
Block a user