# collections - 藏品路由
# Version: 0.0.1
# 更新：

import os
# Version: 1.2.x
# 更新：
import os
# 更新：
import uuid
# 更新：
import re
# 更新：
from typing import Optional, List
# 更新：
from fastapi import APIRouter, Depends, HTTPException, status, Query, UploadFile, File
# 更新：
from sqlalchemy import func, text
# 更新：
from sqlalchemy.orm import Session, joinedload
# 更新：
from app.core.database import get_db
# 更新：
from app.core.auth import get_current_user
# 更新：
from app.core.logging_config import logger
# 更新：
from app.models.models import User, Collection, CollectionImage, Operation
# 更新：
from app.schemas.schemas import (
# 更新：
    CollectionCreate, CollectionUpdate, CollectionResponse, 
# 更新：
    CollectionListResponse, CollectionImageResponse
# 更新：
)
# 更新：
from app.services.oss import upload_to_oss, get_oss_path, delete_from_oss, get_public_url
# 更新：

# 更新：
router = APIRouter(prefix="/api/collections", tags=["藏品"])
# 更新：

# 更新：

# 更新：
def to_camel_case(data: dict) -> dict:
# 更新：
    """将字段编码转换为 camelCase 格式"""
# 更新：
    if not data:
# 更新：
        return data
# 更新：
    
# 更新：
    mapping = {
# 更新：
        'f99_90_id': 'id',
# 更新：
        'f99_91_user_id': 'userId',
# 更新：
        'f99_92_created_at': 'createdAt',
# 更新：
        'f99_93_updated_at': 'updatedAt',
# 更新：
        'f01_01_name': 'name',
# 更新：
        'f01_02_code': 'code',
# 更新：
        'f01_03_category': 'category',
# 更新：
        'f01_04_status': 'status',
# 更新：
        'f01_05_remark': 'remark',
# 更新：
        'f02_10_prefix_serial': 'prefixSerial',
# 更新：
        'f02_11_version': 'version',
# 更新：
        'f02_12_packaging': 'packaging',
# 更新：
        'f02_13_rarity': 'rarity',
# 更新：
        'f02_14_number_category': 'numberCategory',
# 更新：
        'f03_20_is_graded': 'isGraded',
# 更新：
        'f03_21_grading_company': 'gradingCompany',
# 更新：
        'f03_22_grading_score': 'gradingScore',
# 更新：
        'f03_23_three_star': 'threeStar',
# 更新：
        'f04_30_special_mark': 'specialMark',
# 更新：
        'f04_31_serial_feature': 'serialFeature',
# 更新：
        'f04_32_issuer': 'issuer',
# 更新：
        'f04_33_issue_year': 'issueYear',
# 更新：
        'f04_34_material': 'material',
# 更新：
        'f04_35_denomination': 'denomination',
# 更新：
        'f04_36_issue_quantity': 'issueQuantity',
# 更新：
        'f05_40_cost_price': 'costPrice',
# 更新：
        'f05_41_target_price': 'targetPrice',
# 更新：
        'f05_42_goal_price': 'goalPrice',
# 更新：
        'f05_43_repair_fee': 'repairFee',
# 更新：
        'f05_44_grading_fee': 'gradingFee',
# 更新：
        'f06_50_purpose': 'purpose',
# 更新：
        'images': 'images',
# 更新：
    }
# 更新：
    
# 更新：
    return {mapping.get(k, k): v for k, v in data.items()}
# 更新：

# 更新：

# 更新：
# 编码生成函数
# 更新：
def generate_code(version: str, user_id: str, db: Session) -> str:
# 更新：
    """自动生成藏品编号 - 按用户独立编码"""
# 更新：
    import re
# 更新：
    
# 更新：
    # 查询当前用户的非空编码（不与其他用户混算）- 使用行锁防止并发
# 更新：
    user_codes = db.query(Collection.f01_02_code).filter(
# 更新：
        Collection.f01_02_code.isnot(None),
# 更新：
        Collection.f99_91_user_id == user_id
# 更新：
    ).with_for_update().all()
# 更新：
    
# 更新：
    max_num = 0
# 更新：
    for (code,) in user_codes:
# 更新：
        # 处理纯数字编码（支持4位和5位）
# 更新：
        if re.match(r'^\d{4,5}$', code):
# 更新：
            try:
# 更新：
                num = int(code)
# 更新：
                if num > max_num:
# 更新：
                    max_num = num
# 更新：
            except (ValueError, TypeError):
# 更新：
                pass
# 更新：
    
# 更新：
    # 当前用户最大号 +1
# 更新：
    next_num = max_num + 1
# 更新：
    
# 更新：
    # 如果超过9999，使用5位；否则使用4位
# 更新：
    if next_num > 9999:
# 更新：
        return str(next_num).zfill(5)
# 更新：
    else:
# 更新：
        return str(next_num).zfill(4)
# 更新：

# 更新：

# 更新：
@router.get("/next-code")
# 更新：
def get_next_code(
# 更新：
    current_user: User = Depends(get_current_user),
# 更新：
    db: Session = Depends(get_db)
# 更新：
):
# 更新：
    """获取下一个藏品编号"""
# 更新：
    next_code = generate_code("2024 龙", current_user.f99_90_id, db)
# 更新：
    return {"code": 200, "data": {"nextCode": next_code}}
# 更新：

# 更新：

