v0.1.0 - 认购功能完善版
This commit is contained in:
parent
ee728a5f4e
commit
1fef5177f3
|
|
@ -1,8 +1,8 @@
|
|||
# purchase - 认购群模型
|
||||
# Version: 0.0.1 (2026-04-24)
|
||||
# Version: 0.0.2 (2026-04-29)
|
||||
# 认购群、成员、藏品管理
|
||||
|
||||
from sqlalchemy import Column, String, Text, Integer, DECIMAL, DateTime, ForeignKey
|
||||
from sqlalchemy import Column, String, Text, Integer, DECIMAL, DateTime, ForeignKey, Boolean
|
||||
from sqlalchemy.sql import func
|
||||
from app.core.database import Base
|
||||
import uuid
|
||||
|
|
@ -15,14 +15,19 @@ class PurchaseGroup(Base):
|
|||
__tablename__ = "purchase_group"
|
||||
|
||||
id = Column(String(36), primary_key=True, default=generate_uuid)
|
||||
group_code = Column(String(20), nullable=False, unique=True) # 年+序号,如 26001
|
||||
name = Column(String(100), nullable=False) # 认购群名称
|
||||
description = Column(Text, nullable=True) # 描述
|
||||
creator_id = Column(String(36), nullable=False) # 创建者ID
|
||||
creator_name = Column(String(50), nullable=True) # 创建者名字
|
||||
status = Column(String(20), default="active") # active/closed
|
||||
total_members = Column(Integer, default=0) # 总参与人数
|
||||
total_pending = Column(Integer, default=0) # 待审批人数
|
||||
total_collections = Column(Integer, default=0) # 总藏品数
|
||||
total_confirmed = Column(Integer, default=0) # 已确认数
|
||||
total_amount = Column(DECIMAL(12,2), default=0) # 总金额
|
||||
cycle_months = Column(Integer, nullable=True) # 认购周期(月)
|
||||
start_date = Column(DateTime(timezone=True), nullable=True) # 开始日期
|
||||
avg_price = Column(DECIMAL(10,2), default=0) # 均价
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
|
@ -37,6 +42,13 @@ class PurchaseMember(Base):
|
|||
user_name = Column(String(50), nullable=True)
|
||||
user_avatar = Column(String(255), nullable=True)
|
||||
role = Column(String(20), default="member") # creator/admin/member
|
||||
status = Column(String(20), default="pending") # pending/approved/rejected
|
||||
# 成员扩展信息
|
||||
wechat_name = Column(String(50), nullable=True) # 微信名
|
||||
contact = Column(String(50), nullable=True) # 联系方式
|
||||
purchase_count = Column(Integer, default=0) # 认购数量
|
||||
paid_amount = Column(DECIMAL(12,2), default=0) # 已付认购款
|
||||
notes = Column(Text, nullable=True) # 备注
|
||||
joined_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
class PurchaseCollection(Base):
|
||||
|
|
@ -48,8 +60,13 @@ class PurchaseCollection(Base):
|
|||
user_id = Column(String(36), nullable=False)
|
||||
user_name = Column(String(50), nullable=True)
|
||||
collection_name = Column(String(100), nullable=True) # 藏品名称
|
||||
collection_code = Column(String(50), nullable=True) # 藏品编号
|
||||
number = Column(String(50), nullable=True) # 冠字号
|
||||
price = Column(DECIMAL(10,2), nullable=False) # 认购价格
|
||||
collection_code = Column(String(50), nullable=True) # 藏品编号 (自动生成001开始)
|
||||
number = Column(String(50), nullable=False) # 冠字号 (必填)
|
||||
grading = Column(String(50), nullable=False) # 评级 (必填)
|
||||
boss_name = Column(String(50), nullable=False) # 认购老板 (必填,从成员中选择)
|
||||
price = Column(DECIMAL(10,2), nullable=False) # 认购价格 (必填)
|
||||
source = Column(String(20), nullable=False) # 来源: yichen/live/friend/other (必填)
|
||||
paid = Column(Boolean, default=False) # 认购款是否结清
|
||||
submit_date = Column(DateTime(timezone=True), server_default=func.now()) # 提交日期
|
||||
status = Column(String(20), default="pending") # pending/confirmed
|
||||
submitted_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
# purchase - 认购群API路由
|
||||
# Version: 0.0.1 (2026-04-24)
|
||||
# Version: 0.0.5 (2026-04-29)
|
||||
# 认购群、成员、藏品管理
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
|
|
@ -16,27 +16,92 @@ router = APIRouter(prefix="/api/purchase", tags=["认购"])
|
|||
|
||||
# ============ Schema ============
|
||||
class PurchaseGroupCreate(BaseModel):
|
||||
cycle_months: Optional[int] = None
|
||||
start_date: Optional[str] = None
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
|
||||
class PurchaseGroupResponse(BaseModel):
|
||||
cycle_months: Optional[int] = None
|
||||
start_date: Optional[datetime] = None
|
||||
id: str
|
||||
group_code: str
|
||||
name: str
|
||||
description: Optional[str]
|
||||
creator_id: str
|
||||
creator_name: Optional[str]
|
||||
status: str
|
||||
total_members: int
|
||||
total_pending: int
|
||||
total_collections: int
|
||||
total_confirmed: int
|
||||
total_amount: float
|
||||
avg_price: float
|
||||
created_at: Optional[datetime]
|
||||
|
||||
class PurchaseCollectionCreate(BaseModel):
|
||||
collection_name: str
|
||||
collection_code: Optional[str] = None
|
||||
number: Optional[str] = None
|
||||
number: str
|
||||
grading: str
|
||||
boss_name: str
|
||||
price: float
|
||||
source: str
|
||||
paid: bool = False
|
||||
|
||||
class PurchaseCollectionUpdate(BaseModel):
|
||||
collection_name: Optional[str] = None
|
||||
number: Optional[str] = None
|
||||
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
|
||||
price: float
|
||||
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
|
||||
|
||||
# ============ 认购群API ============
|
||||
|
||||
|
|
@ -59,7 +124,8 @@ def get_my_groups(
|
|||
):
|
||||
"""获取我参与的认购群"""
|
||||
member_groups = db.query(PurchaseMember).filter(
|
||||
PurchaseMember.user_id == current_user.f99_90_id
|
||||
PurchaseMember.user_id == current_user.f99_90_id,
|
||||
PurchaseMember.status == "approved"
|
||||
).all()
|
||||
|
||||
group_ids = [m.group_id for m in member_groups]
|
||||
|
|
@ -69,45 +135,123 @@ def get_my_groups(
|
|||
|
||||
return groups
|
||||
|
||||
@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="无效操作")
|
||||
|
||||
@router.post("/groups", response_model=PurchaseGroupResponse)
|
||||
def create_group(
|
||||
data: PurchaseGroupCreate,
|
||||
current_user = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""创建认购群(创建者即为群主)"""
|
||||
"""创建认购群"""
|
||||
if not current_user:
|
||||
raise HTTPException(status_code=401, detail="请先登录")
|
||||
|
||||
# 普通用户最多创建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()
|
||||
|
||||
group = PurchaseGroup(
|
||||
group_code=group_code,
|
||||
name=data.name,
|
||||
description=data.description,
|
||||
creator_id=current_user.f99_90_id,
|
||||
creator_name=current_user.f01_01_name,
|
||||
status="active"
|
||||
status="active",
|
||||
cycle_months=data.cycle_months,
|
||||
start_date=start_date
|
||||
)
|
||||
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,
|
||||
role="creator"
|
||||
role="creator",
|
||||
status="approved"
|
||||
)
|
||||
db.add(member)
|
||||
group.total_members = 1
|
||||
db.commit()
|
||||
db.refresh(group)
|
||||
|
||||
return group
|
||||
|
||||
@router.get("/groups/{group_id}", response_model=PurchaseGroupResponse)
|
||||
def get_group(
|
||||
group_id: str,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
def get_group(group_id: str, db: Session = Depends(get_db)):
|
||||
"""获取认购群详情"""
|
||||
group = db.query(PurchaseGroup).filter(PurchaseGroup.id == group_id).first()
|
||||
if not group:
|
||||
|
|
@ -120,11 +264,10 @@ def join_group(
|
|||
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="认购群不存在")
|
||||
|
|
@ -132,39 +275,47 @@ def join_group(
|
|||
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:
|
||||
raise HTTPException(status_code=400, detail="您已加入该认购群")
|
||||
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": "已重新申请,等待审批"}
|
||||
|
||||
# 加入
|
||||
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,
|
||||
role="member"
|
||||
role="member",
|
||||
status="pending"
|
||||
)
|
||||
db.add(member)
|
||||
|
||||
# 更新群成员数
|
||||
group.total_members += 1
|
||||
group.total_pending += 1
|
||||
db.commit()
|
||||
|
||||
return {"message": "加入成功"}
|
||||
return {"message": "申请成功,等待群主审批"}
|
||||
|
||||
@router.get("/groups/{group_id}/members")
|
||||
@router.get("/groups/{group_id}/members", response_model=List[MemberResponse])
|
||||
def get_group_members(
|
||||
group_id: str,
|
||||
current_user = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取认购群成员列表"""
|
||||
members = db.query(PurchaseMember).filter(
|
||||
PurchaseMember.group_id == group_id
|
||||
PurchaseMember.group_id == group_id,
|
||||
PurchaseMember.status == "approved"
|
||||
).order_by(PurchaseMember.joined_at.asc()).all()
|
||||
|
||||
return [
|
||||
|
|
@ -174,16 +325,14 @@ def get_group_members(
|
|||
"user_name": m.user_name,
|
||||
"user_avatar": m.user_avatar,
|
||||
"role": m.role,
|
||||
"status": m.status,
|
||||
"joined_at": m.joined_at
|
||||
}
|
||||
for m in members
|
||||
]
|
||||
|
||||
@router.get("/groups/{group_id}/collections")
|
||||
def get_group_collections(
|
||||
group_id: str,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
@router.get("/groups/{group_id}/collections", response_model=List[PurchaseCollectionResponse])
|
||||
def get_group_collections(group_id: str, db: Session = Depends(get_db)):
|
||||
"""获取认购群藏品列表"""
|
||||
collections = db.query(PurchaseCollection).filter(
|
||||
PurchaseCollection.group_id == group_id
|
||||
|
|
@ -192,19 +341,25 @@ def get_group_collections(
|
|||
return [
|
||||
{
|
||||
"id": c.id,
|
||||
"group_id": c.group_id,
|
||||
"user_id": c.user_id,
|
||||
"user_name": c.user_name,
|
||||
"collection_name": c.collection_name,
|
||||
"collection_code": c.collection_code,
|
||||
"number": c.number,
|
||||
"grading": c.grading,
|
||||
"boss_name": c.boss_name,
|
||||
"price": float(c.price),
|
||||
"source": c.source,
|
||||
"paid": c.paid,
|
||||
"status": c.status,
|
||||
"submit_date": c.submit_date,
|
||||
"submitted_at": c.submitted_at
|
||||
}
|
||||
for c in collections
|
||||
]
|
||||
|
||||
@router.post("/groups/{group_id}/collections")
|
||||
@router.post("/groups/{group_id}/collections", response_model=PurchaseCollectionResponse)
|
||||
def add_collection(
|
||||
group_id: str,
|
||||
data: PurchaseCollectionCreate,
|
||||
|
|
@ -215,35 +370,63 @@ def add_collection(
|
|||
if not current_user:
|
||||
raise HTTPException(status_code=401, detail="请先登录")
|
||||
|
||||
# 检查群是否存在
|
||||
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="来源必填")
|
||||
|
||||
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,
|
||||
PurchaseMember.user_id == current_user.f99_90_id
|
||||
PurchaseMember.user_id == current_user.f99_90_id,
|
||||
PurchaseMember.status == "approved"
|
||||
).first()
|
||||
|
||||
if not member:
|
||||
raise HTTPException(status_code=400, detail="请先加入该认购群")
|
||||
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}"
|
||||
|
||||
# 添加藏品
|
||||
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,
|
||||
collection_code=data.collection_code,
|
||||
collection_code=collection_code,
|
||||
number=data.number,
|
||||
price=data.price
|
||||
grading=data.grading,
|
||||
boss_name=data.boss_name,
|
||||
price=data.price,
|
||||
source=data.source,
|
||||
paid=data.paid,
|
||||
status="pending"
|
||||
)
|
||||
db.add(collection)
|
||||
|
||||
# 更新群统计
|
||||
group.total_collections += 1
|
||||
group.total_amount += float(data.price)
|
||||
from decimal import Decimal
|
||||
group.total_amount += Decimal(str(data.price))
|
||||
if group.total_collections > 0:
|
||||
group.avg_price = group.total_amount / group.total_collections
|
||||
|
||||
|
|
@ -252,13 +435,77 @@ def add_collection(
|
|||
|
||||
return collection
|
||||
|
||||
@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
|
||||
|
||||
@router.post("/groups/{group_id}/close")
|
||||
def close_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="请先登录")
|
||||
|
||||
|
|
@ -266,7 +513,6 @@ def close_group(
|
|||
if not group:
|
||||
raise HTTPException(status_code=404, detail="认购群不存在")
|
||||
|
||||
# 检查是否为群主
|
||||
if group.creator_id != current_user.f99_90_id:
|
||||
raise HTTPException(status_code=403, detail="只有群主可以结束认购群")
|
||||
|
||||
|
|
@ -274,3 +520,123 @@ def close_group(
|
|||
db.commit()
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,28 @@
|
|||
# 版本更新记录
|
||||
|
||||
## v0.0.12 (2026-04-29)
|
||||
|
||||
### 前端更新
|
||||
- **Purchase.jsx**
|
||||
- 完善:入群申请审批流程 (待审批成员列表)
|
||||
- 完善:新增认购藏品字段
|
||||
- 编号:自动生成001开始
|
||||
- 冠字号:必填
|
||||
- 评级:必填 (下拉选择)
|
||||
- 认购老板:必填 (从成员中选择)
|
||||
- 价格:必填
|
||||
- 来源:必填 (一尘/直播/群友/其他)
|
||||
- 认购款结清:checkbox
|
||||
- 优化:表单布局
|
||||
|
||||
### 后端更新
|
||||
- **purchase.py**
|
||||
- 新增:入群审批接口 /approve
|
||||
- 新增:待审批成员列表 /pending-members
|
||||
- 新增:认购藏品新字段 (grading, boss_name, source, paid, submit_date)
|
||||
|
||||
---
|
||||
|
||||
## v0.0.7 (2026-04-24)
|
||||
|
||||
### 代码清理
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.12",
|
||||
"updated": "2026-04-24",
|
||||
"modules": {
|
||||
"frontend": {
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.12",
|
||||
"pages": {
|
||||
"Add": "0.0.1",
|
||||
"Admin": "0.0.1",
|
||||
"Detail": "0.0.1",
|
||||
"Edit": "0.0.1",
|
||||
"Home": "0.0.4",
|
||||
"Home": "0.0.7",
|
||||
"List": "0.0.1",
|
||||
"Login": "0.0.1",
|
||||
"News": "0.0.8",
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
}
|
||||
},
|
||||
"backend": {
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.12",
|
||||
"routers": {
|
||||
"auth": "0.0.1",
|
||||
"collections": "0.0.1",
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ export default function Home() {
|
|||
<div style={{ color: '#f59e0b', fontSize: '18px', fontWeight: '600', whiteSpace: 'nowrap' }}>发布寻号</div>
|
||||
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '10px', marginTop: '2px' }}>发布寻配需求</div>
|
||||
</div>
|
||||
<div onClick={() => window.location.hash = '#/add?mode=manual'} style={{
|
||||
<div onClick={() => window.location.hash = '#/purchase'} style={{
|
||||
background: 'linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%)',
|
||||
borderRadius: '12px',
|
||||
padding: '12px 16px',
|
||||
|
|
@ -238,8 +238,8 @@ export default function Home() {
|
|||
justifyContent: 'center',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
<div style={{ color: '#3b82f6', fontSize: '18px', fontWeight: '600', whiteSpace: 'nowrap' }}>发布藏品</div>
|
||||
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '10px', marginTop: '2px' }}>手动发布</div>
|
||||
<div style={{ color: '#ec4899', fontSize: '18px', fontWeight: '600', whiteSpace: 'nowrap' }}>我的认购</div>
|
||||
<div style={{ color: 'rgba(255,255,255,0.5)', fontSize: '10px', marginTop: '2px' }}>认购群入口</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -407,14 +407,16 @@ export default function Home() {
|
|||
{(isAdmin ? [
|
||||
{ icon: '🏠', label: '首页', hash: '#/', idx: 0 },
|
||||
{ icon: '📚', label: '藏品', hash: '#/list', idx: 1 },
|
||||
{ icon: '➕', label: '录入', hash: '#/ocr', idx: 2 },
|
||||
{ icon: '📊', label: '统计', hash: '#/stats', idx: 3 },
|
||||
{ icon: '⚙️', label: '管理', hash: '#/admin', idx: 4 }
|
||||
{ icon: '🎯', label: '认购', hash: '#/purchase', idx: 2 },
|
||||
{ icon: '➕', label: '录入', hash: '#/ocr', idx: 3 },
|
||||
{ icon: '📊', label: '统计', hash: '#/stats', idx: 4 },
|
||||
{ icon: '⚙️', label: '管理', hash: '#/admin', idx: 5 }
|
||||
] : [
|
||||
{ icon: '🏠', label: '首页', hash: '#/', idx: 0 },
|
||||
{ icon: '📚', label: '藏品', hash: '#/list', idx: 1 },
|
||||
{ icon: '➕', label: '录入', hash: '#/ocr', idx: 2 },
|
||||
{ icon: '📊', label: '统计', hash: '#/stats', idx: 3 }
|
||||
{ icon: '🎯', label: '认购', hash: '#/purchase', idx: 2 },
|
||||
{ icon: '➕', label: '录入', hash: '#/ocr', idx: 3 },
|
||||
{ icon: '📊', label: '统计', hash: '#/stats', idx: 4 }
|
||||
]).map((item) => (
|
||||
<div key={item.idx} onClick={() => window.location.hash = item.hash} style={{
|
||||
display: 'flex',
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ function Purchase() {
|
|||
const [selectedGroup, setSelectedGroup] = useState(null)
|
||||
const [groupMembers, setGroupMembers] = useState([])
|
||||
const [groupCollections, setGroupCollections] = useState([])
|
||||
const [showCollectionForm, setShowCollectionForm] = useState(false)
|
||||
const [newGroup, setNewGroup] = useState({ name: '', description: '' })
|
||||
const [newCollection, setNewCollection] = useState({ collection_name: '', collection_code: '', number: '', price: '' })
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
|
@ -247,7 +248,7 @@ function Purchase() {
|
|||
if (selectedGroup) {
|
||||
return (
|
||||
<div style={{
|
||||
minHeight: '100vh',
|
||||
minHeight: '100vh', overflowY: 'auto', WebkitOverflowScrolling: 'touch',
|
||||
background: '#0f172a',
|
||||
color: '#fff',
|
||||
padding: '16px'
|
||||
|
|
@ -369,15 +370,16 @@ function Purchase() {
|
|||
</div>
|
||||
))}
|
||||
{groupCollections.length === 0 && <div style={{ color: 'rgba(255,255,255,0.3)', fontSize: '13px', textAlign: 'center', padding: '20px' }}>暂无认购藏品</div>}
|
||||
{selectedGroup && user && selectedGroup.creator_id === user.id && selectedGroup.status === 'active' && (
|
||||
<button onClick={() => setShowCollectionForm(!showCollectionForm)} style={{ marginTop: '12px', width: '100%', background: showCollectionForm ? '#ef4444' : '#3b82f6', color: '#fff', border: 'none', borderRadius: '8px', padding: '12px', fontSize: '14px', fontWeight: 'bold', cursor: 'pointer' }}>{showCollectionForm ? '收起' : '录入认购藏品'}</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 提交认购藏品表单 */}
|
||||
{selectedGroup.status === 'active' && (
|
||||
{/* 提交认购藏品表单 - 仅群主可提交 */}
|
||||
{selectedGroup && user && selectedGroup.creator_id === user.id && selectedGroup.status === 'active' && showCollectionForm && (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
bottom: '20px',
|
||||
left: '16px',
|
||||
right: '16px',
|
||||
marginTop: '16px',
|
||||
marginBottom: '80px',
|
||||
background: '#1e293b',
|
||||
borderRadius: '12px',
|
||||
padding: '16px',
|
||||
|
|
@ -459,13 +461,20 @@ function Purchase() {
|
|||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 非群主提示 */}
|
||||
{selectedGroup && user && !(selectedGroup.creator_id === user.id) && selectedGroup.status === 'active' && (
|
||||
<div style={{ marginTop: '16px', marginBottom: '80px', background: '#1e293b', borderRadius: '12px', padding: '16px', textAlign: 'center', color: 'rgba(255,255,255,0.5)' }}>
|
||||
仅群主可提交认购藏品
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
minHeight: '100vh',
|
||||
minHeight: '100vh', overflowY: 'auto', WebkitOverflowScrolling: 'touch',
|
||||
background: '#0f172a',
|
||||
color: '#fff',
|
||||
padding: '16px'
|
||||
|
|
|
|||
Loading…
Reference in New Issue