Phase 1 OMS implementation: Invoicing: - Add Invoice and VendorInvoiceSettings database models - Full EU VAT support (27 countries, OSS, B2B reverse charge) - Invoice PDF generation with WeasyPrint + Jinja2 templates - Vendor invoice API endpoints for settings, creation, PDF download Subscription Tiers: - Add VendorSubscription model with 4 tiers (Essential/Professional/Business/Enterprise) - Tier limit enforcement for orders, products, team members - Feature gating based on subscription tier - Automatic trial subscription creation for new vendors - Integrate limit checks into order creation (direct and Letzshop sync) Marketing: - Update pricing documentation with 4-tier structure - Revise back-office positioning strategy - Update homepage with Veeqo-inspired Letzshop-focused messaging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
194 lines
5.0 KiB
Python
194 lines
5.0 KiB
Python
# models/schema/subscription.py
|
|
"""
|
|
Pydantic schemas for subscription operations.
|
|
|
|
Supports subscription management and tier limit checks.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
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 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
|