# 更新：
@router.get("")
# 更新：
def get_collections(
# 更新：
    id: str = Query(None, description="filter by collection id"),
# 更新：
    category: Optional[str] = None,
# 更新：
    status: Optional[str] = None,
# 更新：
    search: Optional[str] = None,
# 更新：
    specialMark: Optional[str] = Query(None, description="special mark filter"),
# 更新：
    numberCategory: Optional[str] = Query(None, description="number category filter"),
# 更新：
    gradingCompany: Optional[str] = Query(None, description="grading company filter"),
# 更新：
    gradingScore: Optional[str] = Query(None, description="grading score filter"),
# 更新：
    packaging: Optional[str] = Query(None, description="packaging filter"),
# 更新：
    rarity: Optional[str] = Query(None, description="rarity filter"),
# 更新：
    version: Optional[str] = Query(None, description="version filter"),
# 更新：
    profitLoss: Optional[str] = Query(None, description="profit loss filter: profit or loss"),
# 更新：
    page: int = Query(1, ge=1),
# 更新：
    limit: int = Query(20, ge=1, le=500),
# 更新：
    sortBy: str = Query('createdAt'),
# 更新：
    sortOrder: str = Query('desc'),
# 更新：
    all_users: bool = Query(False, description="return all users data for admin"),
# 更新：
    user_id: str = Query(None, description="filter by user id"),
# 更新：
    current_user: User = Depends(get_current_user),
# 更新：
    db: Session = Depends(get_db)
# 更新：
):
# 更新：
    """获取藏品列表"""
# 更新：
    # admin 用户可以看到所有藏品，普通用户只能看到自己的
# 更新：
    # 如果指定 all_users=true，则返回所有用户藏品
# 更新：
    from sqlalchemy.orm import joinedload
# 更新：
    
# 更新：
    # 管理员默认查看全库，普通用户只看自己，未登录返回空列表
# 更新：
    if current_user is None or current_user.role != "admin":
# 更新：
        # 非管理员或未登录用户只能查看自己的藏品（如果没有登录则返回空）
# 更新：
        if current_user is None:
# 更新：
            return {"data": [], "total": 0, "page": 1, "limit": 20}
# 更新：
        query = db.query(Collection).filter(Collection.f99_91_user_id == current_user.f99_90_id)
# 更新：
    else:
# 更新：
        # 管理员查看所有藏品
# 更新：
        query = db.query(Collection, User.f01_01_name.label('owner_name')).join(
# 更新：
            User, Collection.f99_91_user_id == User.f99_90_id, isouter=True
# 更新：
        )
# 更新：
    
# 更新：
    # 如果指定了user_id参数，则只返回该用户的藏品
# 更新：
    if user_id:
# 更新：
        query = query.filter(Collection.f99_91_user_id == user_id)
# 更新：
    
# 更新：
    # 按ID精确筛选
# 更新：
    if id:
# 更新：
        query = query.filter(Collection.f99_90_id == id)
# 更新：
    
# 更新：
    if category:
# 更新：
        query = query.filter(Collection.f01_03_category == category)
# 更新：
    if status:
# 更新：
        query = query.filter(Collection.f01_04_status == status)
# 更新：
    if specialMark:
# 更新：
        query = query.filter(Collection.f04_30_special_mark == specialMark)
# 更新：
    if gradingCompany:
# 更新：
        query = query.filter(Collection.f03_21_grading_company.contains(gradingCompany))
# 更新：
    if gradingScore:
# 更新：
        query = query.filter(Collection.f03_22_grading_score.contains(gradingScore))
# 更新：
    if numberCategory:
# 更新：
        query = query.filter(Collection.f02_14_number_category.contains(numberCategory))
# 更新：
    if packaging:
# 更新：
        query = query.filter(Collection.f02_12_packaging== packaging)
# 更新：
    if rarity:
# 更新：
        query = query.filter(Collection.f02_13_rarity.contains(rarity))
# 更新：
    if version:
# 更新：
        query = query.filter(Collection.f02_11_version.contains(version))
# 更新：
    
# 更新：
    # 盈亏筛选（只对已售藏品有效）
# 更新：
    if profitLoss:
# 更新：
        if profitLoss == 'profit':
# 更新：
            # 盈利：售价 > 成本价
# 更新：
            query = query.filter(
# 更新：
                Collection.f01_04_status == 'sold',
# 更新：
                Collection.f05_42_goal_price > Collection.f05_40_cost_price
# 更新：
            )
# 更新：
        elif profitLoss == 'loss':
# 更新：
            # 亏损：售价 <= 成本价
# 更新：
            query = query.filter(
# 更新：
                Collection.f01_04_status == 'sold',
# 更新：
                Collection.f05_42_goal_price <= Collection.f05_40_cost_price
# 更新：
            )
# 更新：
    
# 更新：
    if search:
# 更新：
        query = query.filter(
# 更新：
            (Collection.f01_01_name.contains(search)) | 
# 更新：
            (Collection.f01_05_remark.contains(search))
# 更新：
        )
# 更新：
    
# 更新：
    # 总数（应用筛选条件后的数量）
# 更新：
    total = query.count()
# 更新：
    
# 更新：
    # 使用joinedload预加载图片，避免N+1查询问题
# 更新：
    query = query.options(joinedload(Collection.images))
# 更新：
    
# 更新：
    # 分页
# 更新：
    data = query.order_by(Collection.f99_92_created_at.desc()) \
# 更新：
        .offset((page - 1) * limit) \
# 更新：
        .limit(limit) \
# 更新：
        .all()
# 更新：
    
# 更新：
    # 转换为字典列表并转为 camelCase
# 更新：
    data_list = []
# 更新：
    for item in data:
# 更新：
        # 处理联表查询结果
# 更新：
        if current_user is not None and current_user.role == "admin":
# 更新：
            collection_item, owner_name = item
# 更新：
        else:
# 更新：
            collection_item = item
# 更新：
            owner_name = None
# 更新：
        
