major refactoring adding vendor and customer features

This commit is contained in:
2025-10-11 09:09:25 +02:00
parent f569995883
commit dd16198276
126 changed files with 15109 additions and 3747 deletions

21
app/api/v1/vendor/__init__.py vendored Normal file
View File

@@ -0,0 +1,21 @@
# app/api/v1/vendor/__init__.py
"""
Vendor API endpoints.
"""
from fastapi import APIRouter
from . import auth, dashboard, products, orders, marketplace, inventory, vendor
# Create vendor router
router = APIRouter()
# Include all vendor sub-routers
router.include_router(auth.router, tags=["vendor-auth"])
router.include_router(dashboard.router, tags=["vendor-dashboard"])
router.include_router(products.router, tags=["vendor-products"])
router.include_router(orders.router, tags=["vendor-orders"])
router.include_router(marketplace.router, tags=["vendor-marketplace"])
router.include_router(inventory.router, tags=["vendor-inventory"])
router.include_router(vendor.router, tags=["vendor-management"])
__all__ = ["router"]

83
app/api/v1/vendor/auth.py vendored Normal file
View File

@@ -0,0 +1,83 @@
# app/api/v1/vendor/auth.py
"""
Vendor team authentication endpoints.
This module provides:
- Vendor team member login
- Vendor owner login
- Vendor-scoped authentication
"""
import logging
from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.services.auth_service import auth_service
from app.exceptions import InvalidCredentialsException
from middleware.vendor_context import get_current_vendor
from models.schemas.auth import LoginResponse, UserLogin
from models.database.vendor import Vendor
router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("/login", response_model=LoginResponse)
def vendor_login(
user_credentials: UserLogin,
request: Request,
db: Session = Depends(get_db)
):
"""
Vendor team member login.
Authenticates users who are part of a vendor team.
Validates against vendor context if available.
"""
# Authenticate user
login_result = auth_service.login_user(db=db, user_credentials=user_credentials)
user = login_result["user"]
# Prevent admin users from using vendor login
if user.role == "admin":
logger.warning(f"Admin user attempted vendor login: {user.username}")
raise InvalidCredentialsException("Please use admin portal to login")
# Optional: Validate user belongs to current vendor context
vendor = get_current_vendor(request)
if vendor:
# Check if user is vendor owner or team member
is_owner = any(v.id == vendor.id for v in user.owned_vendors)
is_team_member = any(
vm.vendor_id == vendor.id and vm.is_active
for vm in user.vendor_memberships
)
if not (is_owner or is_team_member):
logger.warning(
f"User {user.username} attempted login to vendor {vendor.vendor_code} "
f"but is not authorized"
)
raise InvalidCredentialsException(
"You do not have access to this vendor"
)
logger.info(f"Vendor team login successful: {user.username}")
return LoginResponse(
access_token=login_result["token_data"]["access_token"],
token_type=login_result["token_data"]["token_type"],
expires_in=login_result["token_data"]["expires_in"],
user=login_result["user"],
)
@router.post("/logout")
def vendor_logout():
"""
Vendor team member logout.
Client should remove token from storage.
"""
return {"message": "Logged out successfully"}

62
app/api/v1/vendor/dashboard.py vendored Normal file
View File

