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

View File

@@ -12,55 +12,89 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_user
from app.core.database import get_db
from app.services.admin_service import admin_service
from models.schema.vendor import VendorListResponse, VendorResponse, VendorCreate
from models.schema.vendor import (
VendorListResponse,
VendorResponse,
VendorDetailResponse,
VendorCreate,
VendorCreateResponse,
VendorUpdate,
VendorTransferOwnership,
VendorTransferOwnershipResponse,
)
from models.database.user import User
router = APIRouter(prefix="/vendors")
logger = logging.getLogger(__name__)
@router.post("", response_model=VendorResponse)
@router.post("", response_model=VendorCreateResponse)
def create_vendor_with_owner(
vendor_data: VendorCreate,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
vendor_data: VendorCreate,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""
Create a new vendor with owner user account (Admin only).
This endpoint:
1. Creates a new vendor
2. Creates an owner user account for the vendor
3. Sets up default roles (Owner, Manager, Editor, Viewer)
4. Sends welcome email to vendor owner (if email service configured)
1. Creates a new vendor record
2. Creates an owner user account with owner_email
3. Sets contact_email (defaults to owner_email if not provided)
4. Sets up default roles (Owner, Manager, Editor, Viewer)
5. Returns credentials (temporary password shown ONCE)
Returns the created vendor with owner information.
**Email Fields:**
- `owner_email`: Used for owner's login/authentication (stored in users.email)
- `contact_email`: Public business contact (stored in vendors.contact_email)
Returns vendor details with owner credentials.
"""
vendor, owner_user, temp_password = admin_service.create_vendor_with_owner(
db=db,
vendor_data=vendor_data
)
return {
**VendorResponse.model_validate(vendor).model_dump(),
"owner_email": owner_user.email,
"owner_username": owner_user.username,
"temporary_password": temp_password, # Only shown once!
"login_url": f"{vendor.subdomain}.platform.com/vendor/login" if vendor.subdomain else None
}
return VendorCreateResponse(
# Vendor fields
id=vendor.id,
vendor_code=vendor.vendor_code,
subdomain=vendor.subdomain,
name=vendor.name,
description=vendor.description,
owner_user_id=vendor.owner_user_id,
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 or {},
is_active=vendor.is_active,
is_verified=vendor.is_verified,
created_at=vendor.created_at,
updated_at=vendor.updated_at,
# Owner credentials
owner_email=owner_user.email,
owner_username=owner_user.username,
temporary_password=temp_password,
login_url=f"http://localhost:8000/vendor/{vendor.subdomain}/login"
)
@router.get("", response_model=VendorListResponse)
def get_all_vendors_admin(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
search: Optional[str] = Query(None, description="Search by name or vendor code"),
is_active: Optional[bool] = Query(None),
is_verified: Optional[bool] = Query(None),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
search: Optional[str] = Query(None, description="Search by name or vendor code"),
is_active: Optional[bool] = Query(None),
is_verified: Optional[bool] = Query(None),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get all vendors with admin view (Admin only)."""
"""Get all vendors with filtering (Admin only)."""
vendors, total = admin_service.get_all_vendors(
db=db,
skip=skip,
@@ -72,15 +106,144 @@ def get_all_vendors_admin(
return VendorListResponse(vendors=vendors, total=total, skip=skip, limit=limit)
@router.get("/{vendor_id}", response_model=VendorResponse)
@router.get("/{vendor_id}", response_model=VendorDetailResponse)
def get_vendor_details(
vendor_id: int,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
vendor_id: int,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get detailed vendor information (Admin only)."""
"""
Get detailed vendor information including owner details (Admin only).
Returns both:
- `contact_email` (business contact)
- `owner_email` (owner's authentication email)
"""
vendor = admin_service.get_vendor_by_id(db, vendor_id)
return VendorResponse.model_validate(vendor)
return VendorDetailResponse(
# Vendor fields
id=vendor.id,
vendor_code=vendor.vendor_code,
subdomain=vendor.subdomain,
name=vendor.name,
description=vendor.description,
owner_user_id=vendor.owner_user_id,
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 or {},
is_active=vendor.is_active,
is_verified=vendor.is_verified,
created_at=vendor.created_at,
updated_at=vendor.updated_at,
# Owner details
owner_email=vendor.owner.email,
owner_username=vendor.owner.username,
)
@router.put("/{vendor_id}", response_model=VendorDetailResponse)
def update_vendor(
vendor_id: int,
vendor_update: VendorUpdate,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""
Update vendor information (Admin only).
**Can update:**
- Basic info: name, description, subdomain
- Business contact: contact_email, contact_phone, website
- Business details: business_address, tax_number
- Marketplace URLs
- Status: is_active, is_verified
**Cannot update:**
- `owner_email` (use POST /vendors/{id}/transfer-ownership)
- `vendor_code` (immutable)
- `owner_user_id` (use POST /vendors/{id}/transfer-ownership)
"""
vendor = admin_service.update_vendor(db, vendor_id, vendor_update)
return VendorDetailResponse(
id=vendor.id,
vendor_code=vendor.vendor_code,
subdomain=vendor.subdomain,
name=vendor.name,
description=vendor.description,
owner_user_id=vendor.owner_user_id,
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 or {},
is_active=vendor.is_active,
is_verified=vendor.is_verified,
created_at=vendor.created_at,
updated_at=vendor.updated_at,
owner_email=vendor.owner.email,
owner_username=vendor.owner.username,
)
@router.post("/{vendor_id}/transfer-ownership", response_model=VendorTransferOwnershipResponse)
def transfer_vendor_ownership(
vendor_id: int,
transfer_data: VendorTransferOwnership,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""
Transfer vendor ownership to another user (Admin only).
**This is a critical operation that:**
- Changes the owner_user_id
- Assigns new owner to "Owner" role
- Demotes old owner to "Manager" role (or removes them)
- Creates audit trail
⚠️ **This action is logged and should be used carefully.**
**Requires:**
- `new_owner_user_id`: ID of user who will become owner
- `confirm_transfer`: Must be true
- `transfer_reason`: Optional reason for audit trail
"""
from datetime import datetime, timezone
vendor, old_owner, new_owner = admin_service.transfer_vendor_ownership(
db, vendor_id, transfer_data
)
return VendorTransferOwnershipResponse(
message="Ownership transferred successfully",
vendor_id=vendor.id,
vendor_code=vendor.vendor_code,
vendor_name=vendor.name,
old_owner={
"id": old_owner.id,
"username": old_owner.username,
"email": old_owner.email,
},
new_owner={
"id": new_owner.id,
"username": new_owner.username,
"email": new_owner.email,
},
transferred_at=datetime.now(timezone.utc),
transfer_reason=transfer_data.transfer_reason,
)
@router.put("/{vendor_id}/verify")
@@ -115,14 +278,14 @@ def delete_vendor(
"""
Delete vendor and all associated data (Admin only).
WARNING: This is destructive and will delete:
⚠️ **WARNING: This is destructive and will delete:**
- Vendor account
- All products
- All orders
- All customers
- All team members
Requires confirmation parameter.
Requires confirmation parameter: `confirm=true`
"""
if not confirm:
raise HTTPException(

128
app/api/v1/public/vendors/vendors.py vendored Normal file
View File

@@ -0,0 +1,128 @@
# app/api/v1/public/vendors/vendors.py
"""
Public vendor information endpoints.
Provides public-facing vendor lookup and information retrieval.
"""
import logging
from fastapi import APIRouter, Depends, HTTPException, Path
from sqlalchemy.orm import Session
from app.core.database import get_db
from models.database.vendor import Vendor
router = APIRouter()
logger = logging.getLogger(__name__)
@router.get("/by-code/{vendor_code}")
def get_vendor_by_code(
vendor_code: str = Path(..., description="Vendor code (e.g., TECHSTORE233)"),
db: Session = Depends(get_db)
):
"""
Get public vendor information by vendor code.
This endpoint is used by:
- Frontend vendor login page to validate vendor existence
- Customer shop to display vendor information
Returns basic vendor information (no sensitive data).
"""
vendor = db.query(Vendor).filter(
Vendor.vendor_code == vendor_code.upper(),
Vendor.is_active == True
).first()
if not vendor:
logger.warning(f"Vendor lookup failed for code: {vendor_code}")
raise HTTPException(
status_code=404,
detail=f"Vendor '{vendor_code}' not found or inactive"
)
logger.info(f"Vendor lookup successful: {vendor.vendor_code}")
# Return public vendor information (no sensitive data)
return {
"id": vendor.id,
"vendor_code": vendor.vendor_code,
"subdomain": vendor.subdomain,
"name": vendor.name,
"description": vendor.description,
"website": vendor.website,
"is_active": vendor.is_active,
"is_verified": vendor.is_verified
}
@router.get("/by-subdomain/{subdomain}")
def get_vendor_by_subdomain(
subdomain: str = Path(..., description="Vendor subdomain (e.g., techstore233)"),
db: Session = Depends(get_db)
):
"""
Get public vendor information by subdomain.
Used for subdomain-based vendor detection in production environments.
Example: techstore233.platform.com -> subdomain is "techstore233"
"""
vendor = db.query(Vendor).filter(
Vendor.subdomain == subdomain.lower(),
Vendor.is_active == True
).first()
if not vendor:
logger.warning(f"Vendor lookup failed for subdomain: {subdomain}")
raise HTTPException(
status_code=404,
detail=f"Vendor with subdomain '{subdomain}' not found or inactive"
)
logger.info(f"Vendor lookup by subdomain successful: {vendor.vendor_code}")
return {
"id": vendor.id,
"vendor_code": vendor.vendor_code,
"subdomain": vendor.subdomain,
"name": vendor.name,
"description": vendor.description,
"website": vendor.website,
"is_active": vendor.is_active,
"is_verified": vendor.is_verified
}
@router.get("/{vendor_id}/info")
def get_vendor_info(
vendor_id: int = Path(..., description="Vendor ID"),
db: Session = Depends(get_db)
):
"""
Get public vendor information by ID.
Used when vendor_id is already known (e.g., from URL parameters).
"""
vendor = db.query(Vendor).filter(
Vendor.id == vendor_id,
Vendor.is_active == True
).first()
if not vendor:
logger.warning(f"Vendor lookup failed for ID: {vendor_id}")
raise HTTPException(
status_code=404,
detail=f"Vendor with ID {vendor_id} not found or inactive"
)
return {
"id": vendor.id,
"vendor_code": vendor.vendor_code,
"subdomain": vendor.subdomain,
"name": vendor.name,
"description": vendor.description,
"website": vendor.website,
"is_active": vendor.is_active,
"is_verified": vendor.is_verified
}

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)