deliveryman-api/app/api/endpoints/merchant.py
2025-04-02 09:29:19 +08:00

394 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from typing import List, Optional, Dict, Any
from sqlalchemy import text
from app.models.merchant import (
MerchantDB,
MerchantCreate,
MerchantUpdate,
MerchantInfo)
from app.models.merchant_category import MerchantCategoryDB
from app.models.database import get_db
from app.api.deps import get_admin_user, get_current_user
from app.models.user import UserDB
from app.core.response import success_response, error_response, ResponseModel
from app.models.merchant_pay_order import MerchantPayOrderDB
from sqlalchemy.sql import func, desc
from app.models.merchant_product import MerchantProductDB, ProductStatus
from app.models.merchant import MerchantStatus, MerchantApply
from app.models.merchant_auth import MerchantAuthDB, MerchantAuthInfo
router = APIRouter()
@router.get("/apply", response_model=ResponseModel)
async def get_merchant_apply(
db: Session = Depends(get_db),
user: UserDB = Depends(get_current_user)
):
"""获取商家申请信息"""
merchant = db.query(MerchantDB).filter(MerchantDB.user_id == user.userid).first()
if not merchant:
return error_response(code=404, message="商家申请信息不存在")
auth = db.query(MerchantAuthDB).filter(MerchantAuthDB.merchant_id == merchant.id).first()
return success_response(data={
"merchant": MerchantInfo.model_validate(merchant),
"auth": MerchantAuthInfo.model_validate(auth)
})
@router.put("/apply", response_model=ResponseModel)
async def update_merchant_apply(
apply: MerchantApply,
db: Session = Depends(get_db),
user: UserDB = Depends(get_current_user)
):
"""更新商家申请信息"""
merchant = db.query(MerchantDB).filter(MerchantDB.user_id == user.userid).first()
if not merchant:
return error_response(code=404, message="商家不存在")
merchant.name = apply.name
merchant.business_hours = apply.business_hours
merchant.address = apply.address
merchant.longitude = apply.longitude
merchant.latitude = apply.latitude
merchant.phone = apply.phone
merchant.brand_image_url = apply.brand_image_url
merchant.category_id = apply.category_id
merchant.pay_share_rate = apply.pay_share_rate
auth = db.query(MerchantAuthDB).filter(MerchantAuthDB.merchant_id == merchant.id).first()
if auth:
auth.license_image_url = apply.license_image_url
auth.id_front_url = apply.id_front_url
auth.id_back_url = apply.id_back_url
db.commit()
db.refresh(merchant)
return success_response(data=MerchantInfo.model_validate(merchant))
@router.post("/apply", response_model=ResponseModel)
async def apply_merchant(
apply: MerchantApply,
db: Session = Depends(get_db),
user: UserDB = Depends(get_current_user)
):
"""申请成为商家"""
try:
# 创建商家
merchant_apply = MerchantDB(
user_id=user.userid,
name=apply.name,
business_hours=apply.business_hours,
address=apply.address,
longitude=apply.longitude,
latitude=apply.latitude,
phone=apply.phone,
brand_image_url=apply.brand_image_url,
category_id=apply.category_id,
pay_share_rate=apply.pay_share_rate,
status=MerchantStatus.PENDING
)
db.add(merchant_apply)
db.refresh(merchant_apply)
# 创建商家认证信息
auth = db.query(MerchantAuthDB).filter(MerchantAuthDB.merchant_id == merchant_apply.id).first()
if not auth:
merchant_auth = MerchantAuthDB(
merchant_id=merchant_apply.id,
license_image_url=apply.license_image_url,
id_front_url=apply.id_front_url,
id_back_url=apply.id_back_url
)
else:
auth.license_image_url = apply.license_image_url
auth.id_front_url = apply.id_front_url
auth.id_back_url = apply.id_back_url
db.add(merchant_auth)
db.commit()
return success_response()
except Exception as e:
db.rollback()
return error_response(code=500, message=f"申请失败: {str(e)}")
@router.put("/{merchant_id}/approve", response_model=ResponseModel)
async def approve_merchant(
merchant_id: int,
db: Session = Depends(get_db),
admin: UserDB = Depends(get_admin_user)
):
"""审核通过商家申请(管理员)"""
merchant = db.query(MerchantDB).filter(MerchantDB.id == merchant_id).first()
if not merchant:
return error_response(code=404, message="商家不存在")
merchant.status = MerchantStatus.OFFLINE
db.commit()
db.refresh(merchant)
return success_response(data=merchant)
@router.put("/{merchant_id}/offline", response_model=ResponseModel)
async def offline_merchant(
merchant_id: int,
db: Session = Depends(get_db),
admin: UserDB = Depends(get_admin_user)
):
"""下线商家(管理员)"""
merchant = db.query(MerchantDB).filter(MerchantDB.id == merchant_id).first()
if not merchant:
return error_response(code=404, message="商家不存在")
merchant.status = MerchantStatus.OFFLINE
db.commit()
db.refresh(merchant)
return success_response(data=merchant)
@router.get("/{merchant_id}/audit", response_model=ResponseModel)
async def get_merchant_audit(
merchant_id: int,
db: Session = Depends(get_db)
):
"""获取商家审核信息"""
merchant = db.query(MerchantDB).filter(MerchantDB.id == merchant_id).first()
if not merchant:
return error_response(code=404, message="商家不存在")
auth = db.query(MerchantAuthDB).filter(MerchantAuthDB.merchant_id == merchant_id).first()
if not auth:
return error_response(code=404, message="商家认证信息不存在")
result = {
"merchant": MerchantInfo.model_validate(merchant),
"auth": MerchantAuthInfo.model_validate(auth)
}
return success_response(data=result)
@router.post("", response_model=ResponseModel)
async def create_merchant(
merchant: MerchantCreate,
db: Session = Depends(get_db),
admin: UserDB = Depends(get_admin_user)
):
"""创建商家(管理员)"""
# 创建商家基本信息
merchant_data = merchant.model_dump()
db_merchant = MerchantDB(**merchant_data)
db.add(db_merchant)
try:
db.commit()
db.refresh(db_merchant)
return success_response(data=MerchantInfo.model_validate(db_merchant))
except Exception as e:
db.rollback()
return error_response(code=500, message=f"创建失败: {str(e)}")
@router.put("/{merchant_id}", response_model=ResponseModel)
async def update_merchant(
merchant_id: int,
merchant: MerchantUpdate,
db: Session = Depends(get_db),
admin: UserDB = Depends(get_admin_user)
):
"""更新商家信息(管理员)"""
db_merchant = db.query(MerchantDB).filter(
MerchantDB.id == merchant_id
).first()
if not db_merchant:
return error_response(code=404, message="商家不存在")
# 如果要更新用户ID先验证用户是否存在
if merchant.user_id is not None:
user_exists = db.query(UserDB).filter(
UserDB.userid == merchant.user_id
).first()
if not user_exists:
return error_response(code=400, message="指定的用户不存在")
# 只更新传入的非空字段
update_data = merchant.model_dump(exclude_unset=True)
for key, value in update_data.items():
if value is not None: # 只更新非空值
setattr(db_merchant, key, value)
try:
db.commit()
db.refresh(db_merchant)
# 获取更新后的完整信息(包括用户信息)
updated_merchant = db.query(
MerchantDB,
UserDB.phone.label('user_phone'),
UserDB.nickname.label('user_nickname')
).join(
UserDB,
MerchantDB.user_id == UserDB.userid
).filter(
MerchantDB.id == merchant_id
).first()
# 构建返回数据
merchant_info = MerchantInfo.model_validate(updated_merchant.MerchantDB)
merchant_data = merchant_info.model_dump()
merchant_data.update({
'user_phone': updated_merchant.user_phone,
'user_nickname': updated_merchant.user_nickname
})
return success_response(data=merchant_data)
except Exception as e:
db.rollback()
return error_response(code=500, message=f"更新失败: {str(e)}")
@router.get("/{merchant_id}", response_model=ResponseModel)
async def get_merchant(
merchant_id: int,
longitude: Optional[float] = None,
latitude: Optional[float] = None,
db: Session = Depends(get_db)
):
"""获取商家详情"""
# 构建基础查询
query = db.query(
MerchantDB,
UserDB.phone.label('user_phone'),
UserDB.nickname.label('user_nickname')
)
# 如果提供了经纬度,添加距离计算
if longitude is not None and latitude is not None:
query = query.add_columns(
text("ST_Distance_Sphere(point(merchants.longitude, merchants.latitude), "
"point(:lon, :lat)) as distance").params(lon=longitude, lat=latitude)
)
else:
query = query.add_columns(text("NULL as distance"))
# 完成查询
merchant = query.join(
UserDB,
MerchantDB.user_id == UserDB.userid
).filter(
MerchantDB.id == merchant_id
).first()
if not merchant:
return error_response(code=404, message="商家不存在")
# 构建返回数据
merchant_info = MerchantInfo.model_validate(merchant[0])
merchant_data = merchant_info.model_dump()
merchant_data.update({
'user_phone': merchant[1],
'user_nickname': merchant[2],
'distance': round(merchant[3]) if merchant[3] else None
})
return success_response(data=merchant_data)
@router.get("", response_model=ResponseModel)
async def list_merchants(
longitude: Optional[float] = None,
latitude: Optional[float] = None,
category_id: Optional[int] = None,
status: Optional[MerchantStatus] = None,
skip: int = 0,
limit: int = 20,
db: Session = Depends(get_db)
):
"""获取商家列表,支持经纬度排序和分类过滤"""
query = db.query(
MerchantDB,
MerchantCategoryDB.name.label('category_name'),
UserDB.phone.label('user_phone'),
UserDB.nickname.label('user_nickname')
).outerjoin(
MerchantCategoryDB,
MerchantDB.category_id == MerchantCategoryDB.id
).join(
UserDB,
MerchantDB.user_id == UserDB.userid
)
if status is not None:
query = query.filter(MerchantDB.status == status)
# 添加分类过滤
if category_id is not None:
query = query.filter(MerchantDB.category_id == category_id)
# 根据经纬度排序
if longitude is not None and latitude is not None:
query = query.add_columns(
text("ST_Distance_Sphere(point(longitude, latitude), point(:lon, :lat)) as distance")
).params(lon=longitude, lat=latitude)
# 默认按距离排序
query = query.order_by(text("distance"))
else:
# 如果没有经纬度,则按创建时间排序
query = query.order_by(MerchantDB.create_time.desc())
# 添加一个空的距离列,保持返回结构一致
query = query.add_columns(text("NULL as distance"))
merchants = query.offset(skip).limit(limit).all()
merchant_ids = [m[0].id for m in merchants]
# 获取商家最新或限购商品
merchant_products = {}
for merchant_id in merchant_ids:
# 先查询有限购的商品
product = db.query(MerchantProductDB).filter(
MerchantProductDB.merchant_id == merchant_id,
MerchantProductDB.status == ProductStatus.LISTING,
MerchantProductDB.purchase_limit > 0
).order_by(
MerchantProductDB.create_time.desc()
).first()
# 如果没有限购商品,则查询最新上架的商品
if not product:
product = db.query(MerchantProductDB).filter(
MerchantProductDB.merchant_id == merchant_id,
MerchantProductDB.status == ProductStatus.LISTING
).order_by(
MerchantProductDB.create_time.desc()
).first()
if product:
merchant_products[merchant_id] = {
"product_id": product.id,
"product_name": product.name,
"product_image": product.optimized_image_url,
"product_price": float(product.sale_price),
"purchase_limit": product.purchase_limit,
"promotion_text": product.promotion_text
}
# 处理返回结果
merchant_list = [{
**MerchantInfo.model_validate(m[0]).model_dump(),
"category_name": m[1],
"user_phone": m[2],
"user_nickname": m[3],
"gift_points_rate_text": f"赠送积分 {m[0].pay_gift_points_rate * 10}%",
"featured_product": merchant_products.get(m[0].id),
"distance": round(m[4]) if longitude is not None and latitude is not None else None
} for m in merchants]
# 获取总数(需要考虑分类过滤)
total_query = db.query(MerchantDB)
if category_id is not None:
total_query = total_query.filter(MerchantDB.category_id == category_id)
total = total_query.count()
return success_response(data={
"total": total,
"items": merchant_list
})