from fastapi import APIRouter, Depends, Query, HTTPException from sqlalchemy.orm import Session from pydantic import BaseModel from typing import Optional from datetime import datetime, date from app.core.database import get_db from app.core.auth import get_current_user from app.models.deal_info import DealInfo router = APIRouter(prefix="/api/deal", tags=["成交行情"]) # ============ Schema ============ class DealInfoCreate(BaseModel): title: str content: Optional[str] = None deal_price: Optional[float] = None deal_date: Optional[str] = None # YYYY-MM-DD packaging: Optional[str] = None category: Optional[str] = None is_graded: Optional[bool] = False grading_company: Optional[str] = None grading_score: Optional[str] = None tail_number: Optional[str] = None size_type: Optional[str] = None version: Optional[str] = None platform: Optional[str] = None seller: Optional[str] = None buyer: Optional[str] = None class DealInfoUpdate(BaseModel): title: Optional[str] = None content: Optional[str] = None deal_price: Optional[float] = None deal_date: Optional[str] = None packaging: Optional[str] = None category: Optional[str] = None is_graded: Optional[bool] = None grading_company: Optional[str] = None grading_score: Optional[str] = None tail_number: Optional[str] = None size_type: Optional[str] = None version: Optional[str] = None platform: Optional[str] = None seller: Optional[str] = None buyer: Optional[str] = None status: Optional[str] = None class DealInfoResponse(BaseModel): id: str user_id: Optional[str] title: str content: Optional[str] deal_price: Optional[float] deal_date: Optional[date] deal_no: Optional[str] packaging: Optional[str] category: Optional[str] is_graded: Optional[bool] grading_company: Optional[str] grading_score: Optional[str] tail_number: Optional[str] size_type: Optional[str] version: Optional[str] platform: Optional[str] seller: Optional[str] buyer: Optional[str] status: str view_count: int contact_count: int created_at: Optional[datetime] updated_at: Optional[datetime] class Config: from_attributes = True # 生成行情编号 def generate_deal_no(db: Session): """生成行情编号,从A000001开始递增""" last = db.query(DealInfo).order_by(DealInfo.deal_no.desc()).first() if last and last.deal_no: # 例如 A000001 -> 2 -> A000002 num = int(last.deal_no[1:]) + 1 return f"A{num:06d}" return "A000001" # ============ API ============ @router.get("/list", response_model=list[DealInfoResponse]) def get_deal_list( status: str = Query("active"), deal_date: Optional[str] = Query(None), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=1000), user_only: bool = Query(False), # 是否只查看自己的 current_user: Optional = Depends(get_current_user), db: Session = Depends(get_db) ): """获取成交行情列表""" query = db.query(DealInfo).filter(DealInfo.status == status) # 我的行情:只查看自己的(管理员也只看自己的) if user_only and current_user: query = query.filter(DealInfo.user_id == current_user.f99_90_id) # 成交日期过滤 if deal_date: query = query.filter(DealInfo.deal_date == deal_date) # 排序:优先成交日期倒序,同日按编号倒序 query = query.order_by(DealInfo.deal_date.desc().nullslast(), DealInfo.deal_no.desc().nullslast()) # 分页 offset = (page - 1) * page_size items = query.offset(offset).limit(page_size).all() return items @router.get("/stats") def get_deal_stats( db: Session = Depends(get_db) ): """获取成交行情统计""" total = db.query(DealInfo).filter(DealInfo.status == "active").count() # 按日期统计 from sqlalchemy import func date_stats = db.query( DealInfo.deal_date, func.count(DealInfo.id).label('count') ).filter( DealInfo.status == "active", DealInfo.deal_date.isnot(None) ).group_by(DealInfo.deal_date).order_by(DealInfo.deal_date.desc()).limit(10).all() return { "total": total, "by_date": [{"date": str(d.deal_date), "count": d.count} for d in date_stats] } @router.post("", response_model=DealInfoResponse) def create_deal( data: DealInfoCreate, current_user = Depends(get_current_user), db: Session = Depends(get_db) ): """创建成交行情""" if not current_user: raise HTTPException(status_code=401, detail="请先登录") # 生成行情编号 deal_no = generate_deal_no(db) # 解析日期 deal_date = None if data.deal_date: try: deal_date = datetime.strptime(data.deal_date, "%Y-%m-%d").date() except: pass deal = DealInfo( user_id=current_user.f99_90_id if current_user else None, title=data.title, content=data.content, deal_price=data.deal_price, deal_date=deal_date, deal_no=deal_no, packaging=data.packaging, category=data.category, is_graded=data.is_graded or False, grading_company=data.grading_company, grading_score=data.grading_score, tail_number=data.tail_number, size_type=data.size_type, version=data.version, platform=data.platform, seller=data.seller, buyer=data.buyer, status="active" ) db.add(deal) db.commit() db.refresh(deal) return deal @router.get("/category-stats") def get_deal_category_stats( version: str = Query("龙钞", description="版本筛选:龙钞、马钞、蛇钞、其他"), db: Session = Depends(get_db) ): """获取成交行情分类汇总统计数据 - 后端计算优化版""" from collections import defaultdict # 定义版本前缀映射 version_prefix_map = {"龙钞": "J0", "马钞": "J1", "蛇钞": "J3"} packagings = ["标百", "标十", "单张"] category_map = {"通货": "带4号", "无4": "带7号", "永恒": "永恒号", "钻石": "钻石号"} # 构建查询 query = db.query(DealInfo).filter( DealInfo.status == "active", DealInfo.deal_price.isnot(None), DealInfo.deal_price > 0 ) if version != "其他" and version in version_prefix_map: query = query.filter(DealInfo.title.startswith(version_prefix_map[version])) deals = query.all() stats = defaultdict(lambda: defaultdict(lambda: {"count": 0, "total": 0})) for deal in deals: content = deal.content or "" packaging = deal.packaging if not packaging and "包装:" in content: packaging = content.split("包装:")[1].split("\n")[0].strip() category = deal.category if not category and "分类:" in content: category = content.split("分类:")[1].split("\n")[0].strip() if category in category_map: category = category_map[category] packaging = packaging or "单张" category = category or "带4号" stats[packaging][category]["count"] += 1 stats[packaging][category]["total"] += deal.deal_price result = [] category_order = ['带4号', '带7号', '永恒号', '天马号', '天马王', '金山号', '金山王', '钻石号', '朦胧号', '朦胧王', '金马号', '金马王', '倒置号', '圆圆号'] for cat in category_order: row = {"category": cat} has_data = False for pkg in packagings: data = stats[pkg][cat] if data["count"] > 0: row[pkg] = {"avg": round(data["total"] / data["count"]), "count": data["count"]} has_data = True else: row[pkg] = None if has_data: result.append(row) return {"version": version, "data": result} @router.get("/{deal_id}", response_model=DealInfoResponse) def get_deal( deal_id: str, current_user = Depends(get_current_user), db: Session = Depends(get_db) ): """获取成交行情详情""" deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first() if not deal: raise HTTPException(status_code=404, detail="成交行情不存在") # 增加浏览数 deal.view_count += 1 db.commit() return deal @router.put("/{deal_id}", response_model=DealInfoResponse) def update_deal( deal_id: str, data: DealInfoUpdate, current_user = Depends(get_current_user), db: Session = Depends(get_db) ): """更新成交行情""" if not current_user: raise HTTPException(status_code=401, detail="请先登录") deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first() if not deal: raise HTTPException(status_code=404, detail="成交行情不存在") # 处理日期 if data.deal_date: try: data.deal_date = datetime.strptime(data.deal_date, "%Y-%m-%d").date() except: data.deal_date = None for key, value in data.model_dump(exclude_unset=True).items(): setattr(deal, key, value) db.commit() db.refresh(deal) return deal @router.delete("/{deal_id}") def delete_deal( deal_id: str, current_user = Depends(get_current_user), db: Session = Depends(get_db) ): """删除成交行情""" if not current_user: raise HTTPException(status_code=401, detail="请先登录") deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first() if not deal: raise HTTPException(status_code=404, detail="成交行情不存在") deal.status = "deleted" db.commit() return {"message": "删除成功"}