style: apply black and isort formatting across entire codebase

- Standardize quote style (single to double quotes)
- Reorder and group imports alphabetically
- Fix line breaks and indentation for consistency
- Apply PEP 8 formatting standards

Also updated Makefile to exclude both venv and .venv from code quality checks.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-28 19:30:17 +01:00
parent 13f0094743
commit 21c13ca39b
236 changed files with 8450 additions and 6545 deletions

View File

@@ -13,25 +13,9 @@ IMPORTANT:
from fastapi import APIRouter
# Import all sub-routers (JSON API only)
from . import (
info,
auth,
dashboard,
profile,
settings,
products,
orders,
customers,
team,
inventory,
marketplace,
payments,
media,
notifications,
analytics,
content_pages,
)
from . import (analytics, auth, content_pages, customers, dashboard, info,
inventory, marketplace, media, notifications, orders, payments,
products, profile, settings, team)
# Create vendor router
router = APIRouter()
@@ -68,7 +52,11 @@ router.include_router(notifications.router, tags=["vendor-notifications"])
router.include_router(analytics.router, tags=["vendor-analytics"])
# Content pages management
router.include_router(content_pages.router, prefix="/{vendor_code}/content-pages", tags=["vendor-content-pages"])
router.include_router(
content_pages.router,
prefix="/{vendor_code}/content-pages",
tags=["vendor-content-pages"],
)
# Vendor info endpoint - MUST BE LAST! Has catch-all GET /{vendor_code}
router.include_router(info.router, tags=["vendor-info"])

View File

@@ -4,13 +4,14 @@ 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_vendor_api
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.stats_service import stats_service
from middleware.vendor_context import require_vendor_context
from models.database.user import User
from models.database.vendor import Vendor
@@ -20,10 +21,10 @@ 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_vendor_api),
db: Session = Depends(get_db),
period: str = Query("30d", description="Time period: 7d, 30d, 90d, 1y"),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
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