# 更新：
        item_dict = {
# 更新：
            'f99_90_id': collection_item.f99_90_id,
# 更新：
            'f99_91_user_id': collection_item.f99_91_user_id,
# 更新：
            'owner_name': owner_name,  # 所属用户名（仅管理员可见）
# 更新：
            'f01_01_name': collection_item.f01_01_name,
# 更新：
            'f01_02_code': collection_item.f01_02_code,
# 更新：
            'f01_03_category': collection_item.f01_03_category,
# 更新：
            'f01_04_status': collection_item.f01_04_status,
# 更新：
            'f01_05_remark': collection_item.f01_05_remark,
# 更新：
            'f02_10_prefix_serial': collection_item.f02_10_prefix_serial,
# 更新：
            'f02_11_version': collection_item.f02_11_version,
# 更新：
            'f02_12_packaging': collection_item.f02_12_packaging,
# 更新：
            'f02_13_rarity': collection_item.f02_13_rarity,
# 更新：
            'f02_14_number_category': collection_item.f02_14_number_category,
# 更新：
            'f03_20_is_graded': collection_item.f03_20_is_graded,
# 更新：
            'f03_21_grading_company': collection_item.f03_21_grading_company,
# 更新：
            'f03_22_grading_score': collection_item.f03_22_grading_score,
# 更新：
            'f03_23_three_star': collection_item.f03_23_three_star,
# 更新：
            'f04_30_special_mark': collection_item.f04_30_special_mark,
# 更新：
            'f04_31_serial_feature': collection_item.f04_31_serial_feature,
# 更新：
            'f04_32_issuer': collection_item.f04_32_issuer,
# 更新：
            'f04_33_issue_year': collection_item.f04_33_issue_year,
# 更新：
            'f04_34_material': collection_item.f04_34_material,
# 更新：
            'f04_35_denomination': collection_item.f04_35_denomination,
# 更新：
            'f04_36_issue_quantity': collection_item.f04_36_issue_quantity,
# 更新：
            'f05_40_cost_price': float(collection_item.f05_40_cost_price) if collection_item.f05_40_cost_price else None,
# 更新：
            'f05_41_target_price': float(collection_item.f05_41_target_price) if collection_item.f05_41_target_price else None,
# 更新：
            'f05_42_goal_price': float(collection_item.f05_42_goal_price) if collection_item.f05_42_goal_price else None,
# 更新：
            'f05_43_repair_fee': float(collection_item.f05_43_repair_fee) if collection_item.f05_43_repair_fee else None,
# 更新：
            'f05_44_grading_fee': float(collection_item.f05_44_grading_fee) if collection_item.f05_44_grading_fee else None,
# 更新：
            'f06_50_purpose': collection_item.f06_50_purpose,
# 更新：
            'f99_92_created_at': collection_item.f99_92_created_at.isoformat() if collection_item.f99_92_created_at else None,
# 更新：
            'images': []
# 更新：
        }
# 更新：
        
# 更新：
        # 直接使用预加载的图片数据，无需再查询
# 更新：
        for img in collection_item.images:
# 更新：
            item_dict['images'].append({
# 更新：
                'id': img.id,
# 更新：
                'filename': img.filename,
# 更新：
                'original_name': img.original_name,
# 更新：
                'path': img.path,
# 更新：
                'created_at': img.created_at.isoformat() if img.created_at else None
# 更新：
            })
# 更新：
        
# 更新：
        data_list.append(to_camel_case(item_dict))
# 更新：
    
# 更新：
    return {
# 更新：
        "data": data_list,
# 更新：
        "pagination": {
# 更新：
            "page": page,
# 更新：
            "limit": limit,
# 更新：
            "total": total,
# 更新：
            "pages": (total + limit - 1) // limit
# 更新：
        }
# 更新：
    }
# 更新：

# 更新：

# 更新：
@router.get("/stats")
# 更新：
def get_stats(
# 更新：
    current_user: User = Depends(get_current_user),
# 更新：
    db: Session = Depends(get_db)
# 更新：
):
# 更新：
    """获取藏品统计 - 使用SQL聚合查询优化性能"""
# 更新：
    
# 更新：
    # 非管理员或未登录用户只能查看自己的藏品
# 更新：
    if current_user is None:
# 更新：
        return {
# 更新：
            "totalCount": 0,
# 更新：
            "byCategory": [],
# 更新：
            "byStatus": [],
# 更新：
            "byGrading": [],
# 更新：
            "byPackaging": [],
# 更新：
            "byRarity": [],
# 更新：
            "byVersion": [],
# 更新：
            "byGradingCompany": [],
# 更新：
            "byGradingScore": [],
# 更新：
            "bySpecialMark": [],
# 更新：
            "byNumberCategory": [],
# 更新：
            "byProfitLoss": [],
# 更新：
            "totalCost": 0,
# 更新：
            "totalTarget": 0,
# 更新：
            "expectedProfit": 0,
# 更新：
            "totalRevenue": 0,
# 更新：
            "totalProfit": 0
# 更新：
        }
# 更新：
    
# 更新：
    # 构建基础查询条件
# 更新：
    is_admin = current_user.role == "admin"
# 更新：
    
# 更新：
    if not is_admin:
# 更新：
        base_filter = Collection.f99_91_user_id == current_user.f99_90_id
# 更新：
    else:
# 更新：
        base_filter = None
# 更新：
    
# 更新：
    # 总数 - 使用SQL COUNT
# 更新：
    total_count = db.query(func.count(Collection.f99_90_id)).filter(
# 更新：
        base_filter if base_filter is not None else True
# 更新：
    ).scalar()
# 更新：
    if base_filter is not None:
# 更新：
        total_count = db.query(func.count(Collection.f99_90_id)).filter(base_filter).scalar()
# 更新：
    else:
# 更新：
        total_count = db.query(func.count(Collection.f99_90_id)).scalar()
# 更新：
    
# 更新：
    # 按分类统计 - 使用SQL GROUP BY
# 更新：
    if base_filter is not None:
