deliveryman-api/app/api/endpoints/order.py
aaron 91e56c9eb3 1. merchant 增加 user 归属
2. 验证订单后,给对应的 user 增加结算 balance 和 details
3. 配送订单完成后,根据是否需要支付,切换到不同的状态。
2025-01-19 15:03:29 +08:00

516 lines
17 KiB
Python

from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from typing import List, Optional
from app.models.order import (
ShippingOrderDB,
ShippingOrderPackageDB,
OrderCreate,
OrderInfo,
OrderPackageInfo,
generate_order_id,
OrderPriceCalculateRequest,
OrderPriceInfo,
OrderStatus,
OrderCancel,
OrderComplete
)
from app.models.database import get_db
from app.api.deps import get_current_user, get_deliveryman_user
from app.models.user import UserDB,UserRole
from app.core.response import success_response, error_response, ResponseModel
from app.models.coupon import UserCouponDB, CouponStatus
from datetime import datetime, timezone
from app.core.config import settings
from app.models.address import AddressDB
from sqlalchemy.orm import joinedload
from app.models.community import CommunityDB
from app.models.community_building import CommunityBuildingDB
from app.models.station import StationDB
router = APIRouter()
@router.post("/", response_model=ResponseModel)
async def create_shipping_order(
order: OrderCreate,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""创建配送订单"""
# 生成订单号
orderid = generate_order_id()
# 计算订单价格
package_count = sum(
len(package.pickup_codes.split(','))
for package in order.price_request.packages
if package.pickup_codes
)
# 计算原始金额
original_amount = settings.ORDER_BASE_PRICE
if package_count > settings.ORDER_EXTRA_PACKAGE_THRESHOLD:
extra_packages = package_count - settings.ORDER_EXTRA_PACKAGE_THRESHOLD
original_amount += extra_packages * settings.ORDER_EXTRA_PACKAGE_PRICE
# 计算优惠券折扣
coupon_discount = 0
coupon_id = None
# 查询用户优惠券
user_coupon = db.query(UserCouponDB).filter(
UserCouponDB.user_id == current_user.userid,
UserCouponDB.status == CouponStatus.UNUSED,
UserCouponDB.expire_time > datetime.now(timezone.utc)
).first()
if user_coupon:
coupon_discount = user_coupon.coupon_amount
coupon_id = user_coupon.id
# 更新优惠券状态
user_coupon.status = CouponStatus.USED
# 计算最终金额
final_amount = max(0, original_amount - coupon_discount)
# 创建订单
db_order = ShippingOrderDB(
orderid=orderid,
userid=current_user.userid,
addressid=order.addressid,
package_count=package_count,
original_amount=original_amount,
coupon_discount_amount=coupon_discount,
coupon_id=coupon_id,
final_amount=final_amount,
delivery_method=order.delivery_method
)
db.add(db_order)
# 创建订单包裹
for package in order.price_request.packages:
db_package = ShippingOrderPackageDB(
orderid=orderid,
station_id=package.station_id,
pickup_codes=package.pickup_codes
)
db.add(db_package)
try:
db.commit()
db.refresh(db_order)
# 查询包裹信息
packages = db.query(ShippingOrderPackageDB).filter(
ShippingOrderPackageDB.orderid == orderid
).all()
return success_response(
message="订单创建成功",
data={
"order": OrderInfo.model_validate(db_order),
"packages": [OrderPackageInfo.model_validate(p) for p in packages]
}
)
except Exception as e:
db.rollback()
return error_response(code=500, message=f"订单创建失败: {str(e)}")
@router.get("/{orderid}", response_model=ResponseModel)
async def get_order_detail(
orderid: str,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""获取订单详情"""
# 使用 join 查询获取订单和相关地址信息
order = db.query(
ShippingOrderDB,
AddressDB.name.label('address_name'),
AddressDB.phone.label('address_phone'),
AddressDB.address_detail.label('address_detail'),
CommunityBuildingDB.building_name.label('building_name'),
CommunityDB.name.label('community_name')
).join(
AddressDB,
ShippingOrderDB.addressid == AddressDB.id
).join(
CommunityBuildingDB,
AddressDB.community_building_id == CommunityBuildingDB.id
).join(
CommunityDB,
CommunityBuildingDB.community_id == CommunityDB.id
).filter(
ShippingOrderDB.orderid == orderid
).first()
if not order:
return error_response(code=404, message="订单不存在")
# 检查权限
if order.ShippingOrderDB.userid != current_user.userid and UserRole.ADMIN not in current_user.roles:
return error_response(code=403, message="无权查看此订单")
# 查询包裹信息,包含驿站名称
packages = db.query(
ShippingOrderPackageDB,
StationDB.name.label('station_name')
).join(
StationDB,
ShippingOrderPackageDB.station_id == StationDB.id
).filter(
ShippingOrderPackageDB.orderid == orderid
).all()
# 构建响应数据
order_data = {
"orderid": order.ShippingOrderDB.orderid,
"userid": order.ShippingOrderDB.userid,
"addressid": order.ShippingOrderDB.addressid,
"package_count": order.ShippingOrderDB.package_count,
"original_amount": order.ShippingOrderDB.original_amount,
"coupon_discount_amount": order.ShippingOrderDB.coupon_discount_amount,
"coupon_id": order.ShippingOrderDB.coupon_id,
"final_amount": order.ShippingOrderDB.final_amount,
"status": order.ShippingOrderDB.status,
"complete_images": order.ShippingOrderDB.complete_images.split(",") if order.ShippingOrderDB.complete_images else None,
"create_time": order.ShippingOrderDB.create_time,
"delivery_method": order.ShippingOrderDB.delivery_method,
"deliveryman_user_id": order.ShippingOrderDB.deliveryman_user_id,
# 地址相关信息
"address_name": order.address_name,
"address_phone": order.address_phone,
"address_detail": order.address_detail,
"building_name": order.building_name,
"community_name": order.community_name
}
# 构建包裹信息,包含驿站名称
package_list = [{
"id": p.ShippingOrderPackageDB.id,
"orderid": p.ShippingOrderPackageDB.orderid,
"station_id": p.ShippingOrderPackageDB.station_id,
"station_name": p.station_name,
"pickup_codes": p.ShippingOrderPackageDB.pickup_codes,
"create_time": p.ShippingOrderPackageDB.create_time
} for p in packages]
return success_response(data={
"order": order_data,
"packages": package_list
})
@router.get("/user/list", response_model=ResponseModel)
async def get_user_orders(
skip: int = 0,
limit: int = 10,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""获取用户的订单列表"""
orders = db.query(ShippingOrderDB).filter(
ShippingOrderDB.userid == current_user.userid
).order_by(
ShippingOrderDB.create_time.desc()
).offset(skip).limit(limit).all()
return success_response(data=[OrderInfo.model_validate(o) for o in orders])
@router.get("/deliveryman/list", response_model=ResponseModel)
async def get_deliveryman_orders(
status: Optional[OrderStatus] = None,
building_id: Optional[int] = None,
skip: int = 0,
limit: int = 20,
db: Session = Depends(get_db),
deliveryman: UserDB = Depends(get_deliveryman_user)
):
"""获取配送员订单列表"""
# 基础查询
query = db.query(
ShippingOrderDB,
AddressDB,
CommunityBuildingDB,
CommunityDB
).join(
AddressDB,
ShippingOrderDB.addressid == AddressDB.id
).join(
CommunityBuildingDB,
AddressDB.community_building_id == CommunityBuildingDB.id
).join(
CommunityDB,
CommunityBuildingDB.community_id == CommunityDB.id
).filter(
CommunityDB.id == deliveryman.community_id
)
# 状态筛选
if status:
query = query.filter(ShippingOrderDB.status == status)
# 楼栋筛选
if building_id:
query = query.filter(AddressDB.community_building_id == building_id)
# 获取总数
total = query.count()
# 获取分页数据
results = query.order_by(
ShippingOrderDB.create_time.desc()
).offset(skip).limit(limit).all()
# 格式化返回数据
orders = []
for order, address, building, community in results:
# 查询订单包裹信息
packages = db.query(
ShippingOrderPackageDB,
StationDB.name.label('station_name')
).join(
StationDB,
ShippingOrderPackageDB.station_id == StationDB.id
).filter(
ShippingOrderPackageDB.orderid == order.orderid
).all()
# 格式化包裹信息
package_list = [{
"id": package.ShippingOrderPackageDB.id,
"station_id": package.ShippingOrderPackageDB.station_id,
"station_name": package.station_name,
"pickup_codes": package.ShippingOrderPackageDB.pickup_codes
} for package in packages]
orders.append({
"orderid": order.orderid,
"status": order.status,
"package_count": order.package_count,
"create_time": order.create_time,
"delivery_method": order.delivery_method,
"packages": package_list,
"address": {
"id": address.id,
"name": address.name,
"phone": address.phone,
"community_id": community.id,
"community_name": community.name,
"building_id": building.id,
"building_name": building.building_name,
"building_number": building.building_number,
"room_number": address.room_number,
"address_detail": address.address_detail
}
})
return success_response(data={
"total": total,
"items": orders
})
@router.post("/calculate-price", response_model=ResponseModel)
async def calculate_order_price(
request: OrderPriceCalculateRequest,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""计算订单价格"""
# 计算所有包裹中的取件码总数
package_count = sum(
len(package.pickup_codes.split(','))
for package in request.packages
if package.pickup_codes
)
# 计算原始金额
original_amount = settings.ORDER_BASE_PRICE
if package_count > settings.ORDER_EXTRA_PACKAGE_THRESHOLD:
extra_packages = package_count - settings.ORDER_EXTRA_PACKAGE_THRESHOLD
original_amount += extra_packages * settings.ORDER_EXTRA_PACKAGE_PRICE
# 计算优惠券折扣
coupon_discount = 0
used_coupon_id = None
# 查询用户所有可用的优惠券
available_coupons = db.query(UserCouponDB).filter(
UserCouponDB.user_id == current_user.userid,
UserCouponDB.status == CouponStatus.UNUSED
).order_by(
UserCouponDB.coupon_amount.desc() # 优先使用面额大的优惠券
).all()
# 选择最优惠的券
if available_coupons:
user_coupon = available_coupons[0] # 取面额最大的优惠券
coupon_discount = user_coupon.coupon_amount
used_coupon_id = user_coupon.id
# 计算最终金额
final_amount = max(0, original_amount - coupon_discount)
price_info = OrderPriceInfo(
package_count=package_count,
original_amount=original_amount,
coupon_discount_amount=coupon_discount,
coupon_id=used_coupon_id,
final_amount=final_amount
)
return success_response(data=price_info)
@router.post("/{orderid}/cancel", response_model=ResponseModel)
async def cancel_order(
orderid: str,
cancel_data: OrderCancel,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""取消订单"""
# 查询订单
order = db.query(ShippingOrderDB).filter(
ShippingOrderDB.orderid == orderid,
ShippingOrderDB.userid == current_user.userid
).first()
if not order:
return error_response(code=404, message="订单不存在")
# 检查订单状态是否可取消
if order.status not in [OrderStatus.CREATED, OrderStatus.RECEIVED]:
return error_response(code=400, message="当前订单状态不可取消")
try:
# 更新订单状态、取消原因和取消用户
order.status = OrderStatus.CANCELLED
order.cancel_reason = cancel_data.reason
order.cancel_user_id = current_user.userid # 记录取消订单的用户ID
# 如果使用了优惠券,返还优惠券
if order.coupon_id:
coupon = db.query(UserCouponDB).filter(
UserCouponDB.id == order.coupon_id
).first()
if coupon:
coupon.status = CouponStatus.UNUSED
db.commit()
return success_response(
message="订单取消成功",
data=OrderInfo.model_validate(order)
)
except Exception as e:
db.rollback()
return error_response(code=500, message=f"取消订单失败: {str(e)}")
@router.post("/{orderid}/complete", response_model=ResponseModel)
async def complete_order(
orderid: str,
complete_data: OrderComplete,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""完成订单"""
# 查询订单
order = db.query(ShippingOrderDB).filter(
ShippingOrderDB.orderid == orderid,
ShippingOrderDB.userid == current_user.userid
).first()
if not order:
return error_response(code=404, message="订单不存在")
# 检查订单状态
if order.status != OrderStatus.RECEIVED:
return error_response(code=400, message="只有已接单的订单才能标记为完成")
try:
# 根据订单金额决定状态
if order.final_amount > 0:
order.status = OrderStatus.UNPAID # 需要支付
else:
order.status = OrderStatus.COMPLETED # 无需支付,直接完成
# 保存完成图片
if complete_data.images:
order.complete_images = ",".join(complete_data.images)
db.commit()
return success_response(
message="订单已完成" if order.final_amount == 0 else "请继续支付",
data=OrderInfo.model_validate(order)
)
except Exception as e:
db.rollback()
return error_response(code=500, message=f"操作失败: {str(e)}")
@router.post("/{orderid}/receive", response_model=ResponseModel)
async def receive_order(
orderid: str,
db: Session = Depends(get_db),
deliveryman: UserDB = Depends(get_deliveryman_user)
):
"""接单(仅配送员可用)"""
# 查询订单
order = db.query(ShippingOrderDB).filter(
ShippingOrderDB.orderid == orderid
).first()
if not order:
return error_response(code=404, message="订单不存在")
# 检查订单状态
if order.status != OrderStatus.CREATED:
return error_response(code=400, message="只能接待新创建的订单")
# 检查订单是否已被接单
if order.deliveryman_user_id is not None:
return error_response(code=400, message="订单已被其他配送员接单")
try:
# 更新订单状态和配送员ID
order.status = OrderStatus.RECEIVED
order.deliveryman_user_id = deliveryman.userid
db.commit()
return success_response(
message="接单成功",
data=OrderInfo.model_validate(order)
)
except Exception as e:
db.rollback()
return error_response(code=500, message=f"接单失败: {str(e)}")
@router.post("/{orderid}/pickup", response_model=ResponseModel)
async def pickup_order(
orderid: str,
db: Session = Depends(get_db),
deliveryman: UserDB = Depends(get_deliveryman_user)
):
"""标记订单为已取货(仅配送员可用)"""
# 查询订单
order = db.query(ShippingOrderDB).filter(
ShippingOrderDB.orderid == orderid,
ShippingOrderDB.deliveryman_user_id == deliveryman.userid # 必须是该配送员的订单
).first()
if not order:
return error_response(code=404, message="订单不存在")
# 检查订单状态
if order.status != OrderStatus.RECEIVED:
return error_response(code=400, message="只有已接单的订单才能标记为已取货")
try:
# 更新订单状态为配送中
order.status = OrderStatus.DELIVERING
db.commit()
return success_response(
message="已标记为取货",
data=OrderInfo.model_validate(order)
)
except Exception as e:
db.rollback()
return error_response(code=500, message=f"操作失败: {str(e)}")