vendor features for admin and vendor admin area

This commit is contained in:
2025-10-19 16:03:25 +02:00
parent 06bb463468
commit 9aee314837
15 changed files with 1693 additions and 471 deletions

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

@@ -0,0 +1,29 @@
# app/api/v1/vendor/analytics.py
"""
Vendor analytics and reporting endpoints.
"""
import logging
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.stats_service import stats_service
from models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/analytics")
logger = logging.getLogger(__name__)
@router.get("")
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 for specified time period."""
return stats_service.get_vendor_analytics(db, vendor.id, period)

View File

@@ -9,21 +9,32 @@ This module provides:
"""
import logging
from fastapi import APIRouter, Depends, Request
from fastapi import APIRouter, Depends, Request, HTTPException
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.schema.auth import LoginResponse, UserLogin
from models.database.vendor import Vendor
from models.schema.auth import UserLogin
from models.database.vendor import Vendor, VendorUser, Role
from pydantic import BaseModel
router = APIRouter()
router = APIRouter(prefix="/auth")
logger = logging.getLogger(__name__)
@router.post("/login", response_model=LoginResponse)
# Response model for vendor login
class VendorLoginResponse(BaseModel):
access_token: str
token_type: str
expires_in: int
user: dict
vendor: dict
vendor_role: str
@router.post("/login", response_model=VendorLoginResponse)
def vendor_login(
user_credentials: UserLogin,
request: Request,
@@ -35,6 +46,18 @@ def vendor_login(
Authenticates users who are part of a vendor team.
Validates against vendor context if available.
"""
# Try to get vendor from middleware first
vendor = get_current_vendor(request)
# If no vendor from middleware, try to get from request body
if not vendor and hasattr(user_credentials, 'vendor_code'):
vendor_code = getattr(user_credentials, 'vendor_code', None)
if vendor_code:
vendor = db.query(Vendor).filter(
Vendor.vendor_code == vendor_code.upper(),
Vendor.is_active == True
).first()
# Authenticate user
login_result = auth_service.login_user(db=db, user_credentials=user_credentials)
user = login_result["user"]
@@ -44,32 +67,79 @@ def vendor_login(
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)
# Determine vendor and role
vendor_role = "Member"
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
)
# Check if user is vendor owner
is_owner = vendor.owner_user_id == user.id
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"
if is_owner:
vendor_role = "Owner"
else:
# Check if user is team member
vendor_user = db.query(VendorUser).join(Role).filter(
VendorUser.user_id == user.id,
VendorUser.vendor_id == vendor.id,
VendorUser.is_active == True
).first()
if vendor_user:
vendor_role = vendor_user.role.name
else:
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"
)
else:
# No vendor context - find which vendor this user belongs to
# Check owned vendors first
if user.owned_vendors:
vendor = user.owned_vendors[0]
vendor_role = "Owner"
# Check vendor memberships
elif user.vendor_memberships:
active_membership = next(
(vm for vm in user.vendor_memberships if vm.is_active),
None
)
if active_membership:
vendor = active_membership.vendor
vendor_role = active_membership.role.name
if not vendor:
raise InvalidCredentialsException(
"You do not have access to this vendor"
"User is not associated with any vendor"
)
logger.info(f"Vendor team login successful: {user.username}")
logger.info(
f"Vendor team login successful: {user.username} "
f"for vendor {vendor.vendor_code} as {vendor_role}"
)
return LoginResponse(
return VendorLoginResponse(
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"],
user={
"id": user.id,
"username": user.username,
"email": user.email,
"role": user.role,
"is_active": user.is_active
},
vendor={
"id": vendor.id,
"vendor_code": vendor.vendor_code,
"subdomain": vendor.subdomain,
"name": vendor.name,
"is_active": vendor.is_active,
"is_verified": vendor.is_verified
},
vendor_role=vendor_role
)
@@ -80,4 +150,4 @@ def vendor_logout():
Client should remove token from storage.
"""
return {"message": "Logged out successfully"}
return {"message": "Logged out successfully"}

View File

@@ -1 +1,156 @@
# Vendor customer management
# Vendor customer management
# app/api/v1/vendor/customers.py
"""
Vendor customer 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 models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/customers")
logger = logging.getLogger(__name__)
@router.get("")
def get_vendor_customers(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
search: Optional[str] = Query(None),
is_active: Optional[bool] = Query(None),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get all customers for this vendor.
TODO: Implement in Slice 4
- Query customers filtered by vendor_id
- Support search by name/email
- Support filtering by active status
- Return paginated results
"""
return {
"customers": [],
"total": 0,
"skip": skip,
"limit": limit,
"message": "Customer management coming in Slice 4"
}
@router.get("/{customer_id}")
def get_customer_details(
customer_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get detailed customer information.
TODO: Implement in Slice 4
- Get customer by ID
- Verify customer belongs to vendor
- Include order history
- Include total spent, etc.
"""
return {
"message": "Customer details coming in Slice 4"
}
@router.get("/{customer_id}/orders")
def get_customer_orders(
customer_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get order history for a specific customer.
TODO: Implement in Slice 5
- Get all orders for customer
- Filter by vendor_id
- Return order details
"""
return {
"orders": [],
"message": "Customer orders coming in Slice 5"
}
@router.put("/{customer_id}")
def update_customer(
customer_id: int,
customer_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Update customer information.
TODO: Implement in Slice 4
- Update customer details
- Verify customer belongs to vendor
- Update customer preferences
"""
return {
"message": "Customer update coming in Slice 4"
}
@router.put("/{customer_id}/status")
def toggle_customer_status(
customer_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Activate/deactivate customer account.
TODO: Implement in Slice 4
- Toggle customer is_active status
- Verify customer belongs to vendor
- Log the change
"""
return {
"message": "Customer status toggle coming in Slice 4"
}
@router.get("/{customer_id}/stats")
def get_customer_statistics(
customer_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get customer statistics and metrics.
TODO: Implement in Slice 4
- Total orders
- Total spent
- Average order value
- Last order date
"""
return {
"total_orders": 0,
"total_spent": 0.0,
"average_order_value": 0.0,
"last_order_date": None,
"message": "Customer statistics coming in Slice 4"
}

View File

@@ -1 +1,206 @@
# File and media management
# File and media management
# app/api/v1/vendor/media.py
"""
Vendor media and file management endpoints.
"""
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query, UploadFile, File
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 models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/media")
logger = logging.getLogger(__name__)
@router.get("")
def get_media_library(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
media_type: Optional[str] = Query(None, description="image, video, document"),
search: Optional[str] = Query(None),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get vendor media library.
TODO: Implement in Slice 3
- Get all media files for vendor
- Filter by type (image, video, document)
- Search by filename
- Support pagination
- Return file URLs, sizes, metadata
"""
return {
"media": [],
"total": 0,
"skip": skip,
"limit": limit,
"message": "Media library coming in Slice 3"
}
@router.post("/upload")
async def upload_media(
file: UploadFile = File(...),
folder: Optional[str] = Query(None, description="products, general, etc."),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Upload media file.
TODO: Implement in Slice 3
- Accept file upload
- Validate file type and size
- Store file (local or cloud storage)
- Generate thumbnails for images
- Save metadata to database
- Return file URL
"""
return {
"file_url": None,
"thumbnail_url": None,
"message": "Media upload coming in Slice 3"
}
@router.post("/upload/multiple")
async def upload_multiple_media(
files: list[UploadFile] = File(...),
folder: Optional[str] = Query(None),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Upload multiple media files at once.
TODO: Implement in Slice 3
- Accept multiple files
- Process each file
- Return list of uploaded file URLs
- Handle errors gracefully
"""
return {
"uploaded_files": [],
"failed_files": [],
"message": "Multiple upload coming in Slice 3"
}
@router.get("/{media_id}")
def get_media_details(
media_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get media file details.
TODO: Implement in Slice 3
- Get file metadata
- Return file URL
- Return usage information (which products use this file)
"""
return {
"message": "Media details coming in Slice 3"
}
@router.put("/{media_id}")
def update_media_metadata(
media_id: int,
metadata: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Update media file metadata.
TODO: Implement in Slice 3
- Update filename
- Update alt text
- Update tags/categories
- Update description
"""
return {
"message": "Media update coming in Slice 3"
}
@router.delete("/{media_id}")
def delete_media(
media_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Delete media file.
TODO: Implement in Slice 3
- Verify file belongs to vendor
- Check if file is in use by products
- Delete file from storage
- Delete database record
- Return success/error
"""
return {
"message": "Media deletion coming in Slice 3"
}
@router.get("/{media_id}/usage")
def get_media_usage(
media_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get where this media file is being used.
TODO: Implement in Slice 3
- Check products using this media
- Check other entities using this media
- Return list of usage
"""
return {
"products": [],
"other_usage": [],
"message": "Media usage tracking coming in Slice 3"
}
@router.post("/optimize/{media_id}")
def optimize_media(
media_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Optimize media file (compress, resize, etc.).
TODO: Implement in Slice 3
- Optimize image (compress, resize)
- Generate multiple sizes
- Keep original
- Update database with new versions
"""
return {
"message": "Media optimization coming in Slice 3"
}

View File

@@ -1 +1,224 @@
# Notification management
# app/api/v1/vendor/notifications.py
"""
Vendor notification 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 models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/notifications")
logger = logging.getLogger(__name__)
@router.get("")
def get_notifications(
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
unread_only: Optional[bool] = Query(False),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get vendor notifications.
TODO: Implement in Slice 5
- Get all notifications for vendor
- Filter by read/unread status
- Support pagination
- Return notification details
"""
return {
"notifications": [],
"total": 0,
"unread_count": 0,
"message": "Notifications coming in Slice 5"
}
@router.get("/unread-count")
def get_unread_count(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get count of unread notifications.
TODO: Implement in Slice 5
- Count unread notifications for vendor
- Used for notification badge
"""
return {
"unread_count": 0,
"message": "Unread count coming in Slice 5"
}
@router.put("/{notification_id}/read")
def mark_as_read(
notification_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Mark notification as read.
TODO: Implement in Slice 5
- Mark single notification as read
- Update read timestamp
"""
return {
"message": "Mark as read coming in Slice 5"
}
@router.put("/mark-all-read")
def mark_all_as_read(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Mark all notifications as read.
TODO: Implement in Slice 5
- Mark all vendor notifications as read
- Update timestamps
"""
return {
"message": "Mark all as read coming in Slice 5"
}
@router.delete("/{notification_id}")
def delete_notification(
notification_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Delete notification.
TODO: Implement in Slice 5
- Delete single notification
- Verify notification belongs to vendor
"""
return {
"message": "Notification deletion coming in Slice 5"
}
@router.get("/settings")
def get_notification_settings(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get notification preferences.
TODO: Implement in Slice 5
- Get email notification settings
- Get in-app notification settings
- Get notification types enabled/disabled
"""
return {
"email_notifications": True,
"in_app_notifications": True,
"notification_types": {},
"message": "Notification settings coming in Slice 5"
}
@router.put("/settings")
def update_notification_settings(
settings: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Update notification preferences.
TODO: Implement in Slice 5
- Update email notification settings
- Update in-app notification settings
- Enable/disable specific notification types
"""
return {
"message": "Notification settings update coming in Slice 5"
}
@router.get("/templates")
def get_notification_templates(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get notification email templates.
TODO: Implement in Slice 5
- Get all notification templates
- Include: order confirmation, shipping notification, etc.
- Return template details
"""
return {
"templates": [],
"message": "Notification templates coming in Slice 5"
}
@router.put("/templates/{template_id}")
def update_notification_template(
template_id: int,
template_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Update notification email template.
TODO: Implement in Slice 5
- Update template subject
- Update template body (HTML/text)
- Validate template variables
- Preview template
"""
return {
"message": "Template update coming in Slice 5"
}
@router.post("/test")
def send_test_notification(
notification_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Send test notification.
TODO: Implement in Slice 5
- Send test email notification
- Use specified template
- Send to current user's email
"""
return {
"message": "Test notification coming in Slice 5"
}

View File

@@ -1 +1,190 @@
# Payment configuration and processing
# app/api/v1/vendor/payments.py
"""
Vendor payment configuration and processing 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 models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/payments")
logger = logging.getLogger(__name__)
@router.get("/config")
def get_payment_configuration(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get vendor payment configuration.
TODO: Implement in Slice 5
- Get payment gateway settings (Stripe, PayPal, etc.)
- Get accepted payment methods
- Get currency settings
- Return masked/secure information only
"""
return {
"payment_gateway": None,
"accepted_methods": [],
"currency": "EUR",
"stripe_connected": False,
"message": "Payment configuration coming in Slice 5"
}
@router.put("/config")
def update_payment_configuration(
payment_config: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Update vendor payment configuration.
TODO: Implement in Slice 5
- Update payment gateway settings
- Connect/disconnect Stripe account
- Update accepted payment methods
- Validate configuration before saving
"""
return {
"message": "Payment configuration update coming in Slice 5"
}
@router.post("/stripe/connect")
def connect_stripe_account(
stripe_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Connect Stripe account for payment processing.
TODO: Implement in Slice 5
- Handle Stripe OAuth flow
- Store Stripe account ID securely
- Verify Stripe account is active
- Enable payment processing
"""
return {
"message": "Stripe connection coming in Slice 5"
}
@router.delete("/stripe/disconnect")
def disconnect_stripe_account(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Disconnect Stripe account.
TODO: Implement in Slice 5
- Remove Stripe account connection
- Disable payment processing
- Warn about pending payments
"""
return {
"message": "Stripe disconnection coming in Slice 5"
}
@router.get("/methods")
def get_payment_methods(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get accepted payment methods for vendor.
TODO: Implement in Slice 5
- Return list of enabled payment methods
- Include: credit card, PayPal, bank transfer, etc.
"""
return {
"methods": [],
"message": "Payment methods coming in Slice 5"
}
@router.get("/transactions")
def get_payment_transactions(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get payment transaction history.
TODO: Implement in Slice 5
- Get all payment transactions for vendor
- Filter by date range, status, etc.
- Include payment details
- Support pagination
"""
return {
"transactions": [],
"total": 0,
"message": "Payment transactions coming in Slice 5"
}
@router.get("/balance")
def get_payment_balance(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Get vendor payment balance and payout information.
TODO: Implement in Slice 5
- Get available balance
- Get pending balance
- Get next payout date
- Get payout history
"""
return {
"available_balance": 0.0,
"pending_balance": 0.0,
"currency": "EUR",
"next_payout_date": None,
"message": "Payment balance coming in Slice 5"
}
@router.post("/refund/{payment_id}")
def refund_payment(
payment_id: int,
refund_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""
Process payment refund.
TODO: Implement in Slice 5
- Verify payment belongs to vendor
- Process refund through payment gateway
- Update order status
- Send refund notification to customer
"""
return {
"message": "Payment refund coming in Slice 5"
}

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

@@ -0,0 +1,44 @@
# app/api/v1/vendor/profile.py
"""
Vendor profile management endpoints.
"""
import logging
from fastapi import APIRouter, Depends, HTTPException
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 models.schema.vendor import VendorUpdate, VendorResponse
from models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/profile")
logger = logging.getLogger(__name__)
@router.get("", 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("", 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):
raise HTTPException(status_code=403, detail="Insufficient permissions")
return vendor_service.update_vendor(db, vendor.id, vendor_update)

View File

@@ -1 +1,95 @@
# Vendor settings and configuration
# app/api/v1/vendor/settings.py
"""
Vendor settings and configuration endpoints.
"""
import logging
from fastapi import APIRouter, Depends, HTTPException
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 models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/settings")
logger = logging.getLogger(__name__)
@router.get("")
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("/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):
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("/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):
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,
}

View File

@@ -1 +1,73 @@
# Team member management
# app/api/v1/vendor/teams.py
"""
Vendor team member management 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.team_service import team_service
from models.database.user import User
from models.database.vendor import Vendor
router = APIRouter(prefix="/teams")
logger = logging.getLogger(__name__)
@router.get("/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("/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("/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("/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("/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)

View File

@@ -1,330 +0,0 @@
# 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.schema.vendor import VendorUpdate, VendorResponse
from models.schema.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.schema.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.schema.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)