refactor: modernize code quality tooling with Ruff
- Replace black, isort, and flake8 with Ruff (all-in-one linter and formatter) - Add comprehensive pyproject.toml configuration - Simplify Makefile code quality targets - Configure exclusions for venv/.venv in pyproject.toml - Auto-fix 1,359 linting issues across codebase Benefits: - Much faster builds (Ruff is written in Rust) - Single tool replaces multiple tools - More comprehensive rule set (UP, B, C4, SIM, PIE, RET, Q) - All configuration centralized in pyproject.toml - Better import sorting and formatting consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -9,10 +9,9 @@ This module provides functions for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import and_, or_
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import AdminOperationException
|
||||
@@ -33,10 +32,10 @@ class AdminAuditService:
|
||||
action: str,
|
||||
target_type: str,
|
||||
target_id: str,
|
||||
details: Optional[Dict[str, Any]] = None,
|
||||
ip_address: Optional[str] = None,
|
||||
user_agent: Optional[str] = None,
|
||||
request_id: Optional[str] = None,
|
||||
details: dict[str, Any] | None = None,
|
||||
ip_address: str | None = None,
|
||||
user_agent: str | None = None,
|
||||
request_id: str | None = None,
|
||||
) -> AdminAuditLog:
|
||||
"""
|
||||
Log an admin action to the audit trail.
|
||||
@@ -85,7 +84,7 @@ class AdminAuditService:
|
||||
|
||||
def get_audit_logs(
|
||||
self, db: Session, filters: AdminAuditLogFilters
|
||||
) -> List[AdminAuditLogResponse]:
|
||||
) -> list[AdminAuditLogResponse]:
|
||||
"""
|
||||
Get filtered admin audit logs with pagination.
|
||||
|
||||
@@ -187,14 +186,14 @@ class AdminAuditService:
|
||||
|
||||
def get_recent_actions_by_admin(
|
||||
self, db: Session, admin_user_id: int, limit: int = 10
|
||||
) -> List[AdminAuditLogResponse]:
|
||||
) -> list[AdminAuditLogResponse]:
|
||||
"""Get recent actions by a specific admin."""
|
||||
filters = AdminAuditLogFilters(admin_user_id=admin_user_id, limit=limit)
|
||||
return self.get_audit_logs(db, filters)
|
||||
|
||||
def get_actions_by_target(
|
||||
self, db: Session, target_type: str, target_id: str, limit: int = 50
|
||||
) -> List[AdminAuditLogResponse]:
|
||||
) -> list[AdminAuditLogResponse]:
|
||||
"""Get all actions performed on a specific target."""
|
||||
try:
|
||||
logs = (
|
||||
|
||||
@@ -13,17 +13,21 @@ This module provides classes and functions for:
|
||||
import logging
|
||||
import secrets
|
||||
import string
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Tuple
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (AdminOperationException, CannotModifySelfException,
|
||||
UserNotFoundException, UserStatusChangeException,
|
||||
ValidationException, VendorAlreadyExistsException,
|
||||
VendorNotFoundException,
|
||||
VendorVerificationException)
|
||||
from app.exceptions import (
|
||||
AdminOperationException,
|
||||
CannotModifySelfException,
|
||||
UserNotFoundException,
|
||||
UserStatusChangeException,
|
||||
ValidationException,
|
||||
VendorAlreadyExistsException,
|
||||
VendorNotFoundException,
|
||||
VendorVerificationException,
|
||||
)
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Role, Vendor, VendorUser
|
||||
@@ -40,7 +44,7 @@ class AdminService:
|
||||
# USER MANAGEMENT
|
||||
# ============================================================================
|
||||
|
||||
def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> List[User]:
|
||||
def get_all_users(self, db: Session, skip: int = 0, limit: int = 100) -> list[User]:
|
||||
"""Get paginated list of all users."""
|
||||
try:
|
||||
return db.query(User).offset(skip).limit(limit).all()
|
||||
@@ -52,7 +56,7 @@ class AdminService:
|
||||
|
||||
def toggle_user_status(
|
||||
self, db: Session, user_id: int, current_admin_id: int
|
||||
) -> Tuple[User, str]:
|
||||
) -> tuple[User, str]:
|
||||
"""Toggle user active status."""
|
||||
user = self._get_user_by_id_or_raise(db, user_id)
|
||||
|
||||
@@ -72,7 +76,7 @@ class AdminService:
|
||||
try:
|
||||
original_status = user.is_active
|
||||
user.is_active = not user.is_active
|
||||
user.updated_at = datetime.now(timezone.utc)
|
||||
user.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
|
||||
@@ -98,7 +102,7 @@ class AdminService:
|
||||
|
||||
def create_vendor_with_owner(
|
||||
self, db: Session, vendor_data: VendorCreate
|
||||
) -> Tuple[Vendor, User, str]:
|
||||
) -> tuple[Vendor, User, str]:
|
||||
"""
|
||||
Create vendor with owner user account.
|
||||
|
||||
@@ -222,10 +226,10 @@ class AdminService:
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
search: Optional[str] = None,
|
||||
is_active: Optional[bool] = None,
|
||||
is_verified: Optional[bool] = None,
|
||||
) -> Tuple[List[Vendor], int]:
|
||||
search: str | None = None,
|
||||
is_active: bool | None = None,
|
||||
is_verified: bool | None = None,
|
||||
) -> tuple[list[Vendor], int]:
|
||||
"""Get paginated list of all vendors with filtering."""
|
||||
try:
|
||||
query = db.query(Vendor)
|
||||
@@ -261,17 +265,17 @@ class AdminService:
|
||||
"""Get vendor by ID."""
|
||||
return self._get_vendor_by_id_or_raise(db, vendor_id)
|
||||
|
||||
def verify_vendor(self, db: Session, vendor_id: int) -> Tuple[Vendor, str]:
|
||||
def verify_vendor(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
|
||||
"""Toggle vendor verification status."""
|
||||
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
|
||||
|
||||
try:
|
||||
original_status = vendor.is_verified
|
||||
vendor.is_verified = not vendor.is_verified
|
||||
vendor.updated_at = datetime.now(timezone.utc)
|
||||
vendor.updated_at = datetime.now(UTC)
|
||||
|
||||
if vendor.is_verified:
|
||||
vendor.verified_at = datetime.now(timezone.utc)
|
||||
vendor.verified_at = datetime.now(UTC)
|
||||
|
||||
db.commit()
|
||||
db.refresh(vendor)
|
||||
@@ -291,14 +295,14 @@ class AdminService:
|
||||
current_verification_status=original_status,
|
||||
)
|
||||
|
||||
def toggle_vendor_status(self, db: Session, vendor_id: int) -> Tuple[Vendor, str]:
|
||||
def toggle_vendor_status(self, db: Session, vendor_id: int) -> tuple[Vendor, str]:
|
||||
"""Toggle vendor active status."""
|
||||
vendor = self._get_vendor_by_id_or_raise(db, vendor_id)
|
||||
|
||||
try:
|
||||
original_status = vendor.is_active
|
||||
vendor.is_active = not vendor.is_active
|
||||
vendor.updated_at = datetime.now(timezone.utc)
|
||||
vendor.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(vendor)
|
||||
|
||||
@@ -347,7 +351,10 @@ class AdminService:
|
||||
)
|
||||
|
||||
def update_vendor(
|
||||
self, db: Session, vendor_id: int, vendor_update # VendorUpdate schema
|
||||
self,
|
||||
db: Session,
|
||||
vendor_id: int,
|
||||
vendor_update, # VendorUpdate schema
|
||||
) -> Vendor:
|
||||
"""
|
||||
Update vendor information (Admin only).
|
||||
@@ -402,7 +409,7 @@ class AdminService:
|
||||
for field, value in update_data.items():
|
||||
setattr(vendor, field, value)
|
||||
|
||||
vendor.updated_at = datetime.now(timezone.utc)
|
||||
vendor.updated_at = datetime.now(UTC)
|
||||
|
||||
db.commit()
|
||||
db.refresh(vendor)
|
||||
@@ -430,7 +437,7 @@ class AdminService:
|
||||
db: Session,
|
||||
vendor_id: int,
|
||||
transfer_data, # VendorTransferOwnership schema
|
||||
) -> Tuple[Vendor, User, User]:
|
||||
) -> tuple[Vendor, User, User]:
|
||||
"""
|
||||
Transfer vendor ownership to another user.
|
||||
|
||||
@@ -556,7 +563,7 @@ class AdminService:
|
||||
|
||||
# Update vendor owner_user_id
|
||||
vendor.owner_user_id = new_owner.id
|
||||
vendor.updated_at = datetime.now(timezone.utc)
|
||||
vendor.updated_at = datetime.now(UTC)
|
||||
|
||||
db.commit()
|
||||
db.refresh(vendor)
|
||||
@@ -593,12 +600,12 @@ class AdminService:
|
||||
def get_marketplace_import_jobs(
|
||||
self,
|
||||
db: Session,
|
||||
marketplace: Optional[str] = None,
|
||||
vendor_name: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
marketplace: str | None = None,
|
||||
vendor_name: str | None = None,
|
||||
status: str | None = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> List[MarketplaceImportJobResponse]:
|
||||
) -> list[MarketplaceImportJobResponse]:
|
||||
"""Get filtered and paginated marketplace import jobs."""
|
||||
try:
|
||||
query = db.query(MarketplaceImportJob)
|
||||
@@ -633,7 +640,7 @@ class AdminService:
|
||||
# STATISTICS
|
||||
# ============================================================================
|
||||
|
||||
def get_recent_vendors(self, db: Session, limit: int = 5) -> List[dict]:
|
||||
def get_recent_vendors(self, db: Session, limit: int = 5) -> list[dict]:
|
||||
"""Get recently created vendors."""
|
||||
try:
|
||||
vendors = (
|
||||
@@ -656,7 +663,7 @@ class AdminService:
|
||||
logger.error(f"Failed to get recent vendors: {str(e)}")
|
||||
return []
|
||||
|
||||
def get_recent_import_jobs(self, db: Session, limit: int = 10) -> List[dict]:
|
||||
def get_recent_import_jobs(self, db: Session, limit: int = 10) -> list[dict]:
|
||||
"""Get recent marketplace import jobs."""
|
||||
try:
|
||||
jobs = (
|
||||
|
||||
@@ -10,17 +10,23 @@ This module provides functions for:
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (AdminOperationException, ResourceNotFoundException,
|
||||
ValidationException)
|
||||
from app.exceptions import (
|
||||
AdminOperationException,
|
||||
ResourceNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
from models.database.admin import AdminSetting
|
||||
from models.schema.admin import (AdminSettingCreate, AdminSettingResponse,
|
||||
AdminSettingUpdate)
|
||||
from models.schema.admin import (
|
||||
AdminSettingCreate,
|
||||
AdminSettingResponse,
|
||||
AdminSettingUpdate,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,7 +34,7 @@ logger = logging.getLogger(__name__)
|
||||
class AdminSettingsService:
|
||||
"""Service for managing platform-wide settings."""
|
||||
|
||||
def get_setting_by_key(self, db: Session, key: str) -> Optional[AdminSetting]:
|
||||
def get_setting_by_key(self, db: Session, key: str) -> AdminSetting | None:
|
||||
"""Get setting by key."""
|
||||
try:
|
||||
return (
|
||||
@@ -60,14 +66,13 @@ class AdminSettingsService:
|
||||
try:
|
||||
if setting.value_type == "integer":
|
||||
return int(setting.value)
|
||||
elif setting.value_type == "float":
|
||||
if setting.value_type == "float":
|
||||
return float(setting.value)
|
||||
elif setting.value_type == "boolean":
|
||||
if setting.value_type == "boolean":
|
||||
return setting.value.lower() in ("true", "1", "yes")
|
||||
elif setting.value_type == "json":
|
||||
if setting.value_type == "json":
|
||||
return json.loads(setting.value)
|
||||
else:
|
||||
return setting.value
|
||||
return setting.value
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to convert setting {key} value: {str(e)}")
|
||||
return default
|
||||
@@ -75,9 +80,9 @@ class AdminSettingsService:
|
||||
def get_all_settings(
|
||||
self,
|
||||
db: Session,
|
||||
category: Optional[str] = None,
|
||||
is_public: Optional[bool] = None,
|
||||
) -> List[AdminSettingResponse]:
|
||||
category: str | None = None,
|
||||
is_public: bool | None = None,
|
||||
) -> list[AdminSettingResponse]:
|
||||
"""Get all settings with optional filtering."""
|
||||
try:
|
||||
query = db.query(AdminSetting)
|
||||
@@ -100,7 +105,7 @@ class AdminSettingsService:
|
||||
operation="get_all_settings", reason="Database query failed"
|
||||
)
|
||||
|
||||
def get_settings_by_category(self, db: Session, category: str) -> Dict[str, Any]:
|
||||
def get_settings_by_category(self, db: Session, category: str) -> dict[str, Any]:
|
||||
"""
|
||||
Get all settings in a category as a dictionary.
|
||||
|
||||
@@ -198,7 +203,7 @@ class AdminSettingsService:
|
||||
if update_data.description is not None:
|
||||
setting.description = update_data.description
|
||||
setting.last_modified_by_user_id = admin_user_id
|
||||
setting.updated_at = datetime.now(timezone.utc)
|
||||
setting.updated_at = datetime.now(UTC)
|
||||
|
||||
db.commit()
|
||||
db.refresh(setting)
|
||||
@@ -228,8 +233,7 @@ class AdminSettingsService:
|
||||
value=setting_data.value, description=setting_data.description
|
||||
)
|
||||
return self.update_setting(db, setting_data.key, update_data, admin_user_id)
|
||||
else:
|
||||
return self.create_setting(db, setting_data, admin_user_id)
|
||||
return self.create_setting(db, setting_data, admin_user_id)
|
||||
|
||||
def delete_setting(self, db: Session, key: str, admin_user_id: int) -> str:
|
||||
"""Delete setting."""
|
||||
|
||||
@@ -9,13 +9,17 @@ This module provides classes and functions for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any, Dict, Optional
|
||||
from datetime import UTC
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (InvalidCredentialsException,
|
||||
UserAlreadyExistsException, UserNotActiveException,
|
||||
ValidationException)
|
||||
from app.exceptions import (
|
||||
InvalidCredentialsException,
|
||||
UserAlreadyExistsException,
|
||||
UserNotActiveException,
|
||||
ValidationException,
|
||||
)
|
||||
from middleware.auth import AuthManager
|
||||
from models.database.user import User
|
||||
from models.schema.auth import UserLogin, UserRegister
|
||||
@@ -82,7 +86,7 @@ class AuthService:
|
||||
logger.error(f"Error registering user: {str(e)}")
|
||||
raise ValidationException("Registration failed")
|
||||
|
||||
def login_user(self, db: Session, user_credentials: UserLogin) -> Dict[str, Any]:
|
||||
def login_user(self, db: Session, user_credentials: UserLogin) -> dict[str, Any]:
|
||||
"""
|
||||
Login user and return JWT token with user data.
|
||||
|
||||
@@ -120,7 +124,7 @@ class AuthService:
|
||||
logger.error(f"Error during login: {str(e)}")
|
||||
raise InvalidCredentialsException()
|
||||
|
||||
def get_user_by_email(self, db: Session, email: str) -> Optional[User]:
|
||||
def get_user_by_email(self, db: Session, email: str) -> User | None:
|
||||
"""Get user by email."""
|
||||
try:
|
||||
return db.query(User).filter(User.email == email).first()
|
||||
@@ -128,7 +132,7 @@ class AuthService:
|
||||
logger.error(f"Error getting user by email: {str(e)}")
|
||||
return None
|
||||
|
||||
def get_user_by_username(self, db: Session, username: str) -> Optional[User]:
|
||||
def get_user_by_username(self, db: Session, username: str) -> User | None:
|
||||
"""Get user by username."""
|
||||
try:
|
||||
return db.query(User).filter(User.username == username).first()
|
||||
@@ -138,7 +142,7 @@ class AuthService:
|
||||
|
||||
def authenticate_user(
|
||||
self, db: Session, username: str, password: str
|
||||
) -> Optional[User]:
|
||||
) -> User | None:
|
||||
"""Authenticate user with username/password."""
|
||||
try:
|
||||
return self.auth_manager.authenticate_user(db, username, password)
|
||||
@@ -146,7 +150,7 @@ class AuthService:
|
||||
logger.error(f"Error authenticating user: {str(e)}")
|
||||
return None
|
||||
|
||||
def create_access_token(self, user: User) -> Dict[str, Any]:
|
||||
def create_access_token(self, user: User) -> dict[str, Any]:
|
||||
"""Create access token for user."""
|
||||
try:
|
||||
return self.auth_manager.create_access_token(user)
|
||||
@@ -182,7 +186,7 @@ class AuthService:
|
||||
Returns:
|
||||
Dictionary with access_token, token_type, and expires_in
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from jose import jwt
|
||||
|
||||
@@ -190,13 +194,13 @@ class AuthService:
|
||||
|
||||
try:
|
||||
expires_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
expire = datetime.now(UTC) + expires_delta
|
||||
|
||||
# Build payload with provided data
|
||||
payload = {
|
||||
**data,
|
||||
"exp": expire,
|
||||
"iat": datetime.now(timezone.utc),
|
||||
"iat": datetime.now(UTC),
|
||||
}
|
||||
|
||||
token = jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256")
|
||||
|
||||
@@ -9,20 +9,18 @@ This module provides:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (CartItemNotFoundException, CartValidationException,
|
||||
InsufficientInventoryForCartException,
|
||||
InvalidCartQuantityException,
|
||||
ProductNotAvailableForCartException,
|
||||
ProductNotFoundException)
|
||||
from app.exceptions import (
|
||||
CartItemNotFoundException,
|
||||
InsufficientInventoryForCartException,
|
||||
InvalidCartQuantityException,
|
||||
ProductNotFoundException,
|
||||
)
|
||||
from models.database.cart import CartItem
|
||||
from models.database.product import Product
|
||||
from models.database.vendor import Vendor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,7 +28,7 @@ logger = logging.getLogger(__name__)
|
||||
class CartService:
|
||||
"""Service for managing shopping carts."""
|
||||
|
||||
def get_cart(self, db: Session, vendor_id: int, session_id: str) -> Dict:
|
||||
def get_cart(self, db: Session, vendor_id: int, session_id: str) -> dict:
|
||||
"""
|
||||
Get cart contents for a session.
|
||||
|
||||
@@ -43,7 +41,7 @@ class CartService:
|
||||
Cart data with items and totals
|
||||
"""
|
||||
logger.info(
|
||||
f"[CART_SERVICE] get_cart called",
|
||||
"[CART_SERVICE] get_cart called",
|
||||
extra={
|
||||
"vendor_id": vendor_id,
|
||||
"session_id": session_id,
|
||||
@@ -111,7 +109,7 @@ class CartService:
|
||||
session_id: str,
|
||||
product_id: int,
|
||||
quantity: int = 1,
|
||||
) -> Dict:
|
||||
) -> dict:
|
||||
"""
|
||||
Add product to cart.
|
||||
|
||||
@@ -130,7 +128,7 @@ class CartService:
|
||||
InsufficientInventoryException: If not enough inventory
|
||||
"""
|
||||
logger.info(
|
||||
f"[CART_SERVICE] add_to_cart called",
|
||||
"[CART_SERVICE] add_to_cart called",
|
||||
extra={
|
||||
"vendor_id": vendor_id,
|
||||
"session_id": session_id,
|
||||
@@ -154,7 +152,7 @@ class CartService:
|
||||
|
||||
if not product:
|
||||
logger.error(
|
||||
f"[CART_SERVICE] Product not found",
|
||||
"[CART_SERVICE] Product not found",
|
||||
extra={"product_id": product_id, "vendor_id": vendor_id},
|
||||
)
|
||||
raise ProductNotFoundException(product_id=product_id, vendor_id=vendor_id)
|
||||
@@ -191,7 +189,7 @@ class CartService:
|
||||
# Check inventory for new total quantity
|
||||
if product.available_inventory < new_quantity:
|
||||
logger.warning(
|
||||
f"[CART_SERVICE] Insufficient inventory for update",
|
||||
"[CART_SERVICE] Insufficient inventory for update",
|
||||
extra={
|
||||
"product_id": product_id,
|
||||
"current_in_cart": existing_item.quantity,
|
||||
@@ -212,7 +210,7 @@ class CartService:
|
||||
db.refresh(existing_item)
|
||||
|
||||
logger.info(
|
||||
f"[CART_SERVICE] Updated existing cart item",
|
||||
"[CART_SERVICE] Updated existing cart item",
|
||||
extra={"cart_item_id": existing_item.id, "new_quantity": new_quantity},
|
||||
)
|
||||
|
||||
@@ -221,50 +219,49 @@ class CartService:
|
||||
"product_id": product_id,
|
||||
"quantity": new_quantity,
|
||||
}
|
||||
else:
|
||||
# Check inventory for new item
|
||||
if product.available_inventory < quantity:
|
||||
logger.warning(
|
||||
f"[CART_SERVICE] Insufficient inventory",
|
||||
extra={
|
||||
"product_id": product_id,
|
||||
"requested": quantity,
|
||||
"available": product.available_inventory,
|
||||
},
|
||||
)
|
||||
raise InsufficientInventoryForCartException(
|
||||
product_id=product_id,
|
||||
product_name=product.marketplace_product.title,
|
||||
requested=quantity,
|
||||
available=product.available_inventory,
|
||||
)
|
||||
|
||||
# Create new cart item
|
||||
cart_item = CartItem(
|
||||
vendor_id=vendor_id,
|
||||
session_id=session_id,
|
||||
product_id=product_id,
|
||||
quantity=quantity,
|
||||
price_at_add=current_price,
|
||||
)
|
||||
db.add(cart_item)
|
||||
db.commit()
|
||||
db.refresh(cart_item)
|
||||
|
||||
logger.info(
|
||||
f"[CART_SERVICE] Created new cart item",
|
||||
# Check inventory for new item
|
||||
if product.available_inventory < quantity:
|
||||
logger.warning(
|
||||
"[CART_SERVICE] Insufficient inventory",
|
||||
extra={
|
||||
"cart_item_id": cart_item.id,
|
||||
"quantity": quantity,
|
||||
"price": current_price,
|
||||
"product_id": product_id,
|
||||
"requested": quantity,
|
||||
"available": product.available_inventory,
|
||||
},
|
||||
)
|
||||
raise InsufficientInventoryForCartException(
|
||||
product_id=product_id,
|
||||
product_name=product.marketplace_product.title,
|
||||
requested=quantity,
|
||||
available=product.available_inventory,
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "Product added to cart",
|
||||
"product_id": product_id,
|
||||
# Create new cart item
|
||||
cart_item = CartItem(
|
||||
vendor_id=vendor_id,
|
||||
session_id=session_id,
|
||||
product_id=product_id,
|
||||
quantity=quantity,
|
||||
price_at_add=current_price,
|
||||
)
|
||||
db.add(cart_item)
|
||||
db.commit()
|
||||
db.refresh(cart_item)
|
||||
|
||||
logger.info(
|
||||
"[CART_SERVICE] Created new cart item",
|
||||
extra={
|
||||
"cart_item_id": cart_item.id,
|
||||
"quantity": quantity,
|
||||
}
|
||||
"price": current_price,
|
||||
},
|
||||
)
|
||||
|
||||
return {
|
||||
"message": "Product added to cart",
|
||||
"product_id": product_id,
|
||||
"quantity": quantity,
|
||||
}
|
||||
|
||||
def update_cart_item(
|
||||
self,
|
||||
@@ -273,7 +270,7 @@ class CartService:
|
||||
session_id: str,
|
||||
product_id: int,
|
||||
quantity: int,
|
||||
) -> Dict:
|
||||
) -> dict:
|
||||
"""
|
||||
Update quantity of item in cart.
|
||||
|
||||
@@ -344,7 +341,7 @@ class CartService:
|
||||
db.refresh(cart_item)
|
||||
|
||||
logger.info(
|
||||
f"[CART_SERVICE] Updated cart item quantity",
|
||||
"[CART_SERVICE] Updated cart item quantity",
|
||||
extra={
|
||||
"cart_item_id": cart_item.id,
|
||||
"product_id": product_id,
|
||||
@@ -360,7 +357,7 @@ class CartService:
|
||||
|
||||
def remove_from_cart(
|
||||
self, db: Session, vendor_id: int, session_id: str, product_id: int
|
||||
) -> Dict:
|
||||
) -> dict:
|
||||
"""
|
||||
Remove item from cart.
|
||||
|
||||
@@ -398,7 +395,7 @@ class CartService:
|
||||
db.commit()
|
||||
|
||||
logger.info(
|
||||
f"[CART_SERVICE] Removed item from cart",
|
||||
"[CART_SERVICE] Removed item from cart",
|
||||
extra={
|
||||
"cart_item_id": cart_item.id,
|
||||
"product_id": product_id,
|
||||
@@ -408,7 +405,7 @@ class CartService:
|
||||
|
||||
return {"message": "Item removed from cart", "product_id": product_id}
|
||||
|
||||
def clear_cart(self, db: Session, vendor_id: int, session_id: str) -> Dict:
|
||||
def clear_cart(self, db: Session, vendor_id: int, session_id: str) -> dict:
|
||||
"""
|
||||
Clear all items from cart.
|
||||
|
||||
@@ -432,7 +429,7 @@ class CartService:
|
||||
db.commit()
|
||||
|
||||
logger.info(
|
||||
f"[CART_SERVICE] Cleared cart",
|
||||
"[CART_SERVICE] Cleared cart",
|
||||
extra={
|
||||
"session_id": session_id,
|
||||
"vendor_id": vendor_id,
|
||||
|
||||
@@ -7,16 +7,16 @@ import json
|
||||
import logging
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy import desc, func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.architecture_scan import (ArchitectureRule, ArchitectureScan,
|
||||
ArchitectureViolation,
|
||||
ViolationAssignment,
|
||||
ViolationComment)
|
||||
from app.models.architecture_scan import (
|
||||
ArchitectureScan,
|
||||
ArchitectureViolation,
|
||||
ViolationAssignment,
|
||||
ViolationComment,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -118,7 +118,7 @@ class CodeQualityService:
|
||||
logger.info(f"Scan completed: {scan.total_violations} violations found")
|
||||
return scan
|
||||
|
||||
def get_latest_scan(self, db: Session) -> Optional[ArchitectureScan]:
|
||||
def get_latest_scan(self, db: Session) -> ArchitectureScan | None:
|
||||
"""Get the most recent scan"""
|
||||
return (
|
||||
db.query(ArchitectureScan)
|
||||
@@ -126,11 +126,11 @@ class CodeQualityService:
|
||||
.first()
|
||||
)
|
||||
|
||||
def get_scan_by_id(self, db: Session, scan_id: int) -> Optional[ArchitectureScan]:
|
||||
def get_scan_by_id(self, db: Session, scan_id: int) -> ArchitectureScan | None:
|
||||
"""Get scan by ID"""
|
||||
return db.query(ArchitectureScan).filter(ArchitectureScan.id == scan_id).first()
|
||||
|
||||
def get_scan_history(self, db: Session, limit: int = 30) -> List[ArchitectureScan]:
|
||||
def get_scan_history(self, db: Session, limit: int = 30) -> list[ArchitectureScan]:
|
||||
"""
|
||||
Get scan history for trend graphs
|
||||
|
||||
@@ -158,7 +158,7 @@ class CodeQualityService:
|
||||
file_path: str = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
) -> Tuple[List[ArchitectureViolation], int]:
|
||||
) -> tuple[list[ArchitectureViolation], int]:
|
||||
"""
|
||||
Get violations with filtering and pagination
|
||||
|
||||
@@ -217,7 +217,7 @@ class CodeQualityService:
|
||||
|
||||
def get_violation_by_id(
|
||||
self, db: Session, violation_id: int
|
||||
) -> Optional[ArchitectureViolation]:
|
||||
) -> ArchitectureViolation | None:
|
||||
"""Get single violation with details"""
|
||||
return (
|
||||
db.query(ArchitectureViolation)
|
||||
@@ -348,7 +348,7 @@ class CodeQualityService:
|
||||
logger.info(f"Comment added to violation {violation_id} by user {user_id}")
|
||||
return comment_obj
|
||||
|
||||
def get_dashboard_stats(self, db: Session) -> Dict:
|
||||
def get_dashboard_stats(self, db: Session) -> dict:
|
||||
"""
|
||||
Get statistics for dashboard
|
||||
|
||||
@@ -507,7 +507,7 @@ class CodeQualityService:
|
||||
score = 100 - (scan.errors * 0.5 + scan.warnings * 0.05)
|
||||
return max(0, min(100, int(score))) # Clamp to 0-100
|
||||
|
||||
def _get_git_commit_hash(self) -> Optional[str]:
|
||||
def _get_git_commit_hash(self) -> str | None:
|
||||
"""Get current git commit hash"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
|
||||
@@ -17,10 +17,9 @@ This allows:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import and_, or_
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from models.database.content_page import ContentPage
|
||||
@@ -35,9 +34,9 @@ class ContentPageService:
|
||||
def get_page_for_vendor(
|
||||
db: Session,
|
||||
slug: str,
|
||||
vendor_id: Optional[int] = None,
|
||||
vendor_id: int | None = None,
|
||||
include_unpublished: bool = False,
|
||||
) -> Optional[ContentPage]:
|
||||
) -> ContentPage | None:
|
||||
"""
|
||||
Get content page for a vendor with fallback to platform default.
|
||||
|
||||
@@ -90,11 +89,11 @@ class ContentPageService:
|
||||
@staticmethod
|
||||
def list_pages_for_vendor(
|
||||
db: Session,
|
||||
vendor_id: Optional[int] = None,
|
||||
vendor_id: int | None = None,
|
||||
include_unpublished: bool = False,
|
||||
footer_only: bool = False,
|
||||
header_only: bool = False,
|
||||
) -> List[ContentPage]:
|
||||
) -> list[ContentPage]:
|
||||
"""
|
||||
List all available pages for a vendor (includes vendor overrides + platform defaults).
|
||||
|
||||
@@ -156,16 +155,16 @@ class ContentPageService:
|
||||
slug: str,
|
||||
title: str,
|
||||
content: str,
|
||||
vendor_id: Optional[int] = None,
|
||||
vendor_id: int | None = None,
|
||||
content_format: str = "html",
|
||||
template: str = "default",
|
||||
meta_description: Optional[str] = None,
|
||||
meta_keywords: Optional[str] = None,
|
||||
meta_description: str | None = None,
|
||||
meta_keywords: str | None = None,
|
||||
is_published: bool = False,
|
||||
show_in_footer: bool = True,
|
||||
show_in_header: bool = False,
|
||||
display_order: int = 0,
|
||||
created_by: Optional[int] = None,
|
||||
created_by: int | None = None,
|
||||
) -> ContentPage:
|
||||
"""
|
||||
Create a new content page.
|
||||
@@ -199,7 +198,7 @@ class ContentPageService:
|
||||
meta_description=meta_description,
|
||||
meta_keywords=meta_keywords,
|
||||
is_published=is_published,
|
||||
published_at=datetime.now(timezone.utc) if is_published else None,
|
||||
published_at=datetime.now(UTC) if is_published else None,
|
||||
show_in_footer=show_in_footer,
|
||||
show_in_header=show_in_header,
|
||||
display_order=display_order,
|
||||
@@ -220,18 +219,18 @@ class ContentPageService:
|
||||
def update_page(
|
||||
db: Session,
|
||||
page_id: int,
|
||||
title: Optional[str] = None,
|
||||
content: Optional[str] = None,
|
||||
content_format: Optional[str] = None,
|
||||
template: Optional[str] = None,
|
||||
meta_description: Optional[str] = None,
|
||||
meta_keywords: Optional[str] = None,
|
||||
is_published: Optional[bool] = None,
|
||||
show_in_footer: Optional[bool] = None,
|
||||
show_in_header: Optional[bool] = None,
|
||||
display_order: Optional[int] = None,
|
||||
updated_by: Optional[int] = None,
|
||||
) -> Optional[ContentPage]:
|
||||
title: str | None = None,
|
||||
content: str | None = None,
|
||||
content_format: str | None = None,
|
||||
template: str | None = None,
|
||||
meta_description: str | None = None,
|
||||
meta_keywords: str | None = None,
|
||||
is_published: bool | None = None,
|
||||
show_in_footer: bool | None = None,
|
||||
show_in_header: bool | None = None,
|
||||
display_order: int | None = None,
|
||||
updated_by: int | None = None,
|
||||
) -> ContentPage | None:
|
||||
"""
|
||||
Update an existing content page.
|
||||
|
||||
@@ -275,7 +274,7 @@ class ContentPageService:
|
||||
if is_published is not None:
|
||||
page.is_published = is_published
|
||||
if is_published and not page.published_at:
|
||||
page.published_at = datetime.now(timezone.utc)
|
||||
page.published_at = datetime.now(UTC)
|
||||
if show_in_footer is not None:
|
||||
page.show_in_footer = show_in_footer
|
||||
if show_in_header is not None:
|
||||
@@ -316,14 +315,14 @@ class ContentPageService:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def get_page_by_id(db: Session, page_id: int) -> Optional[ContentPage]:
|
||||
def get_page_by_id(db: Session, page_id: int) -> ContentPage | None:
|
||||
"""Get content page by ID."""
|
||||
return db.query(ContentPage).filter(ContentPage.id == page_id).first()
|
||||
|
||||
@staticmethod
|
||||
def list_all_vendor_pages(
|
||||
db: Session, vendor_id: int, include_unpublished: bool = False
|
||||
) -> List[ContentPage]:
|
||||
) -> list[ContentPage]:
|
||||
"""
|
||||
List only vendor-specific pages (no platform defaults).
|
||||
|
||||
@@ -350,7 +349,7 @@ class ContentPageService:
|
||||
@staticmethod
|
||||
def list_all_platform_pages(
|
||||
db: Session, include_unpublished: bool = False
|
||||
) -> List[ContentPage]:
|
||||
) -> list[ContentPage]:
|
||||
"""
|
||||
List only platform default pages.
|
||||
|
||||
|
||||
@@ -7,22 +7,22 @@ with complete vendor isolation.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, Optional
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions.customer import (CustomerAlreadyExistsException,
|
||||
CustomerNotActiveException,
|
||||
CustomerNotFoundException,
|
||||
CustomerValidationException,
|
||||
DuplicateCustomerEmailException,
|
||||
InvalidCustomerCredentialsException)
|
||||
from app.exceptions.vendor import (VendorNotActiveException,
|
||||
VendorNotFoundException)
|
||||
from app.exceptions.customer import (
|
||||
CustomerNotActiveException,
|
||||
CustomerNotFoundException,
|
||||
CustomerValidationException,
|
||||
DuplicateCustomerEmailException,
|
||||
InvalidCustomerCredentialsException,
|
||||
)
|
||||
from app.exceptions.vendor import VendorNotActiveException, VendorNotFoundException
|
||||
from app.services.auth_service import AuthService
|
||||
from models.database.customer import Customer, CustomerAddress
|
||||
from models.database.customer import Customer
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.auth import UserLogin
|
||||
from models.schema.customer import CustomerRegister, CustomerUpdate
|
||||
@@ -128,7 +128,7 @@ class CustomerService:
|
||||
|
||||
def login_customer(
|
||||
self, db: Session, vendor_id: int, credentials: UserLogin
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Authenticate customer and generate JWT token.
|
||||
|
||||
@@ -177,13 +177,13 @@ class CustomerService:
|
||||
|
||||
# Generate JWT token with customer context
|
||||
# Use auth_manager directly since Customer is not a User model
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime
|
||||
|
||||
from jose import jwt
|
||||
|
||||
auth_manager = self.auth_service.auth_manager
|
||||
expires_delta = timedelta(minutes=auth_manager.token_expire_minutes)
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
expire = datetime.now(UTC) + expires_delta
|
||||
|
||||
payload = {
|
||||
"sub": str(customer.id),
|
||||
@@ -191,7 +191,7 @@ class CustomerService:
|
||||
"vendor_id": vendor_id,
|
||||
"type": "customer",
|
||||
"exp": expire,
|
||||
"iat": datetime.now(timezone.utc),
|
||||
"iat": datetime.now(UTC),
|
||||
}
|
||||
|
||||
token = jwt.encode(
|
||||
@@ -239,7 +239,7 @@ class CustomerService:
|
||||
|
||||
def get_customer_by_email(
|
||||
self, db: Session, vendor_id: int, email: str
|
||||
) -> Optional[Customer]:
|
||||
) -> Customer | None:
|
||||
"""
|
||||
Get customer by email (vendor-scoped).
|
||||
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
# app/services/inventory_service.py
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (InsufficientInventoryException,
|
||||
InvalidInventoryOperationException,
|
||||
InvalidQuantityException,
|
||||
InventoryNotFoundException,
|
||||
InventoryValidationException,
|
||||
NegativeInventoryException,
|
||||
ProductNotFoundException, ValidationException)
|
||||
from app.exceptions import (
|
||||
InsufficientInventoryException,
|
||||
InvalidQuantityException,
|
||||
InventoryNotFoundException,
|
||||
InventoryValidationException,
|
||||
ProductNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
from models.database.inventory import Inventory
|
||||
from models.database.product import Product
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.inventory import (InventoryAdjust, InventoryCreate,
|
||||
InventoryLocationResponse,
|
||||
InventoryReserve, InventoryUpdate,
|
||||
ProductInventorySummary)
|
||||
from models.schema.inventory import (
|
||||
InventoryAdjust,
|
||||
InventoryCreate,
|
||||
InventoryLocationResponse,
|
||||
InventoryReserve,
|
||||
InventoryUpdate,
|
||||
ProductInventorySummary,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -58,7 +61,7 @@ class InventoryService:
|
||||
if existing:
|
||||
old_qty = existing.quantity
|
||||
existing.quantity = inventory_data.quantity
|
||||
existing.updated_at = datetime.now(timezone.utc)
|
||||
existing.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(existing)
|
||||
|
||||
@@ -67,24 +70,23 @@ class InventoryService:
|
||||
f"{old_qty} → {inventory_data.quantity}"
|
||||
)
|
||||
return existing
|
||||
else:
|
||||
# Create new inventory entry
|
||||
new_inventory = Inventory(
|
||||
product_id=inventory_data.product_id,
|
||||
vendor_id=vendor_id,
|
||||
location=location,
|
||||
quantity=inventory_data.quantity,
|
||||
gtin=product.marketplace_product.gtin, # Optional reference
|
||||
)
|
||||
db.add(new_inventory)
|
||||
db.commit()
|
||||
db.refresh(new_inventory)
|
||||
# Create new inventory entry
|
||||
new_inventory = Inventory(
|
||||
product_id=inventory_data.product_id,
|
||||
vendor_id=vendor_id,
|
||||
location=location,
|
||||
quantity=inventory_data.quantity,
|
||||
gtin=product.marketplace_product.gtin, # Optional reference
|
||||
)
|
||||
db.add(new_inventory)
|
||||
db.commit()
|
||||
db.refresh(new_inventory)
|
||||
|
||||
logger.info(
|
||||
f"Created inventory for product {inventory_data.product_id} at {location}: "
|
||||
f"{inventory_data.quantity}"
|
||||
)
|
||||
return new_inventory
|
||||
logger.info(
|
||||
f"Created inventory for product {inventory_data.product_id} at {location}: "
|
||||
f"{inventory_data.quantity}"
|
||||
)
|
||||
return new_inventory
|
||||
|
||||
except (
|
||||
ProductNotFoundException,
|
||||
@@ -162,7 +164,7 @@ class InventoryService:
|
||||
)
|
||||
|
||||
existing.quantity = new_qty
|
||||
existing.updated_at = datetime.now(timezone.utc)
|
||||
existing.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(existing)
|
||||
|
||||
@@ -224,7 +226,7 @@ class InventoryService:
|
||||
|
||||
# Reserve inventory
|
||||
inventory.reserved_quantity += reserve_data.quantity
|
||||
inventory.updated_at = datetime.now(timezone.utc)
|
||||
inventory.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(inventory)
|
||||
|
||||
@@ -284,7 +286,7 @@ class InventoryService:
|
||||
else:
|
||||
inventory.reserved_quantity -= reserve_data.quantity
|
||||
|
||||
inventory.updated_at = datetime.now(timezone.utc)
|
||||
inventory.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(inventory)
|
||||
|
||||
@@ -350,7 +352,7 @@ class InventoryService:
|
||||
inventory.reserved_quantity = max(
|
||||
0, inventory.reserved_quantity - reserve_data.quantity
|
||||
)
|
||||
inventory.updated_at = datetime.now(timezone.utc)
|
||||
inventory.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(inventory)
|
||||
|
||||
@@ -443,9 +445,9 @@ class InventoryService:
|
||||
vendor_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
location: Optional[str] = None,
|
||||
low_stock_threshold: Optional[int] = None,
|
||||
) -> List[Inventory]:
|
||||
location: str | None = None,
|
||||
low_stock_threshold: int | None = None,
|
||||
) -> list[Inventory]:
|
||||
"""
|
||||
Get all inventory for a vendor with filtering.
|
||||
|
||||
@@ -504,7 +506,7 @@ class InventoryService:
|
||||
if inventory_update.location:
|
||||
inventory.location = self._validate_location(inventory_update.location)
|
||||
|
||||
inventory.updated_at = datetime.now(timezone.utc)
|
||||
inventory.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(inventory)
|
||||
|
||||
@@ -565,7 +567,7 @@ class InventoryService:
|
||||
|
||||
def _get_inventory_entry(
|
||||
self, db: Session, product_id: int, location: str
|
||||
) -> Optional[Inventory]:
|
||||
) -> Inventory | None:
|
||||
"""Get inventory entry by product and location."""
|
||||
return (
|
||||
db.query(Inventory)
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
# app/services/marketplace_import_job_service.py
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (ImportJobCannotBeCancelledException,
|
||||
ImportJobCannotBeDeletedException,
|
||||
ImportJobNotFoundException,
|
||||
ImportJobNotOwnedException, ValidationException)
|
||||
from app.exceptions import (
|
||||
ImportJobNotFoundException,
|
||||
ImportJobNotOwnedException,
|
||||
ValidationException,
|
||||
)
|
||||
from models.database.marketplace_import_job import MarketplaceImportJob
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.marketplace_import_job import (MarketplaceImportJobRequest,
|
||||
MarketplaceImportJobResponse)
|
||||
from models.schema.marketplace_import_job import (
|
||||
MarketplaceImportJobRequest,
|
||||
MarketplaceImportJobResponse,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -98,10 +99,10 @@ class MarketplaceImportJobService:
|
||||
db: Session,
|
||||
vendor: Vendor, # ADDED: Vendor filter
|
||||
user: User,
|
||||
marketplace: Optional[str] = None,
|
||||
marketplace: str | None = None,
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
) -> List[MarketplaceImportJob]:
|
||||
) -> list[MarketplaceImportJob]:
|
||||
"""Get marketplace import jobs for a specific vendor."""
|
||||
try:
|
||||
query = db.query(MarketplaceImportJob).filter(
|
||||
|
||||
@@ -8,29 +8,31 @@ This module provides classes and functions for:
|
||||
- Inventory information integration
|
||||
- CSV export functionality
|
||||
"""
|
||||
|
||||
import csv
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from collections.abc import Generator
|
||||
from datetime import UTC, datetime
|
||||
from io import StringIO
|
||||
from typing import Generator, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (InvalidMarketplaceProductDataException,
|
||||
MarketplaceProductAlreadyExistsException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MarketplaceProductValidationException,
|
||||
ValidationException)
|
||||
from app.services.marketplace_import_job_service import \
|
||||
marketplace_import_job_service
|
||||
from app.exceptions import (
|
||||
InvalidMarketplaceProductDataException,
|
||||
MarketplaceProductAlreadyExistsException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MarketplaceProductValidationException,
|
||||
ValidationException,
|
||||
)
|
||||
from app.utils.data_processing import GTINProcessor, PriceProcessor
|
||||
from models.database.inventory import Inventory
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.schema.inventory import (InventoryLocationResponse,
|
||||
InventorySummaryResponse)
|
||||
from models.schema.marketplace_product import (MarketplaceProductCreate,
|
||||
MarketplaceProductUpdate)
|
||||
from models.schema.inventory import InventoryLocationResponse, InventorySummaryResponse
|
||||
from models.schema.marketplace_product import (
|
||||
MarketplaceProductCreate,
|
||||
MarketplaceProductUpdate,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -109,10 +111,9 @@ class MarketplaceProductService:
|
||||
raise MarketplaceProductAlreadyExistsException(
|
||||
product_data.marketplace_product_id
|
||||
)
|
||||
else:
|
||||
raise MarketplaceProductValidationException(
|
||||
"Data integrity constraint violation"
|
||||
)
|
||||
raise MarketplaceProductValidationException(
|
||||
"Data integrity constraint violation"
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error creating product: {str(e)}")
|
||||
@@ -120,7 +121,7 @@ class MarketplaceProductService:
|
||||
|
||||
def get_product_by_id(
|
||||
self, db: Session, marketplace_product_id: str
|
||||
) -> Optional[MarketplaceProduct]:
|
||||
) -> MarketplaceProduct | None:
|
||||
"""Get a product by its ID."""
|
||||
try:
|
||||
return (
|
||||
@@ -160,13 +161,13 @@ class MarketplaceProductService:
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
brand: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
availability: Optional[str] = None,
|
||||
marketplace: Optional[str] = None,
|
||||
vendor_name: Optional[str] = None,
|
||||
search: Optional[str] = None,
|
||||
) -> Tuple[List[MarketplaceProduct], int]:
|
||||
brand: str | None = None,
|
||||
category: str | None = None,
|
||||
availability: str | None = None,
|
||||
marketplace: str | None = None,
|
||||
vendor_name: str | None = None,
|
||||
search: str | None = None,
|
||||
) -> tuple[list[MarketplaceProduct], int]:
|
||||
"""
|
||||
Get products with filtering and pagination.
|
||||
|
||||
@@ -269,7 +270,7 @@ class MarketplaceProductService:
|
||||
for key, value in update_data.items():
|
||||
setattr(product, key, value)
|
||||
|
||||
product.updated_at = datetime.now(timezone.utc)
|
||||
product.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
|
||||
@@ -324,7 +325,7 @@ class MarketplaceProductService:
|
||||
|
||||
def get_inventory_info(
|
||||
self, db: Session, gtin: str
|
||||
) -> Optional[InventorySummaryResponse]:
|
||||
) -> InventorySummaryResponse | None:
|
||||
"""
|
||||
Get inventory information for a product by GTIN.
|
||||
|
||||
@@ -358,15 +359,14 @@ class MarketplaceProductService:
|
||||
|
||||
import csv
|
||||
from io import StringIO
|
||||
from typing import Generator, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
def generate_csv_export(
|
||||
self,
|
||||
db: Session,
|
||||
marketplace: Optional[str] = None,
|
||||
vendor_name: Optional[str] = None,
|
||||
marketplace: str | None = None,
|
||||
vendor_name: str | None = None,
|
||||
) -> Generator[str, None, None]:
|
||||
"""
|
||||
Generate CSV export with streaming for memory efficiency and proper CSV escaping.
|
||||
|
||||
@@ -11,15 +11,17 @@ This module provides:
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Tuple
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import and_, or_
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (CustomerNotFoundException,
|
||||
InsufficientInventoryException,
|
||||
OrderNotFoundException, ValidationException)
|
||||
from app.exceptions import (
|
||||
CustomerNotFoundException,
|
||||
InsufficientInventoryException,
|
||||
OrderNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
from models.database.customer import Customer, CustomerAddress
|
||||
from models.database.order import Order, OrderItem
|
||||
from models.database.product import Product
|
||||
@@ -38,7 +40,7 @@ class OrderService:
|
||||
Format: ORD-{VENDOR_ID}-{TIMESTAMP}-{RANDOM}
|
||||
Example: ORD-1-20250110-A1B2C3
|
||||
"""
|
||||
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d")
|
||||
timestamp = datetime.now(UTC).strftime("%Y%m%d")
|
||||
random_suffix = "".join(
|
||||
random.choices(string.ascii_uppercase + string.digits, k=6)
|
||||
)
|
||||
@@ -266,9 +268,9 @@ class OrderService:
|
||||
vendor_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
status: Optional[str] = None,
|
||||
customer_id: Optional[int] = None,
|
||||
) -> Tuple[List[Order], int]:
|
||||
status: str | None = None,
|
||||
customer_id: int | None = None,
|
||||
) -> tuple[list[Order], int]:
|
||||
"""
|
||||
Get orders for vendor with filtering.
|
||||
|
||||
@@ -306,7 +308,7 @@ class OrderService:
|
||||
customer_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
) -> Tuple[List[Order], int]:
|
||||
) -> tuple[list[Order], int]:
|
||||
"""Get orders for a specific customer."""
|
||||
return self.get_vendor_orders(
|
||||
db=db, vendor_id=vendor_id, skip=skip, limit=limit, customer_id=customer_id
|
||||
@@ -335,7 +337,7 @@ class OrderService:
|
||||
order.status = order_update.status
|
||||
|
||||
# Update timestamp based on status
|
||||
now = datetime.now(timezone.utc)
|
||||
now = datetime.now(UTC)
|
||||
if order_update.status == "shipped" and not order.shipped_at:
|
||||
order.shipped_at = now
|
||||
elif order_update.status == "delivered" and not order.delivered_at:
|
||||
@@ -351,7 +353,7 @@ class OrderService:
|
||||
if order_update.internal_notes:
|
||||
order.internal_notes = order_update.internal_notes
|
||||
|
||||
order.updated_at = datetime.now(timezone.utc)
|
||||
order.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(order)
|
||||
|
||||
|
||||
@@ -9,13 +9,15 @@ This module provides:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Tuple
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (ProductAlreadyExistsException,
|
||||
ProductNotFoundException, ValidationException)
|
||||
from app.exceptions import (
|
||||
ProductAlreadyExistsException,
|
||||
ProductNotFoundException,
|
||||
ValidationException,
|
||||
)
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.product import Product
|
||||
from models.schema.product import ProductCreate, ProductUpdate
|
||||
@@ -106,7 +108,7 @@ class ProductService:
|
||||
|
||||
if existing:
|
||||
raise ProductAlreadyExistsException(
|
||||
f"Product already exists in catalog"
|
||||
"Product already exists in catalog"
|
||||
)
|
||||
|
||||
# Create product
|
||||
@@ -167,7 +169,7 @@ class ProductService:
|
||||
for key, value in update_data.items():
|
||||
setattr(product, key, value)
|
||||
|
||||
product.updated_at = datetime.now(timezone.utc)
|
||||
product.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(product)
|
||||
|
||||
@@ -216,9 +218,9 @@ class ProductService:
|
||||
vendor_id: int,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
is_active: Optional[bool] = None,
|
||||
is_featured: Optional[bool] = None,
|
||||
) -> Tuple[List[Product], int]:
|
||||
is_active: bool | None = None,
|
||||
is_featured: bool | None = None,
|
||||
) -> tuple[list[Product], int]:
|
||||
"""
|
||||
Get products in vendor catalog with filtering.
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ This module provides:
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -36,7 +36,7 @@ class StatsService:
|
||||
# VENDOR-SPECIFIC STATISTICS
|
||||
# ========================================================================
|
||||
|
||||
def get_vendor_stats(self, db: Session, vendor_id: int) -> Dict[str, Any]:
|
||||
def get_vendor_stats(self, db: Session, vendor_id: int) -> dict[str, Any]:
|
||||
"""
|
||||
Get statistics for a specific vendor.
|
||||
|
||||
@@ -177,7 +177,7 @@ class StatsService:
|
||||
|
||||
def get_vendor_analytics(
|
||||
self, db: Session, vendor_id: int, period: str = "30d"
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Get a specific vendor analytics for a time period.
|
||||
|
||||
@@ -283,7 +283,7 @@ class StatsService:
|
||||
# SYSTEM-WIDE STATISTICS (ADMIN)
|
||||
# ========================================================================
|
||||
|
||||
def get_comprehensive_stats(self, db: Session) -> Dict[str, Any]:
|
||||
def get_comprehensive_stats(self, db: Session) -> dict[str, Any]:
|
||||
"""
|
||||
Get comprehensive system statistics for admin dashboard.
|
||||
|
||||
@@ -333,7 +333,7 @@ class StatsService:
|
||||
reason=f"Database query failed: {str(e)}",
|
||||
)
|
||||
|
||||
def get_marketplace_breakdown_stats(self, db: Session) -> List[Dict[str, Any]]:
|
||||
def get_marketplace_breakdown_stats(self, db: Session) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get statistics broken down by marketplace.
|
||||
|
||||
@@ -382,7 +382,7 @@ class StatsService:
|
||||
reason=f"Database query failed: {str(e)}",
|
||||
)
|
||||
|
||||
def get_user_statistics(self, db: Session) -> Dict[str, Any]:
|
||||
def get_user_statistics(self, db: Session) -> dict[str, Any]:
|
||||
"""
|
||||
Get user statistics for admin dashboard.
|
||||
|
||||
@@ -416,7 +416,7 @@ class StatsService:
|
||||
operation="get_user_statistics", reason="Database query failed"
|
||||
)
|
||||
|
||||
def get_import_statistics(self, db: Session) -> Dict[str, Any]:
|
||||
def get_import_statistics(self, db: Session) -> dict[str, Any]:
|
||||
"""
|
||||
Get import job statistics.
|
||||
|
||||
@@ -457,7 +457,7 @@ class StatsService:
|
||||
"success_rate": 0,
|
||||
}
|
||||
|
||||
def get_order_statistics(self, db: Session) -> Dict[str, Any]:
|
||||
def get_order_statistics(self, db: Session) -> dict[str, Any]:
|
||||
"""
|
||||
Get order statistics.
|
||||
|
||||
@@ -472,7 +472,7 @@ class StatsService:
|
||||
"""
|
||||
return {"total_orders": 0, "pending_orders": 0, "completed_orders": 0}
|
||||
|
||||
def get_product_statistics(self, db: Session) -> Dict[str, Any]:
|
||||
def get_product_statistics(self, db: Session) -> dict[str, Any]:
|
||||
"""
|
||||
Get product statistics.
|
||||
|
||||
@@ -548,7 +548,7 @@ class StatsService:
|
||||
.count()
|
||||
)
|
||||
|
||||
def _get_inventory_statistics(self, db: Session) -> Dict[str, int]:
|
||||
def _get_inventory_statistics(self, db: Session) -> dict[str, int]:
|
||||
"""
|
||||
Get inventory-related statistics.
|
||||
|
||||
|
||||
@@ -9,13 +9,12 @@ This module provides:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (UnauthorizedVendorAccessException,
|
||||
ValidationException)
|
||||
from app.exceptions import ValidationException
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Role, VendorUser
|
||||
|
||||
@@ -27,7 +26,7 @@ class TeamService:
|
||||
|
||||
def get_team_members(
|
||||
self, db: Session, vendor_id: int, current_user: User
|
||||
) -> List[Dict[str, Any]]:
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get all team members for vendor.
|
||||
|
||||
@@ -69,7 +68,7 @@ class TeamService:
|
||||
|
||||
def invite_team_member(
|
||||
self, db: Session, vendor_id: int, invitation_data: dict, current_user: User
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Invite a new team member.
|
||||
|
||||
@@ -102,7 +101,7 @@ class TeamService:
|
||||
user_id: int,
|
||||
update_data: dict,
|
||||
current_user: User,
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Update team member role or status.
|
||||
|
||||
@@ -135,7 +134,7 @@ class TeamService:
|
||||
if "is_active" in update_data:
|
||||
vendor_user.is_active = update_data["is_active"]
|
||||
|
||||
vendor_user.updated_at = datetime.now(timezone.utc)
|
||||
vendor_user.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(vendor_user)
|
||||
|
||||
@@ -178,7 +177,7 @@ class TeamService:
|
||||
|
||||
# Soft delete
|
||||
vendor_user.is_active = False
|
||||
vendor_user.updated_at = datetime.now(timezone.utc)
|
||||
vendor_user.updated_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
|
||||
logger.info(f"Removed user {user_id} from vendor {vendor_id}")
|
||||
@@ -189,7 +188,7 @@ class TeamService:
|
||||
logger.error(f"Error removing team member: {str(e)}")
|
||||
raise ValidationException("Failed to remove team member")
|
||||
|
||||
def get_vendor_roles(self, db: Session, vendor_id: int) -> List[Dict[str, Any]]:
|
||||
def get_vendor_roles(self, db: Session, vendor_id: int) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get available roles for vendor.
|
||||
|
||||
|
||||
@@ -12,25 +12,23 @@ This module provides classes and functions for:
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Tuple
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (DNSVerificationException,
|
||||
DomainAlreadyVerifiedException,
|
||||
DomainNotVerifiedException,
|
||||
DomainVerificationFailedException,
|
||||
InvalidDomainFormatException,
|
||||
MaxDomainsReachedException,
|
||||
MultiplePrimaryDomainsException,
|
||||
ReservedDomainException,
|
||||
UnauthorizedDomainAccessException,
|
||||
ValidationException,
|
||||
VendorDomainAlreadyExistsException,
|
||||
VendorDomainNotFoundException,
|
||||
VendorNotFoundException)
|
||||
from app.exceptions import (
|
||||
DNSVerificationException,
|
||||
DomainAlreadyVerifiedException,
|
||||
DomainNotVerifiedException,
|
||||
DomainVerificationFailedException,
|
||||
InvalidDomainFormatException,
|
||||
MaxDomainsReachedException,
|
||||
ReservedDomainException,
|
||||
ValidationException,
|
||||
VendorDomainAlreadyExistsException,
|
||||
VendorDomainNotFoundException,
|
||||
VendorNotFoundException,
|
||||
)
|
||||
from models.database.vendor import Vendor
|
||||
from models.database.vendor_domain import VendorDomain
|
||||
from models.schema.vendor_domain import VendorDomainCreate, VendorDomainUpdate
|
||||
@@ -135,7 +133,7 @@ class VendorDomainService:
|
||||
logger.error(f"Error adding domain: {str(e)}")
|
||||
raise ValidationException("Failed to add domain")
|
||||
|
||||
def get_vendor_domains(self, db: Session, vendor_id: int) -> List[VendorDomain]:
|
||||
def get_vendor_domains(self, db: Session, vendor_id: int) -> list[VendorDomain]:
|
||||
"""
|
||||
Get all domains for a vendor.
|
||||
|
||||
@@ -272,7 +270,7 @@ class VendorDomainService:
|
||||
logger.error(f"Error deleting domain: {str(e)}")
|
||||
raise ValidationException("Failed to delete domain")
|
||||
|
||||
def verify_domain(self, db: Session, domain_id: int) -> Tuple[VendorDomain, str]:
|
||||
def verify_domain(self, db: Session, domain_id: int) -> tuple[VendorDomain, str]:
|
||||
"""
|
||||
Verify domain ownership via DNS TXT record.
|
||||
|
||||
@@ -313,7 +311,7 @@ class VendorDomainService:
|
||||
if txt_value == domain.verification_token:
|
||||
# Verification successful
|
||||
domain.is_verified = True
|
||||
domain.verified_at = datetime.now(timezone.utc)
|
||||
domain.verified_at = datetime.now(UTC)
|
||||
db.commit()
|
||||
db.refresh(domain)
|
||||
|
||||
@@ -419,7 +417,7 @@ class VendorDomainService:
|
||||
raise ReservedDomainException(domain, first_part)
|
||||
|
||||
def _unset_primary_domains(
|
||||
self, db: Session, vendor_id: int, exclude_domain_id: Optional[int] = None
|
||||
self, db: Session, vendor_id: int, exclude_domain_id: int | None = None
|
||||
) -> None:
|
||||
"""Unset all primary domains for vendor."""
|
||||
query = db.query(VendorDomain).filter(
|
||||
|
||||
@@ -10,18 +10,20 @@ This module provides classes and functions for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.exceptions import (InvalidVendorDataException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MaxVendorsReachedException,
|
||||
ProductAlreadyExistsException,
|
||||
UnauthorizedVendorAccessException,
|
||||
ValidationException, VendorAlreadyExistsException,
|
||||
VendorNotFoundException)
|
||||
from app.exceptions import (
|
||||
InvalidVendorDataException,
|
||||
MarketplaceProductNotFoundException,
|
||||
MaxVendorsReachedException,
|
||||
ProductAlreadyExistsException,
|
||||
UnauthorizedVendorAccessException,
|
||||
ValidationException,
|
||||
VendorAlreadyExistsException,
|
||||
VendorNotFoundException,
|
||||
)
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.product import Product
|
||||
from models.database.user import User
|
||||
@@ -108,7 +110,7 @@ class VendorService:
|
||||
limit: int = 100,
|
||||
active_only: bool = True,
|
||||
verified_only: bool = False,
|
||||
) -> Tuple[List[Vendor], int]:
|
||||
) -> tuple[list[Vendor], int]:
|
||||
"""
|
||||
Get vendors with filtering.
|
||||
|
||||
@@ -257,7 +259,7 @@ class VendorService:
|
||||
limit: int = 100,
|
||||
active_only: bool = True,
|
||||
featured_only: bool = False,
|
||||
) -> Tuple[List[Product], int]:
|
||||
) -> tuple[list[Product], int]:
|
||||
"""
|
||||
Get products in vendor catalog with filtering.
|
||||
|
||||
|
||||
@@ -8,20 +8,23 @@ Handles:
|
||||
- Role assignment
|
||||
- Permission management
|
||||
"""
|
||||
|
||||
import logging
|
||||
import secrets
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.permissions import get_preset_permissions
|
||||
from app.exceptions import (CannotRemoveOwnerException,
|
||||
InvalidInvitationTokenException,
|
||||
MaxTeamMembersReachedException,
|
||||
TeamInvitationAlreadyAcceptedException,
|
||||
TeamMemberAlreadyExistsException,
|
||||
UserNotFoundException, VendorNotFoundException)
|
||||
from app.exceptions import (
|
||||
CannotRemoveOwnerException,
|
||||
InvalidInvitationTokenException,
|
||||
MaxTeamMembersReachedException,
|
||||
TeamInvitationAlreadyAcceptedException,
|
||||
TeamMemberAlreadyExistsException,
|
||||
UserNotFoundException,
|
||||
)
|
||||
from middleware.auth import AuthManager
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Role, Vendor, VendorUser, VendorUserType
|
||||
@@ -43,8 +46,8 @@ class VendorTeamService:
|
||||
inviter: User,
|
||||
email: str,
|
||||
role_name: str,
|
||||
custom_permissions: Optional[List[str]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
custom_permissions: list[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Invite a new team member to a vendor.
|
||||
|
||||
@@ -196,9 +199,9 @@ class VendorTeamService:
|
||||
db: Session,
|
||||
invitation_token: str,
|
||||
password: str,
|
||||
first_name: Optional[str] = None,
|
||||
last_name: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
first_name: str | None = None,
|
||||
last_name: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Accept a team invitation and activate account.
|
||||
|
||||
@@ -330,7 +333,7 @@ class VendorTeamService:
|
||||
vendor: Vendor,
|
||||
user_id: int,
|
||||
new_role_name: str,
|
||||
custom_permissions: Optional[List[str]] = None,
|
||||
custom_permissions: list[str] | None = None,
|
||||
) -> VendorUser:
|
||||
"""
|
||||
Update a team member's role.
|
||||
@@ -392,7 +395,7 @@ class VendorTeamService:
|
||||
db: Session,
|
||||
vendor: Vendor,
|
||||
include_inactive: bool = False,
|
||||
) -> List[Dict[str, Any]]:
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
Get all team members for a vendor.
|
||||
|
||||
@@ -445,7 +448,7 @@ class VendorTeamService:
|
||||
db: Session,
|
||||
vendor: Vendor,
|
||||
role_name: str,
|
||||
custom_permissions: Optional[List[str]] = None,
|
||||
custom_permissions: list[str] | None = None,
|
||||
) -> Role:
|
||||
"""Get existing role or create new one with preset/custom permissions."""
|
||||
# Try to find existing role with same name
|
||||
@@ -492,7 +495,6 @@ class VendorTeamService:
|
||||
# - Vendor name
|
||||
# - Inviter name
|
||||
# - Expiry date
|
||||
pass
|
||||
|
||||
|
||||
# Create service instance
|
||||
|
||||
@@ -8,21 +8,24 @@ Handles theme CRUD operations, preset application, and validation.
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.theme_presets import (THEME_PRESETS, apply_preset,
|
||||
get_available_presets, get_preset_preview)
|
||||
from app.core.theme_presets import (
|
||||
THEME_PRESETS,
|
||||
apply_preset,
|
||||
get_available_presets,
|
||||
get_preset_preview,
|
||||
)
|
||||
from app.exceptions.vendor import VendorNotFoundException
|
||||
from app.exceptions.vendor_theme import (InvalidColorFormatException,
|
||||
InvalidFontFamilyException,
|
||||
InvalidThemeDataException,
|
||||
ThemeOperationException,
|
||||
ThemePresetAlreadyAppliedException,
|
||||
ThemePresetNotFoundException,
|
||||
ThemeValidationException,
|
||||
VendorThemeNotFoundException)
|
||||
from app.exceptions.vendor_theme import (
|
||||
InvalidColorFormatException,
|
||||
InvalidFontFamilyException,
|
||||
ThemeOperationException,
|
||||
ThemePresetNotFoundException,
|
||||
ThemeValidationException,
|
||||
VendorThemeNotFoundException,
|
||||
)
|
||||
from models.database.vendor import Vendor
|
||||
from models.database.vendor_theme import VendorTheme
|
||||
from models.schema.vendor_theme import ThemePresetPreview, VendorThemeUpdate
|
||||
@@ -77,7 +80,7 @@ class VendorThemeService:
|
||||
# THEME RETRIEVAL
|
||||
# ============================================================================
|
||||
|
||||
def get_theme(self, db: Session, vendor_code: str) -> Dict:
|
||||
def get_theme(self, db: Session, vendor_code: str) -> dict:
|
||||
"""
|
||||
Get theme for vendor. Returns default if no custom theme exists.
|
||||
|
||||
@@ -107,7 +110,7 @@ class VendorThemeService:
|
||||
|
||||
return theme.to_dict()
|
||||
|
||||
def _get_default_theme(self) -> Dict:
|
||||
def _get_default_theme(self) -> dict:
|
||||
"""
|
||||
Get default theme configuration.
|
||||
|
||||
@@ -329,7 +332,7 @@ class VendorThemeService:
|
||||
operation="apply_preset", vendor_code=vendor_code, reason=str(e)
|
||||
)
|
||||
|
||||
def get_available_presets(self) -> List[ThemePresetPreview]:
|
||||
def get_available_presets(self) -> list[ThemePresetPreview]:
|
||||
"""
|
||||
Get list of available theme presets.
|
||||
|
||||
@@ -351,7 +354,7 @@ class VendorThemeService:
|
||||
# THEME DELETION
|
||||
# ============================================================================
|
||||
|
||||
def delete_theme(self, db: Session, vendor_code: str) -> Dict:
|
||||
def delete_theme(self, db: Session, vendor_code: str) -> dict:
|
||||
"""
|
||||
Delete custom theme for vendor (reverts to default).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user