577 lines
20 KiB
Python
577 lines
20 KiB
Python
from app.core.account import AccountManager
|
||
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,
|
||
OrderPriceResult
|
||
)
|
||
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
|
||
from app.models.point import PointRecordDB, PointRecordType
|
||
|
||
router = APIRouter()
|
||
|
||
def calculate_price(price_request: OrderPriceCalculateRequest,user: UserDB,db: Session) -> OrderPriceResult:
|
||
"""
|
||
计算订单价格,自动使用优惠券或积分抵扣
|
||
|
||
Args:
|
||
price_request: 价格计算请求
|
||
user: 用户信息
|
||
|
||
Returns:
|
||
OrderPriceResult: 包含价格信息和使用的优惠券/积分信息
|
||
"""
|
||
# 计算所有包裹中的取件码总数
|
||
package_count = sum(
|
||
len(package.pickup_codes.split(','))
|
||
for package in price_request.packages
|
||
if package.pickup_codes
|
||
)
|
||
|
||
result = OrderPriceResult(
|
||
price_info=OrderPriceInfo(
|
||
package_count=package_count,
|
||
original_amount=settings.ORDER_BASE_PRICE,
|
||
coupon_discount_amount=0,
|
||
points_discount_amount=0,
|
||
final_amount=settings.ORDER_BASE_PRICE
|
||
)
|
||
)
|
||
|
||
original_amount = settings.ORDER_BASE_PRICE
|
||
|
||
# 超过阈值的包裹额外收费
|
||
if package_count > settings.ORDER_EXTRA_PACKAGE_THRESHOLD:
|
||
extra_count = package_count - settings.ORDER_EXTRA_PACKAGE_THRESHOLD
|
||
original_amount += extra_count * settings.ORDER_EXTRA_PACKAGE_PRICE
|
||
|
||
result.price_info.package_count = package_count
|
||
result.price_info.original_amount = original_amount
|
||
result.price_info.final_amount = original_amount
|
||
|
||
remaining_amount = original_amount
|
||
|
||
# 1. 查找用户可用的优惠券(按金额从大到小排序)
|
||
available_coupon = db.query(UserCouponDB).filter(
|
||
UserCouponDB.user_id == user.userid,
|
||
UserCouponDB.status == CouponStatus.UNUSED,
|
||
UserCouponDB.expire_time > datetime.now()
|
||
).order_by(UserCouponDB.coupon_amount.desc()).first()
|
||
|
||
# 2. 如果有可用优惠券,优先使用优惠券
|
||
if available_coupon:
|
||
coupon_discount = min(remaining_amount, available_coupon.coupon_amount)
|
||
result.price_info.coupon_discount_amount = coupon_discount
|
||
result.price_info.coupon_id = available_coupon.id
|
||
result.used_coupon_id = available_coupon.id
|
||
remaining_amount -= coupon_discount
|
||
# 3. 如果没有优惠券,且用户有积分,则使用积分抵扣
|
||
elif user.points > 0:
|
||
# 计算最大可抵扣金额(1元=10积分)
|
||
max_points_discount = user.points / 10
|
||
points_discount = min(remaining_amount, max_points_discount)
|
||
|
||
result.price_info.points_discount_amount = points_discount
|
||
result.price_info.points_used = int(points_discount * 10)
|
||
result.used_points = int(points_discount * 10) # 记录使用的积分数
|
||
remaining_amount -= points_discount
|
||
|
||
# 计算最终金额
|
||
result.price_info.final_amount = remaining_amount
|
||
|
||
return result
|
||
|
||
@router.post("/pre-order", response_model=ResponseModel)
|
||
async def pre_order(
|
||
request: OrderPriceCalculateRequest,
|
||
db: Session = Depends(get_db),
|
||
current_user: UserDB = Depends(get_current_user)
|
||
):
|
||
"""预下单 - 计算价格"""
|
||
price_info = calculate_price(request, current_user, db)
|
||
return success_response(data=price_info)
|
||
|
||
@router.post("/", response_model=ResponseModel)
|
||
async def create_shipping_order(
|
||
order: OrderCreate,
|
||
db: Session = Depends(get_db),
|
||
current_user: UserDB = Depends(get_current_user)
|
||
):
|
||
"""创建配送订单"""
|
||
|
||
# 计算订单价格
|
||
price_result = calculate_price(order.price_request, current_user, db)
|
||
price_info = price_result.price_info
|
||
|
||
# 生成订单号
|
||
orderid = generate_order_id()
|
||
|
||
# 计算原始金额
|
||
original_amount = price_info.original_amount
|
||
|
||
# 计算优惠券折扣
|
||
coupon_discount = price_info.coupon_discount_amount
|
||
coupon_id = price_info.coupon_id
|
||
|
||
# 查询用户优惠券
|
||
if coupon_id:
|
||
user_coupon = db.query(UserCouponDB).filter(
|
||
UserCouponDB.id == coupon_id
|
||
).first()
|
||
|
||
if user_coupon:
|
||
coupon_discount = user_coupon.coupon_amount
|
||
coupon_id = user_coupon.id
|
||
# 更新优惠券状态
|
||
user_coupon.status = CouponStatus.USED
|
||
|
||
# 创建订单
|
||
db_order = ShippingOrderDB(
|
||
orderid=orderid,
|
||
userid=current_user.userid,
|
||
addressid=order.addressid,
|
||
package_count=price_info.package_count,
|
||
original_amount=original_amount,
|
||
coupon_discount_amount=coupon_discount,
|
||
point_discount_amount=price_info.points_discount_amount,
|
||
coupon_id=coupon_id,
|
||
final_amount=price_info.final_amount,
|
||
status=OrderStatus.CREATED,
|
||
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:
|
||
# 如果使用了优惠券,更新优惠券状态
|
||
if price_result.used_coupon_id:
|
||
coupon = db.query(UserCouponDB).filter(
|
||
UserCouponDB.id == price_result.used_coupon_id,
|
||
).first()
|
||
if coupon:
|
||
coupon.status = CouponStatus.USED
|
||
|
||
# 如果使用了积分,扣减用户积分并记录
|
||
if price_result.used_points:
|
||
# 扣减用户积分
|
||
current_user.points -= price_result.used_points
|
||
|
||
# 记录积分变动
|
||
point_record = PointRecordDB(
|
||
user_id=current_user.userid,
|
||
points=-price_result.used_points, # 负数表示扣减
|
||
type=PointRecordType.CONSUME_DEDUCT,
|
||
order_id=db_order.orderid,
|
||
description=f"订单{db_order.orderid}抵扣{price_result.used_points}积分"
|
||
)
|
||
db.add(point_record)
|
||
|
||
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("/{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 # 无需支付,直接完成
|
||
|
||
# 只有在订单完成状态时才进行分账
|
||
# 计算配送员分账金额
|
||
deliveryman_share = order.original_amount * settings.ORDER_DELIVERYMAN_SHARE_RATIO
|
||
|
||
# 使用账户管理器处理分账
|
||
account_manager = AccountManager(db)
|
||
account_manager.change_balance(
|
||
user_id=order.deliveryman_user_id,
|
||
amount=deliveryman_share,
|
||
description=f"订单 {orderid} 配送费分账",
|
||
transaction_id=orderid
|
||
)
|
||
|
||
# 保存完成图片
|
||
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)}") |