@@ -13,19 +13,20 @@ This prevents:
"""
import logging
from fastapi import APIRouter, Depends, Request, Response
from pydantic import BaseModel
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 UserLogin
from models.database.vendor import Vendor, VendorUser, Role
from models.database.user import User
from pydantic import BaseModel
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.core.environment import should_use_secure_cookies
from app.exceptions import InvalidCredentialsException
from app.services.auth_service import auth_service
from middleware.vendor_context import get_current_vendor
from models.database.user import User
from models.database.vendor import Role, Vendor, VendorUser
from models.schema.auth import UserLogin
router = APIRouter(prefix="/auth")
logger = logging.getLogger(__name__)
@@ -43,10 +44,10 @@ class VendorLoginResponse(BaseModel):
@router.post("/login", response_model=VendorLoginResponse)
def vendor_login(
user_credentials: UserLogin,
request: Request,
response: Response,
db: Session = Depends(get_db)
user_credentials: UserLogin,
request: Request,
response: Response,
db: Session = Depends(get_db),
):
"""
Vendor team member login.
@@ -64,13 +65,16 @@ def vendor_login(
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 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()
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)
@@ -79,7 +83,9 @@ def vendor_login(
# CRITICAL: Prevent admin users from using vendor login
if user.role == "admin":
logger.warning(f"Admin user attempted vendor login: {user.username}")
raise InvalidCredentialsException("Admins cannot access vendor portal. Please use admin portal.")
raise InvalidCredentialsException(
"Admins cannot access vendor portal. Please use admin portal."
)
# Determine vendor and role
vendor_role = "Member"
@@ -92,11 +98,16 @@ def vendor_login(
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()
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
@@ -117,17 +128,14 @@ def vendor_login(
# Check vendor memberships
elif user.vendor_memberships:
active_membership = next(
(vm for vm in user.vendor_memberships if vm.is_active),
None
(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(
"User is not associated with any vendor"
)
raise InvalidCredentialsException("User is not associated with any vendor")
logger.info(
f"Vendor team login successful: {user.username} "
@@ -161,7 +169,7 @@ def vendor_login(
"username": user.username,
"email": user.email,
"role": user.role,
"is_active": user.is_active
"is_active": user.is_active,
},
vendor={
"id": vendor.id,
@@ -169,9 +177,9 @@ def vendor_login(
"subdomain": vendor.subdomain,
"name": vendor.name,
"is_active": vendor.is_active,
"is_verified": vendor.is_verified
"is_verified": vendor.is_verified,
},
vendor_role=vendor_role
vendor_role=vendor_role,
)
@@ -198,8 +206,7 @@ def vendor_logout(response: Response):
@router.get("/me")
def get_current_vendor_user(
user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db)
user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db)
):
"""
Get current authenticated vendor user.
@@ -212,5 +219,5 @@ def get_current_vendor_user(
"username": user.username,
"email": user.email,
"role": user.role,
"is_active": user.is_active
"is_active": user.is_active,
}

View File

@@ -10,6 +10,7 @@ Vendors can:
import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session
@@ -26,14 +27,26 @@ logger = logging.getLogger(__name__)
# REQUEST/RESPONSE SCHEMAS
# ============================================================================
class VendorContentPageCreate(BaseModel):
"""Schema for creating a vendor content page."""
slug: str = Field(..., max_length=100, description="URL-safe identifier (about, faq, contact, etc.)")
slug: str = Field(
...,
max_length=100,
description="URL-safe identifier (about, faq, contact, etc.)",
)
title: str = Field(..., max_length=200, description="Page title")
content: str = Field(..., description="HTML or Markdown content")
content_format: str = Field(default="html", description="Content format: html or markdown")
meta_description: Optional[str] = Field(None, max_length=300, description="SEO meta description")
meta_keywords: Optional[str] = Field(None, max_length=300, description="SEO keywords")
content_format: str = Field(
default="html", description="Content format: html or markdown"
)
meta_description: Optional[str] = Field(
None, max_length=300, description="SEO meta description"
)
meta_keywords: Optional[str] = Field(
None, max_length=300, description="SEO keywords"
)
is_published: bool = Field(default=False, description="Publish immediately")
show_in_footer: bool = Field(default=True, description="Show in footer navigation")
show_in_header: bool = Field(default=False, description="Show in header navigation")
@@ -42,6 +55,7 @@ class VendorContentPageCreate(BaseModel):
class VendorContentPageUpdate(BaseModel):
"""Schema for updating a vendor content page."""
title: Optional[str] = Field(None, max_length=200)
content: Optional[str] = None
content_format: Optional[str] = None
@@ -55,6 +69,7 @@ class VendorContentPageUpdate(BaseModel):
class ContentPageResponse(BaseModel):
"""Schema for content page response."""
id: int
vendor_id: Optional[int]
vendor_name: Optional[str]
@@ -81,11 +96,12 @@ class ContentPageResponse(BaseModel):
# VENDOR CONTENT PAGES
# ============================================================================
@router.get("/", response_model=List[ContentPageResponse])
def list_vendor_pages(
include_unpublished: bool = Query(False, description="Include draft pages"),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db)
db: Session = Depends(get_db),
):
"""
List all content pages available for this vendor.
@@ -93,12 +109,12 @@ def list_vendor_pages(
Returns vendor-specific overrides + platform defaults (vendor overrides take precedence).
"""
if not current_user.vendor_id:
raise HTTPException(status_code=403, detail="User is not associated with a vendor")
raise HTTPException(
status_code=403, detail="User is not associated with a vendor"
)
pages = content_page_service.list_pages_for_vendor(
db,
vendor_id=current_user.vendor_id,
include_unpublished=include_unpublished
db, vendor_id=current_user.vendor_id, include_unpublished=include_unpublished
)
return [page.to_dict() for page in pages]
@@ -108,7 +124,7 @@ def list_vendor_pages(
def list_vendor_overrides(
include_unpublished: bool = Query(False, description="Include draft pages"),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db)
db: Session = Depends(get_db),
):
"""
List only vendor-specific content pages (no platform defaults).
@@ -116,12 +132,12 @@ def list_vendor_overrides(
Shows what the vendor has customized.
"""
if not current_user.vendor_id:
raise HTTPException(status_code=403, detail="User is not associated with a vendor")
raise HTTPException(
status_code=403, detail="User is not associated with a vendor"
)
pages = content_page_service.list_all_vendor_pages(
db,
vendor_id=current_user.vendor_id,
include_unpublished=include_unpublished
db, vendor_id=current_user.vendor_id, include_unpublished=include_unpublished
)
return [page.to_dict() for page in pages]
@@ -132,7 +148,7 @@ def get_page(
slug: str,
include_unpublished: bool = Query(False, description="Include draft pages"),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db)
db: Session = Depends(get_db),
):
"""
Get a specific content page by slug.
@@ -140,13 +156,15 @@ def get_page(
Returns vendor override if exists, otherwise platform default.
"""
if not current_user.vendor_id:
raise HTTPException(status_code=403, detail="User is not associated with a vendor")
raise HTTPException(
status_code=403, detail="User is not associated with a vendor"
)
page = content_page_service.get_page_for_vendor(
db,
slug=slug,
vendor_id=current_user.vendor_id,
include_unpublished=include_unpublished
include_unpublished=include_unpublished,
)
if not page:
@@ -159,7 +177,7 @@ def get_page(
def create_vendor_page(
page_data: VendorContentPageCreate,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db)
db: Session = Depends(get_db),
):
"""
Create a vendor-specific content page override.
@@ -167,7 +185,9 @@ def create_vendor_page(
This will be shown instead of the platform default for this vendor.
"""
if not current_user.vendor_id:
raise HTTPException(status_code=403, detail="User is not associated with a vendor")
raise HTTPException(
status_code=403, detail="User is not associated with a vendor"
)
page = content_page_service.create_page(
db,
@@ -182,7 +202,7 @@ def create_vendor_page(
show_in_footer=page_data.show_in_footer,
show_in_header=page_data.show_in_header,
display_order=page_data.display_order,
created_by=current_user.id
created_by=current_user.id,
)
return page.to_dict()
@@ -193,7 +213,7 @@ def update_vendor_page(
page_id: int,
page_data: VendorContentPageUpdate,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db)
db: Session = Depends(get_db),
):
"""
Update a vendor-specific content page.
@@ -201,7 +221,9 @@ def update_vendor_page(
Can only update pages owned by this vendor.
"""
if not current_user.vendor_id:
raise HTTPException(status_code=403, detail="User is not associated with a vendor")
raise HTTPException(
status_code=403, detail="User is not associated with a vendor"
)
# Verify ownership
existing_page = content_page_service.get_page_by_id(db, page_id)
@@ -209,7 +231,9 @@ def update_vendor_page(
raise HTTPException(status_code=404, detail="Content page not found")
if existing_page.vendor_id != current_user.vendor_id:
raise HTTPException(status_code=403, detail="Cannot edit pages from other vendors")
raise HTTPException(
status_code=403, detail="Cannot edit pages from other vendors"
)
# Update
page = content_page_service.update_page(
@@ -224,7 +248,7 @@ def update_vendor_page(
show_in_footer=page_data.show_in_footer,
show_in_header=page_data.show_in_header,
display_order=page_data.display_order,
updated_by=current_user.id
updated_by=current_user.id,
)
return page.to_dict()
@@ -234,7 +258,7 @@ def update_vendor_page(
def delete_vendor_page(
page_id: int,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db)
db: Session = Depends(get_db),
):
"""
Delete a vendor-specific content page.
@@ -243,7 +267,9 @@ def delete_vendor_page(
After deletion, platform default will be shown (if exists).
"""
if not current_user.vendor_id:
raise HTTPException(status_code=403, detail="User is not associated with a vendor")
raise HTTPException(
status_code=403, detail="User is not associated with a vendor"
)
# Verify ownership
existing_page = content_page_service.get_page_by_id(db, page_id)
@@ -251,7 +277,9 @@ def delete_vendor_page(
raise HTTPException(status_code=404, detail="Content page not found")
if existing_page.vendor_id != current_user.vendor_id:
raise HTTPException(status_code=403, detail="Cannot delete pages from other vendors")
raise HTTPException(
status_code=403, detail="Cannot delete pages from other vendors"
)
# Delete
content_page_service.delete_page(db, page_id)

View File

@@ -6,6 +6,7 @@ Vendor customer management endpoints.
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
@@ -21,13 +22,13 @@ 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_vendor_api),
db: Session = Depends(get_db),
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_vendor_api),
db: Session = Depends(get_db),
):
"""
Get all customers for this vendor.
@@ -43,16 +44,16 @@ def get_vendor_customers(
"total": 0,
"skip": skip,
"limit": limit,
"message": "Customer management coming in Slice 4"
"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_vendor_api),
db: Session = Depends(get_db),
customer_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get detailed customer information.
@@ -63,17 +64,15 @@ def get_customer_details(
- Include order history
- Include total spent, etc.
"""
return {
"message": "Customer details coming in Slice 4"
}
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_vendor_api),
db: Session = Depends(get_db),
customer_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get order history for a specific customer.
@@ -83,19 +82,16 @@ def get_customer_orders(
- Filter by vendor_id
- Return order details
"""
return {
"orders": [],
"message": "Customer orders coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
customer_id: int,
customer_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Update customer information.
@@ -105,17 +101,15 @@ def update_customer(
- Verify customer belongs to vendor
- Update customer preferences
"""
return {
"message": "Customer update coming in Slice 4"
}
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_vendor_api),
db: Session = Depends(get_db),
customer_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Activate/deactivate customer account.
@@ -125,17 +119,15 @@ def toggle_customer_status(
- Verify customer belongs to vendor
- Log the change
"""
return {
"message": "Customer status toggle coming in Slice 4"
}
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_vendor_api),
db: Session = Depends(get_db),
customer_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get customer statistics and metrics.
@@ -151,6 +143,5 @@ def get_customer_statistics(
"total_spent": 0.0,
"average_order_value": 0.0,
"last_order_date": None,
"message": "Customer statistics coming in Slice 4"
"message": "Customer statistics coming in Slice 4",
}

View File

@@ -4,13 +4,14 @@ Vendor dashboard and statistics endpoints.
"""
import logging
from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.stats_service import stats_service
from middleware.vendor_context import require_vendor_context
from models.database.user import User
from models.database.vendor import Vendor
@@ -20,9 +21,9 @@ logger = logging.getLogger(__name__)
@router.get("/stats")
def get_vendor_dashboard_stats(
request: Request,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
request: Request,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get vendor-specific dashboard statistics.
@@ -38,24 +39,23 @@ def get_vendor_dashboard_stats(
"""
# Get vendor from authenticated user's vendor_user record
from models.database.vendor import VendorUser
vendor_user = db.query(VendorUser).filter(
VendorUser.user_id == current_user.id
).first()
vendor_user = (
db.query(VendorUser).filter(VendorUser.user_id == current_user.id).first()
)
if not vendor_user:
from fastapi import HTTPException
raise HTTPException(
status_code=403,
detail="User is not associated with any vendor"
status_code=403, detail="User is not associated with any vendor"
)
vendor = vendor_user.vendor
if not vendor or not vendor.is_active:
from fastapi import HTTPException
raise HTTPException(
status_code=404,
detail="Vendor not found or inactive"
)
raise HTTPException(status_code=404, detail="Vendor not found or inactive")
# Get vendor-scoped statistics
stats_data = stats_service.get_vendor_stats(db=db, vendor_id=vendor.id)
@@ -82,5 +82,5 @@ def get_vendor_dashboard_stats(
"revenue": {
"total": stats_data.get("total_revenue", 0),
"this_month": stats_data.get("revenue_this_month", 0),
}
},
}

View File

@@ -8,14 +8,15 @@ This module provides:
"""
import logging
from fastapi import APIRouter, Path, Depends
from sqlalchemy.orm import Session
from fastapi import APIRouter, Depends, Path
from sqlalchemy import func
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.exceptions import VendorNotFoundException
from models.schema.vendor import VendorResponse, VendorDetailResponse
from models.database.vendor import Vendor
from models.schema.vendor import VendorDetailResponse, VendorResponse
router = APIRouter()
logger = logging.getLogger(__name__)
@@ -35,10 +36,14 @@ def _get_vendor_by_code(db: Session, vendor_code: str) -> Vendor:
Raises:
VendorNotFoundException: If vendor not found or inactive
"""
vendor = db.query(Vendor).filter(
func.upper(Vendor.vendor_code) == vendor_code.upper(),
Vendor.is_active == True
).first()
vendor = (
db.query(Vendor)
.filter(
func.upper(Vendor.vendor_code) == vendor_code.upper(),
Vendor.is_active == True,
)
.first()
)
if not vendor:
logger.warning(f"Vendor not found or inactive: {vendor_code}")
@@ -49,8 +54,8 @@ def _get_vendor_by_code(db: Session, vendor_code: str) -> Vendor:
@router.get("/{vendor_code}", response_model=VendorDetailResponse)
def get_vendor_info(
vendor_code: str = Path(..., description="Vendor code"),
db: Session = Depends(get_db)
vendor_code: str = Path(..., description="Vendor code"),
db: Session = Depends(get_db),
):
"""
Get public vendor information by vendor code.

View File

@@ -7,19 +7,14 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.inventory_service import inventory_service
from models.schema.inventory import (
InventoryCreate,
InventoryAdjust,
InventoryUpdate,
InventoryReserve,
InventoryResponse,
ProductInventorySummary,
InventoryListResponse
)
from middleware.vendor_context import require_vendor_context
from models.database.user import User
from models.database.vendor import Vendor
from models.schema.inventory import (InventoryAdjust, InventoryCreate,
InventoryListResponse, InventoryReserve,
InventoryResponse, InventoryUpdate,
ProductInventorySummary)
router = APIRouter()
logger = logging.getLogger(__name__)
@@ -27,10 +22,10 @@ logger = logging.getLogger(__name__)
@router.post("/inventory/set", response_model=InventoryResponse)
def set_inventory(
inventory: InventoryCreate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
inventory: InventoryCreate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Set exact inventory quantity (replaces existing)."""
return inventory_service.set_inventory(db, vendor.id, inventory)
@@ -38,10 +33,10 @@ def set_inventory(
@router.post("/inventory/adjust", response_model=InventoryResponse)
def adjust_inventory(
adjustment: InventoryAdjust,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
adjustment: InventoryAdjust,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Adjust inventory (positive to add, negative to remove)."""
return inventory_service.adjust_inventory(db, vendor.id, adjustment)
@@ -49,10 +44,10 @@ def adjust_inventory(
@router.post("/inventory/reserve", response_model=InventoryResponse)
def reserve_inventory(
reservation: InventoryReserve,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
reservation: InventoryReserve,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Reserve inventory for an order."""
return inventory_service.reserve_inventory(db, vendor.id, reservation)
@@ -60,10 +55,10 @@ def reserve_inventory(
@router.post("/inventory/release", response_model=InventoryResponse)
def release_reservation(
reservation: InventoryReserve,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
reservation: InventoryReserve,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Release reserved inventory (cancel order)."""
return inventory_service.release_reservation(db, vendor.id, reservation)
@@ -71,10 +66,10 @@ def release_reservation(
@router.post("/inventory/fulfill", response_model=InventoryResponse)
def fulfill_reservation(
reservation: InventoryReserve,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
reservation: InventoryReserve,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Fulfill reservation (complete order, remove from stock)."""
return inventory_service.fulfill_reservation(db, vendor.id, reservation)
@@ -82,10 +77,10 @@ def fulfill_reservation(
@router.get("/inventory/product/{product_id}", response_model=ProductInventorySummary)
def get_product_inventory(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get inventory summary for a product."""
return inventory_service.get_product_inventory(db, vendor.id, product_id)
@@ -93,13 +88,13 @@ def get_product_inventory(
@router.get("/inventory", response_model=InventoryListResponse)
def get_vendor_inventory(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
location: Optional[str] = Query(None),
low_stock: Optional[int] = Query(None, ge=0),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
location: Optional[str] = Query(None),
low_stock: Optional[int] = Query(None, ge=0),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get all inventory for vendor."""
inventories = inventory_service.get_vendor_inventory(
@@ -110,31 +105,30 @@ def get_vendor_inventory(
total = len(inventories) # You might want a separate count query for large datasets
return InventoryListResponse(
inventories=inventories,
total=total,
skip=skip,
limit=limit
inventories=inventories, total=total, skip=skip, limit=limit
)
@router.put("/inventory/{inventory_id}", response_model=InventoryResponse)
def update_inventory(
inventory_id: int,
inventory_update: InventoryUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
inventory_id: int,
inventory_update: InventoryUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Update inventory entry."""
return inventory_service.update_inventory(db, vendor.id, inventory_id, inventory_update)
return inventory_service.update_inventory(
db, vendor.id, inventory_id, inventory_update
)
@router.delete("/inventory/{inventory_id}")
def delete_inventory(
inventory_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
inventory_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Delete inventory entry."""
inventory_service.delete_inventory(db, vendor.id, inventory_id)

View File

@@ -12,16 +12,15 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context # IMPORTANT
from app.services.marketplace_import_job_service import marketplace_import_job_service
from app.services.marketplace_import_job_service import \
marketplace_import_job_service
from app.tasks.background_tasks import process_marketplace_import
from middleware.decorators import rate_limit
from models.schema.marketplace_import_job import (
MarketplaceImportJobResponse,
MarketplaceImportJobRequest
)
from middleware.vendor_context import require_vendor_context # IMPORTANT
from models.database.user import User
from models.database.vendor import Vendor
from models.schema.marketplace_import_job import (MarketplaceImportJobRequest,
MarketplaceImportJobResponse)
router = APIRouter()
logger = logging.getLogger(__name__)
@@ -30,11 +29,11 @@ logger = logging.getLogger(__name__)
@router.post("/import", response_model=MarketplaceImportJobResponse)
@rate_limit(max_requests=10, window_seconds=3600)
async def import_products_from_marketplace(
request: MarketplaceImportJobRequest,
background_tasks: BackgroundTasks,
vendor: Vendor = Depends(require_vendor_context()), # ADDED: Vendor from middleware
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
request: MarketplaceImportJobRequest,
background_tasks: BackgroundTasks,
vendor: Vendor = Depends(require_vendor_context()), # ADDED: Vendor from middleware
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Import products from marketplace CSV with background processing (Protected)."""
logger.info(
@@ -66,7 +65,7 @@ async def import_products_from_marketplace(
vendor_name=vendor.name, # FIXED: from vendor object
source_url=request.source_url,
message=f"Marketplace import started from {request.marketplace}. "
f"Check status with /import-status/{import_job.id}",
f"Check status with /import-status/{import_job.id}",
imported=0,
updated=0,
total_processed=0,
@@ -77,10 +76,10 @@ async def import_products_from_marketplace(
@router.get("/imports/{job_id}", response_model=MarketplaceImportJobResponse)
def get_marketplace_import_status(
job_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
job_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get status of marketplace import job (Protected)."""
job = marketplace_import_job_service.get_import_job_by_id(db, job_id, current_user)
@@ -88,6 +87,7 @@ def get_marketplace_import_status(
# Verify job belongs to current vendor
if job.vendor_id != vendor.id:
from app.exceptions import UnauthorizedVendorAccessException
raise UnauthorizedVendorAccessException(vendor.vendor_code, current_user.id)
return marketplace_import_job_service.convert_to_response_model(job)
@@ -95,12 +95,12 @@ def get_marketplace_import_status(
@router.get("/imports", response_model=List[MarketplaceImportJobResponse])
def get_marketplace_import_jobs(
marketplace: Optional[str] = Query(None, description="Filter by marketplace"),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
marketplace: Optional[str] = Query(None, description="Filter by marketplace"),
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get marketplace import jobs for current vendor (Protected)."""
jobs = marketplace_import_job_service.get_import_jobs(
@@ -112,4 +112,6 @@ def get_marketplace_import_jobs(
limit=limit,
)
return [marketplace_import_job_service.convert_to_response_model(job) for job in jobs]
return [
marketplace_import_job_service.convert_to_response_model(job) for job in jobs
]

View File

@@ -6,7 +6,8 @@ Vendor media and file management endpoints.
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query, UploadFile, File
from fastapi import APIRouter, Depends, File, Query, UploadFile
from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
@@ -21,13 +22,13 @@ 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_vendor_api),
db: Session = Depends(get_db),
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_vendor_api),
db: Session = Depends(get_db),
):
"""
Get vendor media library.
@@ -44,17 +45,17 @@ def get_media_library(
"total": 0,
"skip": skip,
"limit": limit,
"message": "Media library coming in Slice 3"
"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_vendor_api),
db: Session = Depends(get_db),
file: UploadFile = File(...),
folder: Optional[str] = Query(None, description="products, general, etc."),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Upload media file.
@@ -70,17 +71,17 @@ async def upload_media(
return {
"file_url": None,
"thumbnail_url": None,
"message": "Media upload coming in Slice 3"
"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_vendor_api),
db: Session = Depends(get_db),
files: list[UploadFile] = File(...),
folder: Optional[str] = Query(None),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Upload multiple media files at once.
@@ -94,16 +95,16 @@ async def upload_multiple_media(
return {
"uploaded_files": [],
"failed_files": [],
"message": "Multiple upload coming in Slice 3"
"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_vendor_api),
db: Session = Depends(get_db),
media_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get media file details.
@@ -113,18 +114,16 @@ def get_media_details(
- Return file URL
- Return usage information (which products use this file)
"""
return {
"message": "Media details coming in Slice 3"
}
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_vendor_api),
db: Session = Depends(get_db),
media_id: int,
metadata: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Update media file metadata.
@@ -135,17 +134,15 @@ def update_media_metadata(
- Update tags/categories
- Update description
"""
return {
"message": "Media update coming in Slice 3"
}
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_vendor_api),
db: Session = Depends(get_db),
media_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Delete media file.
@@ -157,17 +154,15 @@ def delete_media(
- Delete database record
- Return success/error
"""
return {
"message": "Media deletion coming in Slice 3"
}
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_vendor_api),
db: Session = Depends(get_db),
media_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get where this media file is being used.
@@ -180,16 +175,16 @@ def get_media_usage(
return {
"products": [],
"other_usage": [],
"message": "Media usage tracking coming in Slice 3"
"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_vendor_api),
db: Session = Depends(get_db),
media_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Optimize media file (compress, resize, etc.).
@@ -200,7 +195,4 @@ def optimize_media(
- Keep original
- Update database with new versions
"""
return {
"message": "Media optimization coming in Slice 3"
}
return {"message": "Media optimization coming in Slice 3"}

View File

@@ -1,4 +1,4 @@
# Notification management
# Notification management
# app/api/v1/vendor/notifications.py
"""
Vendor notification management endpoints.
@@ -6,6 +6,7 @@ Vendor notification management endpoints.
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
@@ -21,12 +22,12 @@ 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_vendor_api),
db: Session = Depends(get_db),
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_vendor_api),
db: Session = Depends(get_db),
):
"""
Get vendor notifications.
@@ -41,15 +42,15 @@ def get_notifications(
"notifications": [],
"total": 0,
"unread_count": 0,
"message": "Notifications coming in Slice 5"
"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_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get count of unread notifications.
@@ -58,18 +59,15 @@ def get_unread_count(
- Count unread notifications for vendor
- Used for notification badge
"""
return {
"unread_count": 0,
"message": "Unread count coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
notification_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Mark notification as read.
@@ -78,16 +76,14 @@ def mark_as_read(
- Mark single notification as read
- Update read timestamp
"""
return {
"message": "Mark as read coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Mark all notifications as read.
@@ -96,17 +92,15 @@ def mark_all_as_read(
- Mark all vendor notifications as read
- Update timestamps
"""
return {
"message": "Mark all as read coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
notification_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Delete notification.
@@ -115,16 +109,14 @@ def delete_notification(
- Delete single notification
- Verify notification belongs to vendor
"""
return {
"message": "Notification deletion coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get notification preferences.
@@ -138,16 +130,16 @@ def get_notification_settings(
"email_notifications": True,
"in_app_notifications": True,
"notification_types": {},
"message": "Notification settings coming in Slice 5"
"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_vendor_api),
db: Session = Depends(get_db),
settings: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Update notification preferences.
@@ -157,16 +149,14 @@ def update_notification_settings(
- Update in-app notification settings
- Enable/disable specific notification types
"""
return {
"message": "Notification settings update coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get notification email templates.
@@ -176,19 +166,16 @@ def get_notification_templates(
- Include: order confirmation, shipping notification, etc.
- Return template details
"""
return {
"templates": [],
"message": "Notification templates coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
template_id: int,
template_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Update notification email template.
@@ -199,17 +186,15 @@ def update_notification_template(
- Validate template variables
- Preview template
"""
return {
"message": "Template update coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
notification_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Send test notification.
@@ -219,6 +204,4 @@ def send_test_notification(
- Use specified template
- Send to current user's email
"""
return {
"message": "Test notification coming in Slice 5"
}
return {"message": "Test notification coming in Slice 5"}

View File

@@ -6,21 +6,17 @@ Vendor order management endpoints.
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query, Request, HTTPException
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.order_service import order_service
from models.schema.order import (
OrderResponse,
OrderDetailResponse,
OrderListResponse,
OrderUpdate
)
from middleware.vendor_context import require_vendor_context
from models.database.user import User
from models.database.vendor import Vendor, VendorUser
from models.schema.order import (OrderDetailResponse, OrderListResponse,
OrderResponse, OrderUpdate)
router = APIRouter(prefix="/orders")
logger = logging.getLogger(__name__)
@@ -28,13 +24,13 @@ logger = logging.getLogger(__name__)
@router.get("", response_model=OrderListResponse)
def get_vendor_orders(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
status: Optional[str] = Query(None, description="Filter by order status"),
customer_id: Optional[int] = Query(None, description="Filter by customer"),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
status: Optional[str] = Query(None, description="Filter by order status"),
customer_id: Optional[int] = Query(None, description="Filter by customer"),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get all orders for vendor.
@@ -51,45 +47,41 @@ def get_vendor_orders(
skip=skip,
limit=limit,
status=status,
customer_id=customer_id
customer_id=customer_id,
)
return OrderListResponse(
orders=[OrderResponse.model_validate(o) for o in orders],
total=total,
skip=skip,
limit=limit
limit=limit,
)
@router.get("/{order_id}", response_model=OrderDetailResponse)
def get_order_details(
order_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
order_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get detailed order information including items and addresses.
Requires Authorization header (API endpoint).
"""
order = order_service.get_order(
db=db,
vendor_id=vendor.id,
order_id=order_id
)
order = order_service.get_order(db=db, vendor_id=vendor.id, order_id=order_id)
return OrderDetailResponse.model_validate(order)
@router.put("/{order_id}/status", response_model=OrderResponse)
def update_order_status(
order_id: int,
order_update: OrderUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
order_id: int,
order_update: OrderUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Update order status and tracking information.
@@ -105,10 +97,7 @@ def update_order_status(
Requires Authorization header (API endpoint).
"""
order = order_service.update_order_status(
db=db,
vendor_id=vendor.id,
order_id=order_id,
order_update=order_update
db=db, vendor_id=vendor.id, order_id=order_id, order_update=order_update
)
logger.info(

View File

@@ -1,10 +1,11 @@
# Payment configuration and processing
# 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
@@ -20,9 +21,9 @@ logger = logging.getLogger(__name__)
@router.get("/config")
def get_payment_configuration(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get vendor payment configuration.
@@ -38,16 +39,16 @@ def get_payment_configuration(
"accepted_methods": [],
"currency": "EUR",
"stripe_connected": False,
"message": "Payment configuration coming in Slice 5"
"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_vendor_api),
db: Session = Depends(get_db),
payment_config: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Update vendor payment configuration.
@@ -58,17 +59,15 @@ def update_payment_configuration(
- Update accepted payment methods
- Validate configuration before saving
"""
return {
"message": "Payment configuration update coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
stripe_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Connect Stripe account for payment processing.
@@ -79,16 +78,14 @@ def connect_stripe_account(
- Verify Stripe account is active
- Enable payment processing
"""
return {
"message": "Stripe connection coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Disconnect Stripe account.
@@ -98,16 +95,14 @@ def disconnect_stripe_account(
- Disable payment processing
- Warn about pending payments
"""
return {
"message": "Stripe disconnection coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get accepted payment methods for vendor.
@@ -116,17 +111,14 @@ def get_payment_methods(
- Return list of enabled payment methods
- Include: credit card, PayPal, bank transfer, etc.
"""
return {
"methods": [],
"message": "Payment methods coming in Slice 5"
}
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_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get payment transaction history.
@@ -140,15 +132,15 @@ def get_payment_transactions(
return {
"transactions": [],
"total": 0,
"message": "Payment transactions coming in Slice 5"
"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_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get vendor payment balance and payout information.
@@ -164,17 +156,17 @@ def get_payment_balance(
"pending_balance": 0.0,
"currency": "EUR",
"next_payout_date": None,
"message": "Payment balance coming in Slice 5"
"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_vendor_api),
db: Session = Depends(get_db),
payment_id: int,
refund_data: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Process payment refund.
@@ -185,6 +177,4 @@ def refund_payment(
- Update order status
- Send refund notification to customer
"""
return {
"message": "Payment refund coming in Slice 5"
}
return {"message": "Payment refund coming in Slice 5"}

View File

@@ -11,17 +11,13 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.product_service import product_service
from models.schema.product import (
ProductCreate,
ProductUpdate,
ProductResponse,
ProductDetailResponse,
ProductListResponse
)
from middleware.vendor_context import require_vendor_context
from models.database.user import User
from models.database.vendor import Vendor
from models.schema.product import (ProductCreate, ProductDetailResponse,
ProductListResponse, ProductResponse,
ProductUpdate)
router = APIRouter(prefix="/products")
logger = logging.getLogger(__name__)
@@ -29,13 +25,13 @@ logger = logging.getLogger(__name__)
@router.get("", response_model=ProductListResponse)
def get_vendor_products(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
is_active: Optional[bool] = Query(None),
is_featured: Optional[bool] = Query(None),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
is_active: Optional[bool] = Query(None),
is_featured: Optional[bool] = Query(None),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get all products in vendor catalog.
@@ -50,29 +46,27 @@ def get_vendor_products(
skip=skip,
limit=limit,
is_active=is_active,
is_featured=is_featured
is_featured=is_featured,
)
return ProductListResponse(
products=[ProductResponse.model_validate(p) for p in products],
total=total,
skip=skip,
limit=limit
limit=limit,
)
@router.get("/{product_id}", response_model=ProductDetailResponse)
def get_product_details(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get detailed product information including inventory."""
product = product_service.get_product(
db=db,
vendor_id=vendor.id,
product_id=product_id
db=db, vendor_id=vendor.id, product_id=product_id
)
return ProductDetailResponse.model_validate(product)
@@ -80,10 +74,10 @@ def get_product_details(
@router.post("", response_model=ProductResponse)
def add_product_to_catalog(
product_data: ProductCreate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
product_data: ProductCreate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Add a product from marketplace to vendor catalog.
@@ -91,9 +85,7 @@ def add_product_to_catalog(
This publishes a MarketplaceProduct to the vendor's public catalog.
"""
product = product_service.create_product(
db=db,
vendor_id=vendor.id,
product_data=product_data
db=db, vendor_id=vendor.id, product_data=product_data
)
logger.info(
@@ -106,18 +98,15 @@ def add_product_to_catalog(
@router.put("/{product_id}", response_model=ProductResponse)
def update_product(
product_id: int,
product_data: ProductUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
product_id: int,
product_data: ProductUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Update product in vendor catalog."""
product = product_service.update_product(
db=db,
vendor_id=vendor.id,
product_id=product_id,
product_update=product_data
db=db, vendor_id=vendor.id, product_id=product_id, product_update=product_data
)
logger.info(
@@ -130,17 +119,13 @@ def update_product(
@router.delete("/{product_id}")
def remove_product_from_catalog(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Remove product from vendor catalog."""
product_service.delete_product(
db=db,
vendor_id=vendor.id,
product_id=product_id
)
product_service.delete_product(db=db, vendor_id=vendor.id, product_id=product_id)
logger.info(
f"Product {product_id} removed from catalog by user {current_user.username} "
@@ -152,10 +137,10 @@ def remove_product_from_catalog(
@router.post("/from-import/{marketplace_product_id}", response_model=ProductResponse)
def publish_from_marketplace(
marketplace_product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
marketplace_product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Publish a marketplace product to vendor catalog.
@@ -163,14 +148,11 @@ def publish_from_marketplace(
Shortcut endpoint for publishing directly from marketplace import.
"""
product_data = ProductCreate(
marketplace_product_id=marketplace_product_id,
is_active=True
marketplace_product_id=marketplace_product_id, is_active=True
)
product = product_service.create_product(
db=db,
vendor_id=vendor.id,
product_data=product_data
db=db, vendor_id=vendor.id, product_data=product_data
)
logger.info(
@@ -183,10 +165,10 @@ def publish_from_marketplace(
@router.put("/{product_id}/toggle-active")
def toggle_product_active(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Toggle product active status."""
product = product_service.get_product(db, vendor.id, product_id)
@@ -198,18 +180,15 @@ def toggle_product_active(
status = "activated" if product.is_active else "deactivated"
logger.info(f"Product {product_id} {status} for vendor {vendor.vendor_code}")
return {
"message": f"Product {status}",
"is_active": product.is_active
}
return {"message": f"Product {status}", "is_active": product.is_active}
@router.put("/{product_id}/toggle-featured")
def toggle_product_featured(
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
product_id: int,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Toggle product featured status."""
product = product_service.get_product(db, vendor.id, product_id)
@@ -221,7 +200,4 @@ def toggle_product_featured(
status = "featured" if product.is_featured else "unfeatured"
logger.info(f"Product {product_id} {status} for vendor {vendor.vendor_code}")
return {
"message": f"Product {status}",
"is_featured": product.is_featured
}
return {"message": f"Product {status}", "is_featured": product.is_featured}

View File

@@ -4,16 +4,17 @@ Vendor profile management endpoints.
"""
import logging
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
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 middleware.vendor_context import require_vendor_context
from models.database.user import User
from models.database.vendor import Vendor
from models.schema.vendor import VendorResponse, VendorUpdate
router = APIRouter(prefix="/profile")
logger = logging.getLogger(__name__)
@@ -21,9 +22,9 @@ 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_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get current vendor profile information."""
return vendor
@@ -31,10 +32,10 @@ def get_vendor_profile(
@router.put("", response_model=VendorResponse)
def update_vendor_profile(
vendor_update: VendorUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
vendor_update: VendorUpdate,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Update vendor profile information."""
# Verify user has permission to update vendor

View File

@@ -4,13 +4,14 @@ 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_vendor_api
from app.core.database import get_db
from middleware.vendor_context import require_vendor_context
from app.services.vendor_service import vendor_service
from middleware.vendor_context import require_vendor_context
from models.database.user import User
from models.database.vendor import Vendor
@@ -20,9 +21,9 @@ logger = logging.getLogger(__name__)
@router.get("")
def get_vendor_settings(
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get vendor settings and configuration."""
return {
@@ -44,10 +45,10 @@ def get_vendor_settings(
@router.put("/marketplace")
def update_marketplace_settings(
marketplace_config: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
marketplace_config: dict,
vendor: Vendor = Depends(require_vendor_context()),
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Update marketplace integration settings."""
# Verify permissions

View File

@@ -12,35 +12,24 @@ Implements complete team management with:
import logging
from typing import List
from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session
from app.api.deps import (get_current_vendor_api, get_user_permissions,
require_vendor_owner, require_vendor_permission)
from app.core.database import get_db
from app.core.permissions import VendorPermissions
from app.api.deps import (
get_current_vendor_api,
require_vendor_owner,
require_vendor_permission,
get_user_permissions
)
from app.services.vendor_team_service import vendor_team_service
from models.database.user import User
from models.database.vendor import Vendor
from models.schema.team import (
TeamMemberInvite,
TeamMemberUpdate,
TeamMemberResponse,
TeamMemberListResponse,
InvitationAccept,
InvitationResponse,
InvitationAcceptResponse,
RoleResponse,
RoleListResponse,
UserPermissionsResponse,
TeamStatistics,
BulkRemoveRequest,
BulkRemoveResponse,
)
from models.schema.team import (BulkRemoveRequest, BulkRemoveResponse,
InvitationAccept, InvitationAcceptResponse,
InvitationResponse, RoleListResponse,
RoleResponse, TeamMemberInvite,
TeamMemberListResponse, TeamMemberResponse,
TeamMemberUpdate, TeamStatistics,
UserPermissionsResponse)
router = APIRouter(prefix="/team")
logger = logging.getLogger(__name__)
@@ -50,14 +39,15 @@ logger = logging.getLogger(__name__)
# Team Member Routes
# ============================================================================
@router.get("/members", response_model=TeamMemberListResponse)
def list_team_members(
request: Request,
include_inactive: bool = False,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_permission(
VendorPermissions.TEAM_VIEW.value
))
request: Request,
include_inactive: bool = False,
db: Session = Depends(get_db),
current_user: User = Depends(
require_vendor_permission(VendorPermissions.TEAM_VIEW.value)
),
):
"""
Get all team members for current vendor.
@@ -74,9 +64,7 @@ def list_team_members(
vendor = request.state.vendor
members = vendor_team_service.get_team_members(
db=db,
vendor=vendor,
include_inactive=include_inactive
db=db, vendor=vendor, include_inactive=include_inactive
)
# Calculate statistics
@@ -90,19 +78,16 @@ def list_team_members(
)
return TeamMemberListResponse(
members=members,
total=total,
active_count=active,
pending_invitations=pending
members=members, total=total, active_count=active, pending_invitations=pending
)
@router.post("/invite", response_model=InvitationResponse)
def invite_team_member(
invitation: TeamMemberInvite,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_owner) # Owner only
invitation: TeamMemberInvite,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_owner), # Owner only
):
"""
Invite a new team member to the vendor.
@@ -135,7 +120,7 @@ def invite_team_member(
vendor=vendor,
inviter=current_user,
email=invitation.email,
role_id=invitation.role_id
role_id=invitation.role_id,
)
elif invitation.role_name:
# Use role name with optional custom permissions
@@ -145,7 +130,7 @@ def invite_team_member(
inviter=current_user,
email=invitation.email,
role_name=invitation.role_name,
custom_permissions=invitation.custom_permissions
custom_permissions=invitation.custom_permissions,
)
else:
# Default to Staff role
@@ -154,7 +139,7 @@ def invite_team_member(
vendor=vendor,
inviter=current_user,
email=invitation.email,
role_name="staff"
role_name="staff",
)
logger.info(
@@ -166,15 +151,12 @@ def invite_team_member(
message="Invitation sent successfully",
email=result["email"],
role=result["role"],
invitation_sent=True
invitation_sent=True,
)
@router.post("/accept-invitation", response_model=InvitationAcceptResponse)
def accept_invitation(
acceptance: InvitationAccept,
db: Session = Depends(get_db)
):
def accept_invitation(acceptance: InvitationAccept, db: Session = Depends(get_db)):
"""
Accept a team invitation and activate account.
@@ -196,7 +178,7 @@ def accept_invitation(
invitation_token=acceptance.invitation_token,
password=acceptance.password,
first_name=acceptance.first_name,
last_name=acceptance.last_name
last_name=acceptance.last_name,
)
logger.info(
@@ -210,26 +192,26 @@ def accept_invitation(
"id": result["vendor"].id,
"vendor_code": result["vendor"].vendor_code,
"name": result["vendor"].name,
"subdomain": result["vendor"].subdomain
"subdomain": result["vendor"].subdomain,
},
user={
"id": result["user"].id,
"email": result["user"].email,
"username": result["user"].username,
"full_name": result["user"].full_name
"full_name": result["user"].full_name,
},
role=result["role"]
role=result["role"],
)
@router.get("/members/{user_id}", response_model=TeamMemberResponse)
def get_team_member(
user_id: int,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_permission(
VendorPermissions.TEAM_VIEW.value
))
user_id: int,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(
require_vendor_permission(VendorPermissions.TEAM_VIEW.value)
),
):
"""
Get details of a specific team member.
@@ -239,14 +221,13 @@ def get_team_member(
vendor = request.state.vendor
members = vendor_team_service.get_team_members(
db=db,
vendor=vendor,
include_inactive=True
db=db, vendor=vendor, include_inactive=True
)
member = next((m for m in members if m["id"] == user_id), None)
if not member:
from app.exceptions import UserNotFoundException
raise UserNotFoundException(str(user_id))
return TeamMemberResponse(**member)
@@ -254,11 +235,11 @@ def get_team_member(
@router.put("/members/{user_id}", response_model=TeamMemberResponse)
def update_team_member(
user_id: int,
update_data: TeamMemberUpdate,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_owner) # Owner only
user_id: int,
update_data: TeamMemberUpdate,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_owner), # Owner only
):
"""
Update a team member's role or status.
@@ -280,7 +261,7 @@ def update_team_member(
vendor=vendor,
user_id=user_id,
new_role_id=update_data.role_id,
is_active=update_data.is_active
is_active=update_data.is_active,
)
logger.info(
@@ -297,10 +278,10 @@ def update_team_member(
@router.delete("/members/{user_id}")
def remove_team_member(
user_id: int,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_owner) # Owner only
user_id: int,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_owner), # Owner only
):
"""
Remove a team member from the vendor.
@@ -316,29 +297,22 @@ def remove_team_member(
"""
vendor = request.state.vendor
vendor_team_service.remove_team_member(
db=db,
vendor=vendor,
user_id=user_id
)
vendor_team_service.remove_team_member(db=db, vendor=vendor, user_id=user_id)
logger.info(
f"Team member removed: {user_id} from {vendor.vendor_code} "
f"by {current_user.username}"
)
return {
"message": "Team member removed successfully",
"user_id": user_id
}
return {"message": "Team member removed successfully", "user_id": user_id}
@router.post("/members/bulk-remove", response_model=BulkRemoveResponse)
def bulk_remove_team_members(
bulk_remove: BulkRemoveRequest,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_owner)
bulk_remove: BulkRemoveRequest,
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_owner),
):
"""
Remove multiple team members at once.
@@ -354,17 +328,12 @@ def bulk_remove_team_members(
for user_id in bulk_remove.user_ids:
try:
vendor_team_service.remove_team_member(
db=db,
vendor=vendor,
user_id=user_id
db=db, vendor=vendor, user_id=user_id
)
success_count += 1
except Exception as e:
failed_count += 1
errors.append({
"user_id": user_id,
"error": str(e)
})
errors.append({"user_id": user_id, "error": str(e)})
logger.info(
f"Bulk remove completed: {success_count} removed, {failed_count} failed "
@@ -372,9 +341,7 @@ def bulk_remove_team_members(
)
return BulkRemoveResponse(
success_count=success_count,
failed_count=failed_count,
errors=errors
success_count=success_count, failed_count=failed_count, errors=errors
)
@@ -382,13 +349,14 @@ def bulk_remove_team_members(
# Role Management Routes
# ============================================================================
@router.get("/roles", response_model=RoleListResponse)
def list_roles(
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_permission(
VendorPermissions.TEAM_VIEW.value
))
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(
require_vendor_permission(VendorPermissions.TEAM_VIEW.value)
),
):
"""
Get all available roles for the vendor.
@@ -403,21 +371,19 @@ def list_roles(
roles = vendor_team_service.get_vendor_roles(db=db, vendor_id=vendor.id)
return RoleListResponse(
roles=roles,
total=len(roles)
)
return RoleListResponse(roles=roles, total=len(roles))
# ============================================================================
# Permission Routes
# ============================================================================
@router.get("/me/permissions", response_model=UserPermissionsResponse)
def get_my_permissions(
request: Request,
permissions: List[str] = Depends(get_user_permissions),
current_user: User = Depends(get_current_vendor_api)
request: Request,
permissions: List[str] = Depends(get_user_permissions),
current_user: User = Depends(get_current_vendor_api),
):
"""
Get current user's permissions in this vendor.
@@ -443,7 +409,7 @@ def get_my_permissions(
permissions=permissions,
permission_count=len(permissions),
is_owner=is_owner,
role_name=role_name
role_name=role_name,
)
@@ -451,13 +417,14 @@ def get_my_permissions(
# Statistics Routes
# ============================================================================
@router.get("/statistics", response_model=TeamStatistics)
def get_team_statistics(
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(require_vendor_permission(
VendorPermissions.TEAM_VIEW.value
))
request: Request,
db: Session = Depends(get_db),
current_user: User = Depends(
require_vendor_permission(VendorPermissions.TEAM_VIEW.value)
),
):
"""
Get team statistics for the vendor.
@@ -474,9 +441,7 @@ def get_team_statistics(
vendor = request.state.vendor
members = vendor_team_service.get_team_members(
db=db,
vendor=vendor,
include_inactive=True
db=db, vendor=vendor, include_inactive=True
)
# Calculate statistics
@@ -500,5 +465,5 @@ def get_team_statistics(
pending_invitations=pending,
owners=owners,
team_members=team_members,
roles_breakdown=roles_breakdown
roles_breakdown=roles_breakdown,
)