@@ -0,0 +1,62 @@
# app/api/v1/vendor/dashboard.py
"""
Vendor dashboard and statistics endpoints.
"""
import logging
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.stats_service import stats_service
from models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/dashboard")
logger = logging.getLogger(__name__)
@router.get("/stats")
def get_vendor_dashboard_stats(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get vendor-specific dashboard statistics.
Returns statistics for the current vendor only:
- Total products in catalog
- Total orders
- Total customers
- Revenue metrics
"""
# Get vendor-scoped statistics
stats_data = stats_service.get_vendor_stats(db=db, vendor_id=vendor.id)
return {
"vendor": {
"id": vendor.id,
"name": vendor.name,
"vendor_code": vendor.vendor_code,
},
"products": {
"total": stats_data.get("total_products", 0),
"active": stats_data.get("active_products", 0),
},
"orders": {
"total": stats_data.get("total_orders", 0),
"pending": stats_data.get("pending_orders", 0),
"completed": stats_data.get("completed_orders", 0),
},
"customers": {
"total": stats_data.get("total_customers", 0),
"active": stats_data.get("active_customers", 0),
},
"revenue": {
"total": stats_data.get("total_revenue", 0),
"this_month": stats_data.get("revenue_this_month", 0),
}
}

141
app/api/v1/vendor/inventory.py vendored Normal file
View File

@@ -0,0 +1,141 @@
# app/api/v1/vendor/inventory.py
import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.inventory_service import inventory_service
from models.schemas.inventory import (
InventoryCreate,
InventoryAdjust,
InventoryUpdate,
InventoryReserve,
InventoryResponse,
ProductInventorySummary,
InventoryListResponse
)
from models.database.user import User
from models.database.vendor import Vendor
router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("/inventory/set", response_model=InventoryResponse)
def set_inventory(
inventory: InventoryCreate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Set exact inventory quantity (replaces existing)."""
return inventory_service.set_inventory(db, vendor.id, inventory)
@router.post("/inventory/adjust", response_model=InventoryResponse)
def adjust_inventory(
adjustment: InventoryAdjust,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Adjust inventory (positive to add, negative to remove)."""
return inventory_service.adjust_inventory(db, vendor.id, adjustment)
@router.post("/inventory/reserve", response_model=InventoryResponse)
def reserve_inventory(
reservation: InventoryReserve,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Reserve inventory for an order."""
return inventory_service.reserve_inventory(db, vendor.id, reservation)
@router.post("/inventory/release", response_model=InventoryResponse)
def release_reservation(
reservation: InventoryReserve,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Release reserved inventory (cancel order)."""
return inventory_service.release_reservation(db, vendor.id, reservation)
@router.post("/inventory/fulfill", response_model=InventoryResponse)
def fulfill_reservation(
reservation: InventoryReserve,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Fulfill reservation (complete order, remove from stock)."""
return inventory_service.fulfill_reservation(db, vendor.id, reservation)
@router.get("/inventory/product/{product_id}", response_model=ProductInventorySummary)
def get_product_inventory(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get inventory summary for a product."""
return inventory_service.get_product_inventory(db, vendor.id, product_id)
@router.get("/inventory", response_model=InventoryListResponse)
def get_vendor_inventory(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
location: Optional[str] = Query(None),
low_stock: Optional[int] = Query(None, ge=0),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get all inventory for vendor."""
inventories = inventory_service.get_vendor_inventory(
db, vendor.id, skip, limit, location, low_stock
)
# Get total count
total = len(inventories) # You might want a separate count query for large datasets
return InventoryListResponse(
inventories=inventories,
total=total,
skip=skip,
limit=limit
)
@router.put("/inventory/{inventory_id}", response_model=InventoryResponse)
def update_inventory(
inventory_id: int,
inventory_update: InventoryUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Update inventory entry."""
return inventory_service.update_inventory(db, vendor.id, inventory_id, inventory_update)
@router.delete("/inventory/{inventory_id}")
def delete_inventory(
inventory_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Delete inventory entry."""
inventory_service.delete_inventory(db, vendor.id, inventory_id)
return {"message": "Inventory deleted successfully"}

115
app/api/v1/vendor/marketplace.py vendored Normal file
View File

@@ -0,0 +1,115 @@
# app/api/v1/vendor/marketplace.py # Note: Should be under /vendor/ route
"""
Marketplace import endpoints for vendors.
Vendor context is automatically injected by middleware.
"""
import logging
from typing import List, Optional
from fastapi import APIRouter, BackgroundTasks, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context # IMPORTANT
from app.services.marketplace_import_job_service import marketplace_import_job_service
from app.tasks.background_tasks import process_marketplace_import
from middleware.decorators import rate_limit
from models.schemas.marketplace_import_job import (
MarketplaceImportJobResponse,
MarketplaceImportJobRequest
)
from models.database.user import User
from models.database.vendor import Vendor
router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("/import", response_model=MarketplaceImportJobResponse)
@rate_limit(max_requests=10, window_seconds=3600)
async def import_products_from_marketplace(
request: MarketplaceImportJobRequest,
background_tasks: BackgroundTasks,
vendor: Vendor = Depends(require_vendor_context()), # ADDED: Vendor from middleware
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Import products from marketplace CSV with background processing (Protected)."""
logger.info(
f"Starting marketplace import: {request.marketplace} for vendor {vendor.vendor_code} "
f"by user {current_user.username}"
)
# Create import job (vendor comes from middleware)
import_job = marketplace_import_job_service.create_import_job(
db, request, vendor, current_user
)
# Process in background
background_tasks.add_task(
process_marketplace_import,
import_job.id,
request.source_url, # FIXED: was request.url
request.marketplace,
vendor.id, # Pass vendor_id instead of vendor_code
request.batch_size or 1000,
)
return MarketplaceImportJobResponse(
job_id=import_job.id,
status="pending",
marketplace=request.marketplace,
vendor_id=import_job.vendor_id,
vendor_code=vendor.vendor_code,
vendor_name=vendor.name, # FIXED: from vendor object
source_url=request.source_url,
message=f"Marketplace import started from {request.marketplace}. "
f"Check status with /import-status/{import_job.id}",
imported=0,
updated=0,
total_processed=0,
error_count=0,
created_at=import_job.created_at,
)
@router.get("/imports/{job_id}", response_model=MarketplaceImportJobResponse)
def get_marketplace_import_status(
job_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get status of marketplace import job (Protected)."""
job = marketplace_import_job_service.get_import_job_by_id(db, job_id, current_user)
# Verify job belongs to current vendor
if job.vendor_id != vendor.id:
from app.exceptions import UnauthorizedVendorAccessException
raise UnauthorizedVendorAccessException(vendor.vendor_code, current_user.id)
return marketplace_import_job_service.convert_to_response_model(job)
@router.get("/imports", response_model=List[MarketplaceImportJobResponse])
def get_marketplace_import_jobs(
marketplace: Optional[str] = Query(None, description="Filter by marketplace"),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get marketplace import jobs for current vendor (Protected)."""
jobs = marketplace_import_job_service.get_import_jobs(
db=db,
vendor=vendor,
user=current_user,
marketplace=marketplace,
skip=skip,
limit=limit,
)
return [marketplace_import_job_service.convert_to_response_model(job) for job in jobs]

111
app/api/v1/vendor/orders.py vendored Normal file
View File

@@ -0,0 +1,111 @@
# app/api/v1/vendor/orders.py
"""
Vendor order management endpoints.
"""
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.order_service import order_service
from models.schemas.order import (
OrderResponse,
OrderDetailResponse,
OrderListResponse,
OrderUpdate
)
from models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/orders")
logger = logging.getLogger(__name__)
@router.get("", response_model=OrderListResponse)
def get_vendor_orders(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
status: Optional[str] = Query(None, description="Filter by order status"),
customer_id: Optional[int] = Query(None, description="Filter by customer"),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get all orders for vendor.
Supports filtering by:
- status: Order status (pending, processing, shipped, delivered, cancelled)
- customer_id: Filter orders from specific customer
"""
orders, total = order_service.get_vendor_orders(
db=db,
vendor_id=vendor.id,
skip=skip,
limit=limit,
status=status,
customer_id=customer_id
)
return OrderListResponse(
orders=[OrderResponse.model_validate(o) for o in orders],
total=total,
skip=skip,
limit=limit
)
@router.get("/{order_id}", response_model=OrderDetailResponse)
def get_order_details(
order_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get detailed order information including items and addresses."""
order = order_service.get_order(
db=db,
vendor_id=vendor.id,
order_id=order_id
)
return OrderDetailResponse.model_validate(order)
@router.put("/{order_id}/status", response_model=OrderResponse)
def update_order_status(
order_id: int,
order_update: OrderUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Update order status and tracking information.
Valid statuses:
- pending: Order placed, awaiting processing
- processing: Order being prepared
- shipped: Order shipped to customer
- delivered: Order delivered
- cancelled: Order cancelled
- refunded: Order refunded
"""
order = order_service.update_order_status(
db=db,
vendor_id=vendor.id,
order_id=order_id,
order_update=order_update
)
logger.info(
f"Order {order.order_number} status updated to {order.status} "
f"by user {current_user.username}"
)
return OrderResponse.model_validate(order)

227
app/api/v1/vendor/products.py vendored Normal file
View File

@@ -0,0 +1,227 @@
# app/api/v1/vendor/products.py
"""
Vendor product catalog management endpoints.
"""
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.product_service import product_service
from models.schemas.product import (
ProductCreate,
ProductUpdate,
ProductResponse,
ProductDetailResponse,
ProductListResponse
)
from models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/products")
logger = logging.getLogger(__name__)
@router.get("", response_model=ProductListResponse)
def get_vendor_products(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
is_active: Optional[bool] = Query(None),
is_featured: Optional[bool] = Query(None),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get all products in vendor catalog.
Supports filtering by:
- is_active: Filter active/inactive products
- is_featured: Filter featured products
"""
products, total = product_service.get_vendor_products(
db=db,
vendor_id=vendor.id,
skip=skip,
limit=limit,
is_active=is_active,
is_featured=is_featured
)
return ProductListResponse(
products=[ProductResponse.model_validate(p) for p in products],
total=total,
skip=skip,
limit=limit
)
@router.get("/{product_id}", response_model=ProductDetailResponse)
def get_product_details(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get detailed product information including inventory."""
product = product_service.get_product(
db=db,
vendor_id=vendor.id,
product_id=product_id
)
return ProductDetailResponse.model_validate(product)
@router.post("", response_model=ProductResponse)
def add_product_to_catalog(
product_data: ProductCreate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Add a product from marketplace to vendor catalog.
This publishes a MarketplaceProduct to the vendor's public catalog.
"""
product = product_service.create_product(
db=db,
vendor_id=vendor.id,
product_data=product_data
)
logger.info(
f"Product {product.id} added to catalog by user {current_user.username} "
f"for vendor {vendor.vendor_code}"
)
return ProductResponse.model_validate(product)
@router.put("/{product_id}", response_model=ProductResponse)
def update_product(
product_id: int,
product_data: ProductUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Update product in vendor catalog."""
product = product_service.update_product(
db=db,
vendor_id=vendor.id,
product_id=product_id,
product_update=product_data
)
logger.info(
f"Product {product_id} updated by user {current_user.username} "
f"for vendor {vendor.vendor_code}"
)
return ProductResponse.model_validate(product)
@router.delete("/{product_id}")
def remove_product_from_catalog(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Remove product from vendor catalog."""
product_service.delete_product(
db=db,
vendor_id=vendor.id,
product_id=product_id
)
logger.info(
f"Product {product_id} removed from catalog by user {current_user.username} "
f"for vendor {vendor.vendor_code}"
)
return {"message": f"Product {product_id} removed from catalog"}
@router.post("/from-import/{marketplace_product_id}", response_model=ProductResponse)
def publish_from_marketplace(
marketplace_product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Publish a marketplace product to vendor catalog.
Shortcut endpoint for publishing directly from marketplace import.
"""
product_data = ProductCreate(
marketplace_product_id=marketplace_product_id,
is_active=True
)
product = product_service.create_product(
db=db,
vendor_id=vendor.id,
product_data=product_data
)
logger.info(
f"Marketplace product {marketplace_product_id} published to catalog "
f"by user {current_user.username} for vendor {vendor.vendor_code}"
)
return ProductResponse.model_validate(product)
@router.put("/{product_id}/toggle-active")
def toggle_product_active(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Toggle product active status."""
product = product_service.get_product(db, vendor.id, product_id)
product.is_active = not product.is_active
db.commit()
db.refresh(product)
status = "activated" if product.is_active else "deactivated"
logger.info(f"Product {product_id} {status} for vendor {vendor.vendor_code}")
return {
"message": f"Product {status}",
"is_active": product.is_active
}
@router.put("/{product_id}/toggle-featured")
def toggle_product_featured(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Toggle product featured status."""
product = product_service.get_product(db, vendor.id, product_id)
product.is_featured = not product.is_featured
db.commit()
db.refresh(product)
status = "featured" if product.is_featured else "unfeatured"
logger.info(f"Product {product_id} {status} for vendor {vendor.vendor_code}")
return {
"message": f"Product {status}",
"is_featured": product.is_featured
}

330
app/api/v1/vendor/vendor.py vendored Normal file
View File

@@ -0,0 +1,330 @@
# app/api/v1/vendor/vendor.py
"""
Vendor management endpoints for vendor-scoped operations.
This module provides:
- Vendor profile management
- Vendor settings configuration
- Vendor team member management
- Vendor dashboard statistics
"""
import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_user
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.vendor_service import vendor_service
from app.services.team_service import team_service
from models.schemas.vendor import VendorUpdate, VendorResponse
from models.schemas.product import ProductResponse, ProductListResponse
from models.database.user import User
from models.database.vendor import Vendor
router = APIRouter()
logger = logging.getLogger(__name__)
# ============================================================================
# VENDOR PROFILE ENDPOINTS
# ============================================================================
@router.get("/profile", response_model=VendorResponse)
def get_vendor_profile(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get current vendor profile information."""
return vendor
@router.put("/profile", response_model=VendorResponse)
def update_vendor_profile(
vendor_update: VendorUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Update vendor profile information."""
# Verify user has permission to update vendor
if not vendor_service.can_update_vendor(vendor, current_user):
from fastapi import HTTPException
raise HTTPException(status_code=403, detail="Insufficient permissions")
return vendor_service.update_vendor(db, vendor.id, vendor_update)
# ============================================================================
# VENDOR SETTINGS ENDPOINTS
# ============================================================================
@router.get("/settings")
def get_vendor_settings(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get vendor settings and configuration."""
return {
"vendor_code": vendor.vendor_code,
"subdomain": vendor.subdomain,
"name": vendor.name,
"contact_email": vendor.contact_email,
"contact_phone": vendor.contact_phone,
"website": vendor.website,
"business_address": vendor.business_address,
"tax_number": vendor.tax_number,
"letzshop_csv_url_fr": vendor.letzshop_csv_url_fr,
"letzshop_csv_url_en": vendor.letzshop_csv_url_en,
"letzshop_csv_url_de": vendor.letzshop_csv_url_de,
"theme_config": vendor.theme_config,
"is_active": vendor.is_active,
"is_verified": vendor.is_verified,
}
@router.put("/settings/marketplace")
def update_marketplace_settings(
marketplace_config: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Update marketplace integration settings."""
# Verify permissions
if not vendor_service.can_update_vendor(vendor, current_user):
from fastapi import HTTPException
raise HTTPException(status_code=403, detail="Insufficient permissions")
# Update Letzshop URLs
if "letzshop_csv_url_fr" in marketplace_config:
vendor.letzshop_csv_url_fr = marketplace_config["letzshop_csv_url_fr"]
if "letzshop_csv_url_en" in marketplace_config:
vendor.letzshop_csv_url_en = marketplace_config["letzshop_csv_url_en"]
if "letzshop_csv_url_de" in marketplace_config:
vendor.letzshop_csv_url_de = marketplace_config["letzshop_csv_url_de"]
db.commit()
db.refresh(vendor)
return {
"message": "Marketplace settings updated successfully",
"letzshop_csv_url_fr": vendor.letzshop_csv_url_fr,
"letzshop_csv_url_en": vendor.letzshop_csv_url_en,
"letzshop_csv_url_de": vendor.letzshop_csv_url_de,
}
@router.put("/settings/theme")
def update_theme_settings(
theme_config: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Update vendor theme configuration."""
if not vendor_service.can_update_vendor(vendor, current_user):
from fastapi import HTTPException
raise HTTPException(status_code=403, detail="Insufficient permissions")
vendor.theme_config = theme_config
db.commit()
db.refresh(vendor)
return {
"message": "Theme settings updated successfully",
"theme_config": vendor.theme_config,
}
# ============================================================================
# VENDOR CATALOG ENDPOINTS
# ============================================================================
@router.get("/products", response_model=ProductListResponse)
def get_vendor_products(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
is_active: Optional[bool] = Query(None),
is_featured: Optional[bool] = Query(None),
search: Optional[str] = Query(None),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get all products in vendor catalog."""
products, total = vendor_service.get_products(
db=db,
vendor=vendor,
current_user=current_user,
skip=skip,
limit=limit,
active_only=is_active,
featured_only=is_featured,
)
return ProductListResponse(
products=products,
total=total,
skip=skip,
limit=limit
)
@router.post("/products", response_model=ProductResponse)
def add_product_to_catalog(
product_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Add a product from marketplace to vendor catalog."""
from models.schemas.product import ProductCreate
product_create = ProductCreate(**product_data)
return vendor_service.add_product_to_catalog(db, vendor, product_create)
@router.get("/products/{product_id}", response_model=ProductResponse)
def get_vendor_product(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get a specific product from vendor catalog."""
from app.services.product_service import product_service
return product_service.get_product(db, vendor.id, product_id)
@router.put("/products/{product_id}", response_model=ProductResponse)
def update_vendor_product(
product_id: int,
product_update: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Update product in vendor catalog."""
from app.services.product_service import product_service
from models.schemas.product import ProductUpdate
product_update_schema = ProductUpdate(**product_update)
return product_service.update_product(db, vendor.id, product_id, product_update_schema)
@router.delete("/products/{product_id}")
def remove_product_from_catalog(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Remove product from vendor catalog."""
from app.services.product_service import product_service
product_service.delete_product(db, vendor.id, product_id)
return {"message": "Product removed from catalog successfully"}
# ============================================================================
# VENDOR TEAM ENDPOINTS
# ============================================================================
@router.get("/team/members")
def get_team_members(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get all team members for vendor."""
return team_service.get_team_members(db, vendor.id, current_user)
@router.post("/team/invite")
def invite_team_member(
invitation_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Invite a new team member."""
return team_service.invite_team_member(db, vendor.id, invitation_data, current_user)
@router.put("/team/members/{user_id}")
def update_team_member(
user_id: int,
update_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Update team member role or status."""
return team_service.update_team_member(db, vendor.id, user_id, update_data, current_user)
@router.delete("/team/members/{user_id}")
def remove_team_member(
user_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Remove team member from vendor."""
team_service.remove_team_member(db, vendor.id, user_id, current_user)
return {"message": "Team member removed successfully"}
@router.get("/team/roles")
def get_team_roles(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get available roles for vendor team."""
return team_service.get_vendor_roles(db, vendor.id)
# ============================================================================
# VENDOR DASHBOARD & STATISTICS
# ============================================================================
@router.get("/dashboard")
def get_vendor_dashboard(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get vendor dashboard statistics."""
from app.services.stats_service import stats_service
return {
"vendor": {
"code": vendor.vendor_code,
"name": vendor.name,
"subdomain": vendor.subdomain,
"is_verified": vendor.is_verified,
},
"stats": stats_service.get_vendor_stats(db, vendor.id),
"recent_imports": [], # TODO: Implement
"recent_orders": [], # TODO: Implement
"low_stock_products": [], # TODO: Implement
}
@router.get("/analytics")
def get_vendor_analytics(
period: str = Query("30d", description="Time period: 7d, 30d, 90d, 1y"),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get vendor analytics data."""
from app.services.stats_service import stats_service
return stats_service.get_vendor_analytics(db, vendor.id, period)