feat: add invoicing system and subscription tier enforcement

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>
This commit is contained in:
2025-12-24 18:15:27 +01:00
parent 4d9b816072
commit 6232bb47f6
23 changed files with 4342 additions and 241 deletions

View File

@@ -31,6 +31,10 @@ from app.exceptions import (
ValidationException,
)
from app.services.order_item_exception_service import order_item_exception_service
from app.services.subscription_service import (
subscription_service,
TierLimitExceededException,
)
from app.utils.money import Money, cents_to_euros, euros_to_cents
from models.database.customer import Customer
from models.database.marketplace_product import MarketplaceProduct
@@ -271,7 +275,11 @@ class OrderService:
Raises:
ValidationException: If order data is invalid
InsufficientInventoryException: If not enough inventory
TierLimitExceededException: If vendor has reached order limit
"""
# Check tier limit before creating order
subscription_service.check_order_limit(db, vendor_id)
try:
# Get or create customer
if order_data.customer_id:
@@ -428,6 +436,9 @@ class OrderService:
db.flush()
db.refresh(order)
# Increment order count for subscription tracking
subscription_service.increment_order_count(db, vendor_id)
logger.info(
f"Order {order.order_number} created for vendor {vendor_id}, "
f"total: EUR {cents_to_euros(total_amount_cents):.2f}"
@@ -439,6 +450,7 @@ class OrderService:
ValidationException,
InsufficientInventoryException,
CustomerNotFoundException,
TierLimitExceededException,
):
raise
except Exception as e:
@@ -450,6 +462,7 @@ class OrderService:
db: Session,
vendor_id: int,
shipment_data: dict[str, Any],
skip_limit_check: bool = False,
) -> Order:
"""
Create an order from Letzshop shipment data.
@@ -462,13 +475,27 @@ class OrderService:
db: Database session
vendor_id: Vendor ID
shipment_data: Raw shipment data from Letzshop API
skip_limit_check: If True, skip tier limit check (for batch imports
that check limit upfront)
Returns:
Created Order object
Raises:
ValidationException: If product not found by GTIN
TierLimitExceededException: If vendor has reached order limit
"""
# Check tier limit before creating order (unless skipped for batch ops)
if not skip_limit_check:
can_create, message = subscription_service.can_create_order(db, vendor_id)
if not can_create:
raise TierLimitExceededException(
message=message or "Order limit exceeded",
limit_type="orders",
current=0, # Will be filled by caller if needed
limit=0,
)
order_data = shipment_data.get("order", {})
# Generate order number using Letzshop order number
@@ -777,6 +804,9 @@ class OrderService:
f"order {order.order_number}"
)
# Increment order count for subscription tracking
subscription_service.increment_order_count(db, vendor_id)
logger.info(
f"Letzshop order {order.order_number} created for vendor {vendor_id}, "
f"status: {status}, items: {len(inventory_units)}"