# 更新：
        by_category = db.query(
# 更新：
            Collection.f01_03_category,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(base_filter).group_by(Collection.f01_03_category).all()
# 更新：
        
# 更新：
        by_status = db.query(
# 更新：
            Collection.f01_04_status,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(base_filter).group_by(Collection.f01_04_status).all()
# 更新：
        
# 更新：
        by_graded = db.query(
# 更新：
            Collection.f03_20_is_graded,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(base_filter).group_by(Collection.f03_20_is_graded).all()
# 更新：
        
# 更新：
        by_packaging = db.query(
# 更新：
            Collection.f02_12_packaging,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(base_filter, Collection.f02_12_packaging.isnot(None)).group_by(Collection.f02_12_packaging).all()
# 更新：
        
# 更新：
        by_rarity = db.query(
# 更新：
            Collection.f02_13_rarity,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(base_filter, Collection.f02_13_rarity.isnot(None)).group_by(Collection.f02_13_rarity).all()
# 更新：
        
# 更新：
        by_version = db.query(
# 更新：
            Collection.f02_11_version,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(base_filter, Collection.f02_11_version.isnot(None)).group_by(Collection.f02_11_version).all()
# 更新：
        
# 更新：
        by_grading_company = db.query(
# 更新：
            Collection.f03_21_grading_company,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(base_filter, Collection.f03_20_is_graded == True, Collection.f03_21_grading_company.isnot(None)).group_by(Collection.f03_21_grading_company).all()
# 更新：
        
# 更新：
        by_grading_score = db.query(
# 更新：
            Collection.f03_22_grading_score,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(base_filter, Collection.f03_20_is_graded == True, Collection.f03_22_grading_score.isnot(None)).group_by(Collection.f03_22_grading_score).all()
# 更新：
        
# 更新：
        by_special_mark = db.query(
# 更新：
            Collection.f04_30_special_mark,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(base_filter, Collection.f04_30_special_mark.isnot(None)).group_by(Collection.f04_30_special_mark).all()
# 更新：
        
# 更新：
        by_number_category = db.query(
# 更新：
            Collection.f02_14_number_category,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(base_filter, Collection.f02_14_number_category.isnot(None)).group_by(Collection.f02_14_number_category).all()
# 更新：
        
# 更新：
        # 成本相关统计 - 使用SQL SUM
# 更新：
        cost_result = db.query(
# 更新：
            func.coalesce(func.sum(Collection.f05_40_cost_price), 0) +
# 更新：
            func.coalesce(func.sum(Collection.f05_43_repair_fee), 0) +
# 更新：
            func.coalesce(func.sum(Collection.f05_44_grading_fee), 0)
# 更新：
        ).filter(base_filter).first()
# 更新：
        total_cost = cost_result[0] if cost_result else 0
# 更新：
        
# 更新：
        # 预期利润
# 更新：
        expected_profit_result = db.query(
# 更新：
            func.coalesce(func.sum(Collection.f05_41_target_price - Collection.f05_40_cost_price), 0)
# 更新：
        ).filter(base_filter, Collection.f05_41_target_price.isnot(None), Collection.f05_41_target_price > 0).first()
# 更新：
        expected_profit = expected_profit_result[0] if expected_profit_result else 0
# 更新：
        
# 更新：
        # 已售藏品统计
# 更新：
        sold_collections = db.query(Collection).filter(
# 更新：
            base_filter,
# 更新：
            Collection.f01_04_status == 'sold',
# 更新：
            Collection.f05_42_goal_price.isnot(None),
# 更新：
            Collection.f05_42_goal_price > 0
# 更新：
        ).all()
# 更新：
        
# 更新：
    else:
# 更新：
        # 管理员查看所有数据
# 更新：
        by_category = db.query(
# 更新：
            Collection.f01_03_category,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).group_by(Collection.f01_03_category).all()
# 更新：
        
# 更新：
        by_status = db.query(
# 更新：
            Collection.f01_04_status,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).group_by(Collection.f01_04_status).all()
# 更新：
        
# 更新：
        by_graded = db.query(
# 更新：
            Collection.f03_20_is_graded,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).group_by(Collection.f03_20_is_graded).all()
# 更新：
        
# 更新：
        by_packaging = db.query(
# 更新：
            Collection.f02_12_packaging,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(Collection.f02_12_packaging.isnot(None)).group_by(Collection.f02_12_packaging).all()
# 更新：
        
# 更新：
        by_rarity = db.query(
# 更新：
            Collection.f02_13_rarity,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(Collection.f02_13_rarity.isnot(None)).group_by(Collection.f02_13_rarity).all()
# 更新：
        
# 更新：
        by_version = db.query(
# 更新：
            Collection.f02_11_version,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(Collection.f02_11_version.isnot(None)).group_by(Collection.f02_11_version).all()
# 更新：
        
# 更新：
        by_grading_company = db.query(
# 更新：
            Collection.f03_21_grading_company,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(Collection.f03_20_is_graded == True, Collection.f03_21_grading_company.isnot(None)).group_by(Collection.f03_21_grading_company).all()
# 更新：
        
# 更新：
        by_grading_score = db.query(
# 更新：
            Collection.f03_22_grading_score,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(Collection.f03_20_is_graded == True, Collection.f03_22_grading_score.isnot(None)).group_by(Collection.f03_22_grading_score).all()
# 更新：
        
# 更新：
        by_special_mark = db.query(
# 更新：
            Collection.f04_30_special_mark,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(Collection.f04_30_special_mark.isnot(None)).group_by(Collection.f04_30_special_mark).all()
# 更新：
        
# 更新：
        by_number_category = db.query(
# 更新：
            Collection.f02_14_number_category,
# 更新：
            func.count(Collection.f99_90_id)
# 更新：
        ).filter(Collection.f02_14_number_category.isnot(None)).group_by(Collection.f02_14_number_category).all()
# 更新：
        
# 更新：
        # 总成本
# 更新：
        cost_result = db.query(
# 更新：
            func.coalesce(func.sum(Collection.f05_40_cost_price), 0) +
# 更新：
            func.coalesce(func.sum(Collection.f05_43_repair_fee), 0) +
# 更新：
            func.coalesce(func.sum(Collection.f05_44_grading_fee), 0)
# 更新：
        ).first()
# 更新：
        total_cost = cost_result[0] if cost_result else 0
# 更新：
        
# 更新：
        # 预期利润
# 更新：
        expected_profit_result = db.query(
# 更新：
            func.coalesce(func.sum(Collection.f05_41_target_price - Collection.f05_40_cost_price), 0)
# 更新：
        ).filter(Collection.f05_41_target_price.isnot(None), Collection.f05_41_target_price > 0).first()
# 更新：
        expected_profit = expected_profit_result[0] if expected_profit_result else 0
# 更新：
        
# 更新：
        # 已售藏品
# 更新：
        sold_collections = db.query(Collection).filter(
# 更新：
            Collection.f01_04_status == 'sold',
# 更新：
            Collection.f05_42_goal_price.isnot(None),
# 更新：
            Collection.f05_42_goal_price > 0
# 更新：
        ).all()
# 更新：
    
# 更新：
    # 总收入和总利润（已售藏品）
# 更新：
    total_revenue = sum(c.f05_42_goal_price or 0 for c in sold_collections)
# 更新：
    total_profit = sum(
# 更新：
        (c.f05_42_goal_price or 0) - (c.f05_40_cost_price or 0) - (c.f05_43_repair_fee or 0) - (c.f05_44_grading_fee or 0)
# 更新：
        for c in sold_collections
# 更新：
    )
# 更新：
    
# 更新：
    # 盈亏统计
# 更新：
    profit_count = sum(1 for c in sold_collections if c.f05_42_goal_price and c.f05_40_cost_price and c.f05_42_goal_price > c.f05_40_cost_price)
# 更新：
    loss_count = sum(1 for c in sold_collections if c.f05_42_goal_price and c.f05_40_cost_price and c.f05_42_goal_price <= c.f05_40_cost_price)
# 更新：
    
# 更新：
    # 目标价格总和
# 更新：
    total_target_result = db.query(func.coalesce(func.sum(Collection.f05_41_target_price), 0)).filter(
# 更新：
        base_filter if base_filter is not None else True
# 更新：
    ).first()
# 更新：
    if base_filter is not None:
# 更新：
        total_target_result = db.query(func.coalesce(func.sum(Collection.f05_41_target_price), 0)).filter(base_filter).first()
# 更新：
    else:
# 更新：
        total_target_result = db.query(func.coalesce(func.sum(Collection.f05_41_target_price), 0)).first()
# 更新：
    total_target = total_target_result[0] if total_target_result else 0
# 更新：
    
# 更新：
    return {
# 更新：
        "totalCount": total_count,
# 更新：
        "byCategory": [{"category": c, "count": n} for c, n in by_category],
# 更新：
        "byStatus": [{"status": s, "count": n} for s, n in by_status],
# 更新：
        "byGrading": [{"isGraded": g, "count": n} for g, n in by_graded],
# 更新：
        "byPackaging": [{"packaging": p, "count": n} for p, n in by_packaging],
# 更新：
        "byRarity": [{"rarity": r, "count": n} for r, n in by_rarity],
# 更新：
        "byVersion": [{"version": v, "count": n} for v, n in by_version],
# 更新：
        "byGradingCompany": [{"company": c, "count": n} for c, n in by_grading_company],
# 更新：
        "byGradingScore": [{"score": s, "count": n} for s, n in by_grading_score],
# 更新：
        "bySpecialMark": [{"mark": m, "count": n} for m, n in by_special_mark],
# 更新：
        "byNumberCategory": [{"numberCategory": n, "count": c} for n, c in by_number_category],
# 更新：
        "byProfitLoss": [
# 更新：
            {"type": "profit", "label": "盈利", "count": profit_count},
# 更新：
            {"type": "loss", "label": "亏损", "count": loss_count}
# 更新：
        ],
# 更新：
        "totalCost": total_cost,
# 更新：
        "totalTarget": total_target,
# 更新：
        "expectedProfit": expected_profit,
# 更新：
        "totalRevenue": total_revenue,
# 更新：
        "totalProfit": total_profit
# 更新：
    }
# 更新：

# 更新：

# 更新：
@router.get("/{collection_id}")
# 更新：
def get_collection(
# 更新：
    collection_id: str,
# 更新：
    current_user: User = Depends(get_current_user),
# 更新：
    db: Session = Depends(get_db)
# 更新：
):
# 更新：
    """获取单个藏品详情"""
# 更新：
    result = db.execute(
# 更新：
        text("SELECT * FROM collections WHERE f99_90_id = :id"),
# 更新：
        {"id": collection_id}
# 更新：
    ).fetchone()
# 更新：
    
# 更新：
    if not result:
# 更新：
        raise HTTPException(status_code=404, detail="E00033: 藏品不存在")
# 更新：
    
# 更新：
    collection = dict(result._mapping)
# 更新：
    
# 更新：
    # 非管理员只能查看自己的藏品
# 更新：
    if (current_user is None or current_user.role != "admin") and collection.get('f99_91_user_id') != current_user.f99_90_id:
# 更新：
        raise HTTPException(status_code=403, detail="无权访问")
# 更新：
    
# 更新：
    result_dict = {
# 更新：
        'f99_90_id': collection.get('f99_90_id'),
# 更新：
        'f99_91_user_id': collection.get('f99_91_user_id'),
# 更新：
        'f01_01_name': collection.get('f01_01_name'),
# 更新：
        'f01_02_code': collection.get('f01_02_code'),
# 更新：
        'f01_03_category': collection.get('f01_03_category'),
# 更新：
        'f01_04_status': collection.get('f01_04_status'),
# 更新：
        'f01_05_remark': collection.get('f01_05_remark'),
# 更新：
        'f02_10_prefix_serial': collection.get('f02_10_prefix_serial'),
# 更新：
        'f02_11_version': collection.get('f02_11_version'),
# 更新：
        'f02_12_packaging': collection.get('f02_12_packaging'),
# 更新：
        'f02_13_rarity': collection.get('f02_13_rarity'),
# 更新：
        'f02_14_number_category': collection.get('f02_14_number_category'),
# 更新：
        'f03_20_is_graded': collection.get('f03_20_is_graded'),
# 更新：
        'f03_21_grading_company': collection.get('f03_21_grading_company'),
# 更新：
        'f03_22_grading_score': collection.get('f03_22_grading_score'),
# 更新：
        'f03_23_three_star': collection.get('f03_23_three_star'),
# 更新：
        'f04_30_special_mark': collection.get('f04_30_special_mark'),
# 更新：
        'f04_31_serial_feature': collection.get('f04_31_serial_feature'),
# 更新：
        'f04_32_issuer': collection.get('f04_32_issuer'),
# 更新：
        'f04_33_issue_year': collection.get('f04_33_issue_year'),
# 更新：
        'f04_34_material': collection.get('f04_34_material'),
# 更新：
        'f04_35_denomination': collection.get('f04_35_denomination'),
# 更新：
        'f04_36_issue_quantity': collection.get('f04_36_issue_quantity'),
# 更新：
        'f05_40_cost_price': float(collection.get('f05_40_cost_price')) if collection.get('f05_40_cost_price') else None,
# 更新：
        'f05_41_target_price': float(collection.get('f05_41_target_price')) if collection.get('f05_41_target_price') else None,
# 更新：
        'f05_42_goal_price': float(collection.get('f05_42_goal_price')) if collection.get('f05_42_goal_price') else None,
# 更新：
        'f05_43_repair_fee': float(collection.get('f05_43_repair_fee')) if collection.get('f05_43_repair_fee') else None,
# 更新：
        'f05_44_grading_fee': float(collection.get('f05_44_grading_fee')) if collection.get('f05_44_grading_fee') else None,
# 更新：
        'f06_50_purpose': collection.get('f06_50_purpose'),
# 更新：
        'f99_92_created_at': collection.get('f99_92_created_at').isoformat() if collection.get('f99_92_created_at') else None,
# 更新：
        'images': []
# 更新：
    }
# 更新：
    
# 更新：
    # 加载图片数据
# 更新：
    images = db.query(CollectionImage).filter(
# 更新：
        CollectionImage.collection_id == collection_id
# 更新：
    ).all()
# 更新：
    
# 更新：
    for img in images:
# 更新：
        result_dict['images'].append({
# 更新：
            'id': img.id,
# 更新：
            'filename': img.filename,
# 更新：
            'original_name': img.original_name,
# 更新：
            'path': img.path,
# 更新：
            'created_at': img.created_at.isoformat() if img.created_at else None
# 更新：
        })
# 更新：
    
# 更新：
    return to_camel_case(result_dict)
# 更新：

# 更新：

# 更新：
@router.post("")
# 更新：
def create_collection(
# 更新：
    collection_data: CollectionCreate,
# 更新：
    force: bool = False,  # 是否强制保存（忽略重复警告）
# 更新：
    current_user: User = Depends(get_current_user),
# 更新：
    db: Session = Depends(get_db)
# 更新：
):
# 更新：
    """创建藏品 - 支持冠字号查重"""
# 更新：
    from app.core.logging_config import logger
# 更新：
    
# 更新：
    # 自动生成编码
# 更新：
    final_code = collection_data.f01_02_code or generate_code(
# 更新：
        collection_data.f02_11_version or '2024 龙',
# 更新：
        current_user.f99_90_id,
# 更新：
        db
# 更新：
    )
# 更新：
    
# 更新：
    # 编号查重（如果提供了编号且不是强制保存）
# 更新：
    if not force and final_code:
# 更新：
        existing_code = db.query(Collection).filter(
# 更新：
            Collection.f01_02_code == final_code,
# 更新：
            Collection.f99_91_user_id == current_user.f99_90_id
# 更新：
        ).first()
# 更新：
        
# 更新：
        if existing_code:
# 更新：
            logger.warning(f"发现重复编号：{final_code}, 已存在藏品 ID: {existing_code.f99_90_id}")
# 更新：
            return {
# 更新：
                "error": {
# 更新：
                    "code": "DUPLICATE_CODE",
# 更新：
                    "message": f"藏品编号 {final_code} 已存在，请使用其他编号"
# 更新：
                }
# 更新：
            }
# 更新：
    
# 更新：
    # 冠字号查重（如果提供了冠字号且不是强制保存）
# 更新：
    if not force and collection_data.f02_10_prefix_serial:
# 更新：
        # 查询当前用户是否有相同冠字号的藏品
# 更新：
        existing = db.query(Collection).filter(
# 更新：
            Collection.f02_10_prefix_serial == collection_data.f02_10_prefix_serial,
# 更新：
            Collection.f99_91_user_id == current_user.f99_90_id
# 更新：
        ).first()
# 更新：
        
# 更新：
        if existing:
# 更新：
            logger.warning(f"发现重复冠字号：{collection_data.f02_10_prefix_serial}, 已存在藏品 ID: {existing.f99_90_id}")
# 更新：
            # 返回警告信息，让前端询问用户是否继续
# 更新：
            return {
# 更新：
                "warning": {
# 更新：
                    "code": "DUPLICATE_SERIAL",
# 更新：
                    "message": f"发现重复冠字号：{collection_data.f02_10_prefix_serial}",
# 更新：
                    "existing_collection": {
# 更新：
                        "id": existing.f99_90_id,
# 更新：
                        "name": existing.f01_01_name,
# 更新：
                        "code": existing.f01_02_code,
# 更新：
                        "prefix_serial": existing.f02_10_prefix_serial
# 更新：
                    }
# 更新：
                },
# 更新：
                "data": {
# 更新：
                    "ask_continue": True
# 更新：
                }
# 更新：
            }
# 更新：
    
# 更新：
    # 自动分类：如果未提供号码分类，则根据冠字号自动分类
# 更新：
    if not collection_data.f02_14_number_category and collection_data.f02_10_prefix_serial:
# 更新：
        from app.utils.number_category import get_number_category
# 更新：
        collection_data.f02_14_number_category = get_number_category(collection_data.f02_10_prefix_serial)
# 更新：
    
# 更新：
    collection = Collection(
# 更新：
        f99_91_user_id=current_user.f99_90_id,
# 更新：
        f01_01_name=collection_data.f01_01_name,
# 更新：
        f01_02_code=final_code,
# 更新：
        f01_03_category=collection_data.f01_03_category,
# 更新：
        f01_04_status=collection_data.f01_04_status or "in_collection",
# 更新：
        f01_05_remark=collection_data.f01_05_remark,
# 更新：
        f02_10_prefix_serial=collection_data.f02_10_prefix_serial,
# 更新：
        f02_11_version=collection_data.f02_11_version,
# 更新：
        f02_12_packaging=collection_data.f02_12_packaging,
# 更新：
        f02_13_rarity=collection_data.f02_13_rarity,
# 更新：
        f02_14_number_category=collection_data.f02_14_number_category,
# 更新：
        f03_20_is_graded=collection_data.f03_20_is_graded or False,
# 更新：
        f03_21_grading_company=collection_data.f03_21_grading_company,
# 更新：
        f03_22_grading_score=collection_data.f03_22_grading_score,
# 更新：
        f03_23_three_star=collection_data.f03_23_three_star or False,
# 更新：
        f04_30_special_mark=collection_data.f04_30_special_mark,
# 更新：
        f04_31_serial_feature=collection_data.f04_31_serial_feature,
# 更新：
        f04_32_issuer=collection_data.f04_32_issuer,
# 更新：
        f04_33_issue_year=collection_data.f04_33_issue_year,
# 更新：
        f04_34_material=collection_data.f04_34_material,
# 更新：
        f04_35_denomination=collection_data.f04_35_denomination,
# 更新：
        f04_36_issue_quantity=collection_data.f04_36_issue_quantity,
# 更新：
        f05_40_cost_price=collection_data.f05_40_cost_price,
# 更新：
        f05_41_target_price=collection_data.f05_41_target_price,
# 更新：
        f05_42_goal_price=collection_data.f05_42_goal_price,
# 更新：
        f05_43_repair_fee=collection_data.f05_43_repair_fee,
# 更新：
        f05_44_grading_fee=collection_data.f05_44_grading_fee,
# 更新：
        f06_50_purpose=collection_data.f06_50_purpose
# 更新：
    )
# 更新：
    
# 更新：
    db.add(collection)
# 更新：
    db.commit()
# 更新：
    db.refresh(collection)
# 更新：
    
# 更新：
    return {
# 更新：
        'f99_90_id': collection.f99_90_id,
# 更新：
        'f99_91_user_id': collection.f99_91_user_id,
# 更新：
        'f01_01_name': collection.f01_01_name,
# 更新：
        'f01_02_code': collection.f01_02_code,
# 更新：
        'f01_03_category': collection.f01_03_category,
# 更新：
        'f01_04_status': collection.f01_04_status,
# 更新：
        'f01_05_remark': collection.f01_05_remark,
# 更新：
        'f99_92_created_at': collection.f99_92_created_at.isoformat() if collection.f99_92_created_at else None,
# 更新：
        'message': '创建成功'
# 更新：
    }
# 更新：

# 更新：

# 更新：
@router.put("/{collection_id}")
# 更新：
def update_collection(
# 更新：
    collection_id: str,
# 更新：
    collection_data: CollectionUpdate,
# 更新：
    current_user: User = Depends(get_current_user),
# 更新：
    db: Session = Depends(get_db)
# 更新：
):
# 更新：
    """更新藏品"""
# 更新：
    collection = db.query(Collection).filter(
# 更新：
        Collection.f99_90_id == collection_id,
# 更新：
        Collection.f99_91_user_id == current_user.f99_90_id
# 更新：
    ).first()
# 更新：
    
# 更新：
    if not collection:
# 更新：
        raise HTTPException(status_code=404, detail="E00033: 藏品不存在")
# 更新：
    
# 更新：
    # 更新字段 - 使用 model_fields_set 检查哪些字段被设置
# 更新：
    for field_name in collection_data.model_fields_set:
# 更新：
        value = getattr(collection_data, field_name)
# 更新：
        if value is not None:
# 更新：
            setattr(collection, field_name, value)
# 更新：
    
# 更新：
    db.commit()
# 更新：
    db.refresh(collection)
# 更新：
    
# 更新：
    return {
# 更新：
        "f99_90_id": collection.f99_90_id,
# 更新：
        "message": "更新成功"
# 更新：
    }
# 更新：

# 更新：

# 更新：
@router.delete("/{collection_id}")
# 更新：
def delete_collection(
# 更新：
    collection_id: str,
# 更新：
    current_user: User = Depends(get_current_user),
# 更新：
    db: Session = Depends(get_db)
# 更新：
):
# 更新：
    """删除藏品"""
# 更新：
    # 验证权限并检查是否存在
# 更新：
    collection = db.query(Collection).filter(
# 更新：
        Collection.f99_90_id == collection_id,
# 更新：
        Collection.f99_91_user_id == current_user.f99_90_id
# 更新：
    ).first()
# 更新：
    
# 更新：
    if not collection:
# 更新：
        raise HTTPException(status_code=404, detail="E00033: 藏品不存在")
# 更新：
    
# 更新：
    # 使用原生 SQL 删除（避免 ORM 级联查询字段不匹配问题）
# 更新：
    from sqlalchemy import text
# 更新：
    # 1. 删除关联的 operations（f99_91_user_id 关联到 collections.f99_90_id）
# 更新：
    db.execute(text("DELETE FROM operations WHERE f99_91_user_id = :id"), {"id": collection_id})
# 更新：
    # 2. 删除关联的图片
# 更新：
    db.execute(text("DELETE FROM collection_images WHERE collection_id = :id"), {"id": collection_id})
# 更新：
    # 3. 删除藏品本身
# 更新：
    db.execute(text("DELETE FROM collections WHERE f99_90_id = :id"), {"id": collection_id})
# 更新：
    db.commit()
# 更新：
    
# 更新：
    return {"message": "删除成功"}
# 更新：

# 更新：

# 更新：
@router.post("/upload-image")
# 更新：
async def upload_image(
# 更新：
    collection_id: str = None,
# 更新：
    file: UploadFile = File(...),
# 更新：
    current_user: User = Depends(get_current_user),
# 更新：
    db: Session = Depends(get_db)
# 更新：
):
# 更新：
    """上传藏品图片 - 文件名格式：用户名 - 藏品编号 - 冠字号"""
# 更新：
    try:
# 更新：
        # 验证藏品是否存在
# 更新：
        collection = db.query(Collection).filter(
# 更新：
            Collection.f99_90_id == collection_id
# 更新：
        ).first()
# 更新：
        
# 更新：
        if not collection:
# 更新：
            raise HTTPException(status_code=404, detail="E00033: 藏品不存在")
# 更新：
        
# 更新：
        # 获取用户信息（用于文件名）
# 更新：
        owner = db.query(User).filter(User.f99_90_id == collection.f99_91_user_id).first()
# 更新：
        username = owner.f01_01_name if owner else "unknown"
# 更新：
        
# 更新：
        # 获取藏品信息（用于文件名）
# 更新：
        code = collection.f01_02_code or "0000"
# 更新：
        prefix_serial = collection.f02_10_prefix_serial or ""
# 更新：
        
# 更新：
        # 检查文件类型
# 更新：
        if not file.content_type.startswith('image/'):
# 更新：
            raise HTTPException(status_code=400, detail="E00038: 只能上传图片文件")
# 更新：
        
# 更新：
        # 检查文件大小（限制 10MB）
# 更新：
        file_size = 0
# 更新：
        content = await file.read()
# 更新：
        file_size = len(content)
# 更新：
        if file_size > 10 * 1024 * 1024:  # 10MB
# 更新：
            raise HTTPException(status_code=400, detail=f"E00039: 图片大小不能超过 10MB（当前{file_size // 1024 // 1024}MB）")
# 更新：
        
# 更新：
        # 生成OSS存储路径
# 更新：
        user_id = collection.f99_91_user_id
# 更新：
        file_extension = file.filename.split('.')[-1] if '.' in file.filename else 'jpg'
# 更新：
        
# 更新：
        # 清理特殊字符
# 更新：
        clean_username = re.sub(r'[^\w\u4e00-\u9fff\-]', '', username)
# 更新：
        clean_serial = re.sub(r'[^\w\u4e00-\u9fff\-]', '', prefix_serial)
# 更新：
        
# 更新：
        # 文件名格式：用户名-藏品编号-冠字号.jpg
# 更新：
        if clean_serial:
# 更新：
            filename = f"{clean_username}-{code}-{clean_serial}.{file_extension}"
# 更新：
        else:
# 更新：
            filename = f"{clean_username}-{code}.{file_extension}"
# 更新：
        
# 更新：
        # 生成OSS key
# 更新：
        oss_key, unique_name = get_oss_path("collections", user_id=user_id, filename=filename)
# 更新：
        
# 更新：
        # 上传到OSS
# 更新：
        image_url = upload_to_oss(content, oss_key)
# 更新：
        
# 更新：
        # 创建图片记录（保存OSS URL）
# 更新：
        image = CollectionImage(
# 更新：
            id=str(uuid.uuid4()),
# 更新：
            collection_id=collection_id,
# 更新：
            filename=unique_name,
# 更新：
            original_name=file.filename,
# 更新：
            path=image_url  # 保存OSS URL
# 更新：
        )
# 更新：
        
# 更新：
        db.add(image)
# 更新：
        db.commit()
# 更新：
        db.refresh(image)
# 更新：
        
# 更新：
        logger.info(f"图片上传成功：{image_url}, collection_id={collection_id}")
# 更新：
        
# 更新：
        return {
# 更新：
            "message": "上传成功",
# 更新：
            "image_id": image.id,
# 更新：
            "filename": unique_name,
# 更新：
            "url": image_url
# 更新：
        }
# 更新：
    except HTTPException:
# 更新：
        raise
# 更新：
    except Exception as e:
# 更新：
        logger.error(f"图片上传失败：{str(e)}")
# 更新：
        raise HTTPException(status_code=500, detail="上传失败")
# 更新：

# 更新：

# 更新：
@router.delete("/images/{image_id}")
# 更新：
async def delete_image(
# 更新：
    image_id: str,
# 更新：
    current_user: User = Depends(get_current_user),
# 更新：
    db: Session = Depends(get_db)
# 更新：
):
# 更新：
    """删除藏品图片"""
# 更新：
    try:
# 更新：
        # 查找图片记录
# 更新：
        image = db.query(CollectionImage).filter(
# 更新：
            CollectionImage.id == image_id
# 更新：
        ).first()
# 更新：
        
# 更新：
        if not image:
# 更新：
            raise HTTPException(status_code=404, detail="E00033: 图片不存在")
# 更新：
        
# 更新：
        # 检查权限
# 更新：
        collection = db.query(Collection).filter(
# 更新：
            Collection.f99_90_id == image.collection_id
# 更新：
        ).first()
# 更新：
        
# 更新：
        if collection and (current_user is None or current_user.role != "admin") and collection.f99_91_user_id != current_user.f99_90_id:
# 更新：
            raise HTTPException(status_code=403, detail="E00014: 无权删除此图片")
# 更新：
        
# 更新：
        # 删除OSS文件（如果path是OSS URL）
# 更新：
        if image.path and image.path.startswith("https://"):
# 更新：
            # 从OSS URL提取key
# 更新：
            try:
# 更新：
                oss_key = image.path.replace("https://jiachenlong-oss.oss-cn-hangzhou.aliyuncs.com/", "")
# 更新：
                delete_from_oss(oss_key)
# 更新：
            except Exception as e:
# 更新：
                logger.warning(f"OSS文件删除失败: {e}")
# 更新：
        elif image.path and os.path.exists(image.path):
# 更新：
            # 兼容旧的本地上传
# 更新：
            os.remove(image.path)
# 更新：
        
# 更新：
        # 删除数据库记录
# 更新：
        db.delete(image)
# 更新：
        db.commit()
# 更新：
        
# 更新：
        return {"message": "删除成功"}
# 更新：
    except HTTPException:
# 更新：
        raise
# 更新：
    except Exception as e:
# 更新：
        logger.error(f"图片删除失败：{str(e)}")
# 更新：
        raise HTTPException(status_code=500, detail="删除失败")
# 更新：
