jiachenlong/backend/app/routers/purchase.py

643 lines
20 KiB
Python
Raw Normal View History

2026-04-26 21:13:00 +08:00
# purchase - 认购群API路由
2026-04-29 20:48:41 +08:00
# Version: 0.0.5 (2026-04-29)
2026-04-26 21:13:00 +08:00
# 认购群、成员、藏品管理
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from pydantic import BaseModel
from typing import Optional, List
from datetime import datetime
from app.core.database import get_db
from app.core.auth import get_current_user
from app.models.models import User
from app.models.purchase import PurchaseGroup, PurchaseMember, PurchaseCollection
router = APIRouter(prefix="/api/purchase", tags=["认购"])
# ============ Schema ============
class PurchaseGroupCreate(BaseModel):
2026-04-29 20:48:41 +08:00
cycle_months: Optional[int] = None
start_date: Optional[str] = None
2026-04-26 21:13:00 +08:00
name: str
description: Optional[str] = None
class PurchaseGroupResponse(BaseModel):
2026-04-29 20:48:41 +08:00
cycle_months: Optional[int] = None
start_date: Optional[datetime] = None
2026-04-26 21:13:00 +08:00
id: str
2026-04-29 20:48:41 +08:00
group_code: str
2026-04-26 21:13:00 +08:00
name: str
description: Optional[str]
creator_id: str
creator_name: Optional[str]
status: str
total_members: int
2026-04-29 20:48:41 +08:00
total_pending: int
2026-04-26 21:13:00 +08:00
total_collections: int
2026-04-29 20:48:41 +08:00
total_confirmed: int
2026-04-26 21:13:00 +08:00
total_amount: float
avg_price: float
created_at: Optional[datetime]
class PurchaseCollectionCreate(BaseModel):
collection_name: str
2026-04-29 20:48:41 +08:00
number: str
grading: str
boss_name: str
price: float
source: str
paid: bool = False
class PurchaseCollectionUpdate(BaseModel):
collection_name: Optional[str] = None
2026-04-26 21:13:00 +08:00
number: Optional[str] = None
2026-04-29 20:48:41 +08:00
grading: Optional[str] = None
boss_name: Optional[str] = None
price: Optional[float] = None
source: Optional[str] = None
paid: Optional[bool] = None
status: Optional[str] = None
class PurchaseCollectionResponse(BaseModel):
id: str
group_id: str
user_id: str
user_name: Optional[str]
collection_name: Optional[str]
collection_code: Optional[str]
number: str
grading: str
boss_name: str
2026-04-26 21:13:00 +08:00
price: float
2026-04-29 20:48:41 +08:00
source: str
paid: bool
status: str
submit_date: Optional[datetime]
submitted_at: Optional[datetime]
class MemberResponse(BaseModel):
id: str
user_id: str
user_name: Optional[str]
user_avatar: Optional[str]
role: str
status: str
joined_at: Optional[datetime]
# ============ 工具函数 ============
def generate_group_code(db: Session) -> str:
"""生成群编码:年+序号,如 26001"""
year = datetime.now().year % 100 # 26
# 查找当前年份最大的编号
last = db.query(PurchaseGroup).filter(
PurchaseGroup.group_code.like(f"{year}%")
).order_by(PurchaseGroup.group_code.desc()).first()
if last and last.group_code:
try:
num = int(last.group_code) + 1
except:
num = 1
else:
num = 1
return f"{year}{num:03d}" # 26001
2026-04-26 21:13:00 +08:00
# ============ 认购群API ============
@router.get("/groups", response_model=List[PurchaseGroupResponse])
def get_groups(
status: str = Query("active", description="过滤状态"),
db: Session = Depends(get_db)
):
"""获取所有认购群列表"""
query = db.query(PurchaseGroup)
if status:
query = query.filter(PurchaseGroup.status == status)
groups = query.order_by(PurchaseGroup.created_at.desc()).all()
return groups
@router.get("/my-groups", response_model=List[PurchaseGroupResponse])
def get_my_groups(
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取我参与的认购群"""
member_groups = db.query(PurchaseMember).filter(
2026-04-29 20:48:41 +08:00
PurchaseMember.user_id == current_user.f99_90_id,
PurchaseMember.status == "approved"
2026-04-26 21:13:00 +08:00
).all()
group_ids = [m.group_id for m in member_groups]
groups = db.query(PurchaseGroup).filter(
PurchaseGroup.id.in_(group_ids)
).all()
return groups
2026-04-29 20:48:41 +08:00
@router.get("/groups/{group_id}/pending-members", response_model=List[MemberResponse])
def get_pending_members(
group_id: str,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取待审批成员列表"""
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
if group.creator_id != current_user.f99_90_id:
raise HTTPException(status_code=403, detail="只有群主可以查看")
members = db.query(PurchaseMember).filter(
PurchaseMember.group_id == group_id,
PurchaseMember.status == "pending"
).order_by(PurchaseMember.joined_at.asc()).all()
return members
@router.post("/groups/{group_id}/members/{member_id}/approve")
def approve_member(
group_id: str,
member_id: str,
action: str = Query(..., description="approve/reject"),
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""审批或拒绝成员"""
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
if group.creator_id != current_user.f99_90_id:
raise HTTPException(status_code=403, detail="只有群主可以审批")
member = db.query(PurchaseMember).filter(
PurchaseMember.id == member_id,
PurchaseMember.group_id == group_id
).first()
if not member:
raise HTTPException(status_code=404, detail="成员不存在")
if action == "approve":
member.status = "approved"
group.total_members += 1
group.total_pending -= 1
db.commit()
return {"message": "已批准加入"}
elif action == "reject":
member.status = "rejected"
group.total_pending -= 1
db.commit()
return {"message": "已拒绝加入"}
else:
raise HTTPException(status_code=400, detail="无效操作")
2026-04-26 21:13:00 +08:00
@router.post("/groups", response_model=PurchaseGroupResponse)
def create_group(
data: PurchaseGroupCreate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
2026-04-29 20:48:41 +08:00
"""创建认购群"""
2026-04-26 21:13:00 +08:00
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
2026-04-29 20:48:41 +08:00
# 普通用户最多创建3个认购群
if current_user.role not in ["admin", "superadmin"]:
existing_count = db.query(PurchaseGroup).filter(
PurchaseGroup.creator_id == current_user.f99_90_id
).count()
if existing_count >= 3:
raise HTTPException(status_code=400, detail="普通用户最多创建3个认购群")
group_code = generate_group_code(db)
# 处理日期
start_date = None
if data.start_date:
try:
start_date = datetime.fromisoformat(data.start_date.replace('Z', '+08:00'))
except:
start_date = datetime.now()
2026-04-26 21:13:00 +08:00
group = PurchaseGroup(
2026-04-29 20:48:41 +08:00
group_code=group_code,
2026-04-26 21:13:00 +08:00
name=data.name,
description=data.description,
creator_id=current_user.f99_90_id,
creator_name=current_user.f01_01_name,
2026-04-29 20:48:41 +08:00
status="active",
cycle_months=data.cycle_months,
start_date=start_date
2026-04-26 21:13:00 +08:00
)
db.add(group)
db.commit()
member = PurchaseMember(
group_id=group.id,
user_id=current_user.f99_90_id,
user_name=current_user.f01_01_name,
user_avatar=current_user.avatar,
2026-04-29 20:48:41 +08:00
role="creator",
status="approved"
2026-04-26 21:13:00 +08:00
)
db.add(member)
2026-04-29 20:48:41 +08:00
group.total_members = 1
2026-04-26 21:13:00 +08:00
db.commit()
db.refresh(group)
return group
@router.get("/groups/{group_id}", response_model=PurchaseGroupResponse)
2026-04-29 20:48:41 +08:00
def get_group(group_id: str, db: Session = Depends(get_db)):
2026-04-26 21:13:00 +08:00
"""获取认购群详情"""
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
return group
@router.post("/groups/{group_id}/join")
def join_group(
group_id: str,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
2026-04-29 20:48:41 +08:00
"""申请加入认购群"""
2026-04-26 21:13:00 +08:00
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
if group.status != "active":
raise HTTPException(status_code=400, detail="该认购群已结束")
existing = db.query(PurchaseMember).filter(
PurchaseMember.group_id == group_id,
PurchaseMember.user_id == current_user.f99_90_id
).first()
if existing:
2026-04-29 20:48:41 +08:00
if existing.status == "approved":
raise HTTPException(status_code=400, detail="您已加入该认购群")
elif existing.status == "pending":
raise HTTPException(status_code=400, detail="您已申请加入,等待审批")
elif existing.status == "rejected":
existing.status = "pending"
existing.joined_at = datetime.now()
group.total_pending += 1
db.commit()
return {"message": "已重新申请,等待审批"}
2026-04-26 21:13:00 +08:00
member = PurchaseMember(
group_id=group_id,
user_id=current_user.f99_90_id,
user_name=current_user.f01_01_name,
user_avatar=current_user.avatar,
2026-04-29 20:48:41 +08:00
role="member",
status="pending"
2026-04-26 21:13:00 +08:00
)
db.add(member)
2026-04-29 20:48:41 +08:00
group.total_pending += 1
2026-04-26 21:13:00 +08:00
db.commit()
2026-04-29 20:48:41 +08:00
return {"message": "申请成功,等待群主审批"}
2026-04-26 21:13:00 +08:00
2026-04-29 20:48:41 +08:00
@router.get("/groups/{group_id}/members", response_model=List[MemberResponse])
2026-04-26 21:13:00 +08:00
def get_group_members(
group_id: str,
2026-04-29 20:48:41 +08:00
current_user = Depends(get_current_user),
2026-04-26 21:13:00 +08:00
db: Session = Depends(get_db)
):
"""获取认购群成员列表"""
members = db.query(PurchaseMember).filter(
2026-04-29 20:48:41 +08:00
PurchaseMember.group_id == group_id,
PurchaseMember.status == "approved"
2026-04-26 21:13:00 +08:00
).order_by(PurchaseMember.joined_at.asc()).all()
return [
{
"id": m.id,
"user_id": m.user_id,
"user_name": m.user_name,
"user_avatar": m.user_avatar,
"role": m.role,
2026-04-29 20:48:41 +08:00
"status": m.status,
2026-04-26 21:13:00 +08:00
"joined_at": m.joined_at
}
for m in members
]
2026-04-29 20:48:41 +08:00
@router.get("/groups/{group_id}/collections", response_model=List[PurchaseCollectionResponse])
def get_group_collections(group_id: str, db: Session = Depends(get_db)):
2026-04-26 21:13:00 +08:00
"""获取认购群藏品列表"""
collections = db.query(PurchaseCollection).filter(
PurchaseCollection.group_id == group_id
).order_by(PurchaseCollection.submitted_at.desc()).all()
return [
{
"id": c.id,
2026-04-29 20:48:41 +08:00
"group_id": c.group_id,
2026-04-26 21:13:00 +08:00
"user_id": c.user_id,
"user_name": c.user_name,
"collection_name": c.collection_name,
"collection_code": c.collection_code,
"number": c.number,
2026-04-29 20:48:41 +08:00
"grading": c.grading,
"boss_name": c.boss_name,
2026-04-26 21:13:00 +08:00
"price": float(c.price),
2026-04-29 20:48:41 +08:00
"source": c.source,
"paid": c.paid,
2026-04-26 21:13:00 +08:00
"status": c.status,
2026-04-29 20:48:41 +08:00
"submit_date": c.submit_date,
2026-04-26 21:13:00 +08:00
"submitted_at": c.submitted_at
}
for c in collections
]
2026-04-29 20:48:41 +08:00
@router.post("/groups/{group_id}/collections", response_model=PurchaseCollectionResponse)
2026-04-26 21:13:00 +08:00
def add_collection(
group_id: str,
data: PurchaseCollectionCreate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""提交认购藏品"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
2026-04-29 20:48:41 +08:00
if not data.number:
raise HTTPException(status_code=400, detail="冠字号必填")
if not data.grading:
raise HTTPException(status_code=400, detail="评级必填")
if not data.boss_name:
raise HTTPException(status_code=400, detail="认购老板必填")
if not data.price:
raise HTTPException(status_code=400, detail="价格必填")
if not data.source:
raise HTTPException(status_code=400, detail="来源必填")
2026-04-26 21:13:00 +08:00
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
member = db.query(PurchaseMember).filter(
PurchaseMember.group_id == group_id,
2026-04-29 20:48:41 +08:00
PurchaseMember.user_id == current_user.f99_90_id,
PurchaseMember.status == "approved"
2026-04-26 21:13:00 +08:00
).first()
if not member:
2026-04-29 20:48:41 +08:00
raise HTTPException(status_code=400, detail="请先加入该认购群并通过审批")
max_code = db.query(PurchaseCollection).filter(
PurchaseCollection.group_id == group_id
).order_by(PurchaseCollection.collection_code.desc()).first()
if max_code and max_code.collection_code:
try:
next_num = int(max_code.collection_code) + 1
except:
next_num = 1
else:
next_num = 1
collection_code = f"{next_num:03d}"
2026-04-26 21:13:00 +08:00
collection = PurchaseCollection(
group_id=group_id,
user_id=current_user.f99_90_id,
user_name=current_user.f01_01_name,
collection_name=data.collection_name,
2026-04-29 20:48:41 +08:00
collection_code=collection_code,
2026-04-26 21:13:00 +08:00
number=data.number,
2026-04-29 20:48:41 +08:00
grading=data.grading,
boss_name=data.boss_name,
price=data.price,
source=data.source,
paid=data.paid,
status="pending"
2026-04-26 21:13:00 +08:00
)
db.add(collection)
group.total_collections += 1
2026-04-29 20:48:41 +08:00
from decimal import Decimal
group.total_amount += Decimal(str(data.price))
2026-04-26 21:13:00 +08:00
if group.total_collections > 0:
group.avg_price = group.total_amount / group.total_collections
db.commit()
db.refresh(collection)
return collection
2026-04-29 20:48:41 +08:00
@router.put("/groups/{group_id}/collections/{collection_id}", response_model=PurchaseCollectionResponse)
def update_collection(
group_id: str,
collection_id: str,
data: PurchaseCollectionUpdate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""编辑认购藏品 (仅群主可操作)"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
if group.creator_id != current_user.f99_90_id:
raise HTTPException(status_code=403, detail="只有群主可以编辑")
collection = db.query(PurchaseCollection).filter(
PurchaseCollection.id == collection_id,
PurchaseCollection.group_id == group_id
).first()
if not collection:
raise HTTPException(status_code=404, detail="藏品不存在")
old_price = float(collection.price)
# 更新字段
if data.collection_name is not None:
collection.collection_name = data.collection_name
if data.number is not None:
collection.number = data.number
if data.grading is not None:
collection.grading = data.grading
if data.boss_name is not None:
collection.boss_name = data.boss_name
if data.source is not None:
collection.source = data.source
if data.paid is not None:
collection.paid = data.paid
# 处理状态变化
new_status = data.status
if new_status and new_status != collection.status:
if new_status == "confirmed" and collection.status != "confirmed":
group.total_confirmed += 1
elif collection.status == "confirmed" and new_status != "confirmed":
group.total_confirmed -= 1
collection.status = new_status
# 处理价格变化
if data.price is not None and data.price != old_price:
from decimal import Decimal
group.total_amount = group.total_amount - Decimal(str(old_price)) + Decimal(str(data.price))
if group.total_collections > 0:
group.avg_price = group.total_amount / group.total_collections
db.commit()
db.refresh(collection)
return collection
2026-04-26 21:13:00 +08:00
@router.post("/groups/{group_id}/close")
def close_group(
group_id: str,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
2026-04-29 20:48:41 +08:00
"""结束认购群"""
2026-04-26 21:13:00 +08:00
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
if group.creator_id != current_user.f99_90_id:
raise HTTPException(status_code=403, detail="只有群主可以结束认购群")
group.status = "closed"
db.commit()
2026-04-29 20:48:41 +08:00
return {"message": "认购群已结束"}
@router.delete("/groups/{group_id}")
def delete_group(
group_id: str,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""删除认购群 (仅管理员可操作)"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
# 管理员权限检查
if current_user.role not in ["admin", "superadmin"]:
raise HTTPException(status_code=403, detail="只有管理员可以删除认购群")
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
# 删除相关成员和藏品
db.query(PurchaseMember).filter(PurchaseMember.group_id == group_id).delete()
db.query(PurchaseCollection).filter(PurchaseCollection.group_id == group_id).delete()
db.delete(group)
db.commit()
return {"message": "认购群已删除"}
class MemberUpdate(BaseModel):
wechat_name: Optional[str] = None
contact: Optional[str] = None
purchase_count: Optional[int] = None
paid_amount: Optional[float] = None
notes: Optional[str] = None
class GroupUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
cycle_months: Optional[int] = None
start_date: Optional[str] = None
# 更新成员信息
@router.put("/groups/{group_id}/members/{member_id}")
def update_member(
group_id: str,
member_id: str,
data: MemberUpdate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""更新成员信息 (成员自己或群主可编辑)"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
member = db.query(PurchaseMember).filter(
PurchaseMember.id == member_id,
PurchaseMember.group_id == group_id
).first()
if not member:
raise HTTPException(status_code=404, detail="成员不存在")
# 只有成员自己或群主可以修改
if member.user_id != current_user.f99_90_id and group.creator_id != current_user.f99_90_id:
raise HTTPException(status_code=403, detail="无权修改")
if data.wechat_name is not None:
member.wechat_name = data.wechat_name
if data.contact is not None:
member.contact = data.contact
if data.purchase_count is not None:
member.purchase_count = data.purchase_count
if data.paid_amount is not None:
from decimal import Decimal
member.paid_amount = Decimal(str(data.paid_amount))
if data.notes is not None:
member.notes = data.notes
db.commit()
db.refresh(member)
return {"message": "更新成功", "member_id": member.id}
# 更新群信息
@router.put("/groups/{group_id}", response_model=PurchaseGroupResponse)
def update_group(
group_id: str,
data: GroupUpdate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""更新群信息 (仅群主可编辑)"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
if not group:
raise HTTPException(status_code=404, detail="认购群不存在")
if group.creator_id != current_user.f99_90_id:
raise HTTPException(status_code=403, detail="只有群主可以修改")
if data.name is not None:
group.name = data.name
if data.description is not None:
group.description = data.description
if data.cycle_months is not None:
group.cycle_months = data.cycle_months
if data.start_date is not None:
try:
group.start_date = datetime.fromisoformat(data.start_date.replace('Z', '+08:00'))
except:
pass
db.commit()
db.refresh(group)
return group