deliveryman-api/app/api/endpoints/order.py
2025-02-20 22:53:49 +08:00

1177 lines
42 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 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,
OrderPriceCalculateRequest,
OrderPriceInfo,
OrderStatus,
OrderCancel,
OrderComplete,
OrderPriceResult
)
from app.models.database import get_db
from app.api.deps import get_current_user, get_deliveryman_user, get_admin_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 app.models.point_product_order import PointProductOrderDB, PointProductOrderInfo, PointProductOrderStatus
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, CommunityBuildingInfo
from app.models.station import StationDB
from app.models.point import PointRecordDB, PointRecordType
from app.core.utils import CommonUtils
import logging
from sqlalchemy import func
from app.core.mpclient import mp_client
from datetime import timedelta
from app.core.imageprocessor import process_image, ImageFormat
from app.core.point_manager import PointManager
router = APIRouter()
def calculate_price(price_request: OrderPriceCalculateRequest,user: UserDB,db: Session) -> OrderPriceResult:
"""
计算订单价格,自动使用优惠券或积分抵扣
Args:
price_request: 价格计算请求
user: 用户信息
Returns:
OrderPriceResult: 包含价格信息和使用的优惠券/积分信息
"""
# 计算所有包裹中的取件码总数
package_count = sum(
# 如果package.pickup_codes是空字符串则取0
0 if len(package.pickup_codes.split(',')) == 0 else 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
),
price_detail_text=settings.ORDER_PREORDER_PRICE_TEXT
)
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:
# 计算最大可抵扣金额
max_points_discount = user.points // settings.POINT_RATIO # 使用整除
points_discount_amount = min(remaining_amount, max_points_discount)
if points_discount_amount > 0:
result.price_info.points_discount_amount = points_discount_amount
result.used_points = int(points_discount_amount * settings.POINT_RATIO)
remaining_amount -= points_discount_amount
# 计算最终金额
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)
):
# 检查是否有未支付的订单
unpay_order = db.query(ShippingOrderDB).filter(
ShippingOrderDB.userid == current_user.userid,
ShippingOrderDB.status == OrderStatus.UNPAID
).first()
if unpay_order:
return error_response(code=400, message="存在未完成的订单", data={
"orderid": unpay_order.orderid
})
"""预下单 - 计算价格"""
price_info = calculate_price(request, current_user, db)
return success_response(data=price_info)
@router.post("", response_model=ResponseModel)
async def create_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 = CommonUtils.generate_order_id('D')
# 计算原始金额
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
# 查询地址信息
address = db.query(AddressDB).filter(
AddressDB.id == order.addressid
).first()
# 创建订单
db_order = ShippingOrderDB(
orderid=orderid,
userid=current_user.userid,
address_customer_name=address.name,
address_customer_phone=address.phone,
address_customer_gender=address.gender,
address_community_id=address.community_id,
address_community_building_id=address.community_building_id,
address_community_name=address.community_name,
address_community_building_name=address.community_building_name,
address_detail=address.address_detail,
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:
# 如果包裹有取件码,则创建包裹
if len(package.pickup_codes) > 0:
station = db.query(StationDB).filter(
StationDB.id == package.station_id
).first()
db_package = ShippingOrderPackageDB(
orderid=orderid,
station_id=package.station_id,
station_name=station.name,
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.add(point_record)
db.commit()
db.refresh(db_order)
# 查询包裹信息
packages = db.query(ShippingOrderPackageDB).filter(
ShippingOrderPackageDB.orderid == orderid
).all()
#发送订单创建成功的消息
if current_user.mp_openid:
await mp_client.send_template_message(
openid=current_user.mp_openid,
template_id=settings.DELIVERY_ORDER_CREATED_TEMPLATE_ID,
data={
"character_string13": db_order.orderid,
"time4": db_order.create_time.strftime("%Y-%m-%d %H:%M:%S"),
"thing10": db_order.address_customer_name,
"thing15": f"{db_order.address_community_name} {db_order.address_community_building_name} {db_order.address_detail}",
"thing9": db_order.status.status_text
}, miniprogram={
"appid": settings.WECHAT_APPID,
"path": f"/pages/order/detail/index?id={db_order.orderid}"
})
# 超过晚上8点则使用明天送达的文案
if db_order.create_time.time() > datetime.time(20, 0, 0):
success_text = settings.ORDER_SUCCESS_TOMORROW_TEXT
else:
success_text = settings.ORDER_SUCCESS_TODAY_TEXT
return success_response(
message="订单创建成功",
data={
"order": OrderInfo.model_validate(db_order),
"packages": [OrderPackageInfo.model_validate(p) for p in packages],
"success_text" : success_text
}
)
except Exception as e:
db.rollback()
logging.exception(f"订单创建失败: {str(e)}")
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)):
"""获取订单详情"""
# 使用 join 查询获取订单和相关地址信息
order = db.query(
ShippingOrderDB
).filter(
ShippingOrderDB.orderid == orderid
).first()
if not order:
return error_response(code=404, message="订单不存在")
# 查询包裹信息,包含驿站名称
packages = db.query(
ShippingOrderPackageDB,
StationDB.name.label('station_name')
).join(
StationDB,
ShippingOrderPackageDB.station_id == StationDB.id
).filter(
ShippingOrderPackageDB.orderid == orderid
).all()
# 如果有配送员 id则获取配送员信息
if order.deliveryman_user_id:
deliveryman_user = db.query(UserDB).filter(
UserDB.userid == order.deliveryman_user_id
).first()
deliveryman_user_name = deliveryman_user.nickname
deliveryman_user_avatar = deliveryman_user.optimized_avatar
deliveryman_user_phone = deliveryman_user.phone
delivery_count = db.query(ShippingOrderDB).filter(
ShippingOrderDB.deliveryman_user_id == order.deliveryman_user_id,
ShippingOrderDB.status == OrderStatus.COMPLETED
).count()
else:
delivery_count = 0
deliveryman_user_name = None
deliveryman_user_avatar = None
deliveryman_user_phone = None
# 计算配送员分账金额
deliveryman_share = round(order.original_amount * settings.ORDER_DELIVERYMAN_SHARE_RATIO, 1)
# 构建完成图片
complete_images = []
if order.complete_images:
complete_images = order.complete_images.split(",")
complete_images = [process_image(image).thumbnail(500, 500).format(ImageFormat.WEBP).build() for image in complete_images]
# 构建响应数据
order_data = {
"orderid": order.orderid,
"userid": order.userid,
"package_count": order.package_count,
"original_amount": order.original_amount,
"coupon_discount_amount": order.coupon_discount_amount,
"coupon_id": order.coupon_id,
"final_amount": order.final_amount,
"deliveryman_share": deliveryman_share,
"status": order.status,
"complete_images": complete_images,
"create_time": order.create_time,
"complete_time": order.completed_time,
"pickup_time": order.pickup_time,
"received_time": order.received_time,
"delivery_method": order.delivery_method,
"deliveryman_user_id": order.deliveryman_user_id,
"deliveryman_nickname": deliveryman_user_name,
"deliveryman_avatar": deliveryman_user_avatar,
"deliveryman_phone": deliveryman_user_phone,
"delivery_count": delivery_count,
# 地址相关信息
"address_name": order.address_customer_name,
"address_phone": order.address_customer_phone,
"address_detail": order.address_detail,
"address_gender": order.address_customer_gender,
"building_id": order.address_community_building_id,
"building_name": order.address_community_building_name,
"community_id": order.address_community_id,
"community_name": order.address_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
})
# 提供一个接口传入community_id返回订单状态数量
@router.get("/status/count", response_model=ResponseModel)
async def get_order_status_count(
community_id: int,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""获取社区订单状态数量"""
created_count = db.query(
ShippingOrderDB.status
).filter(
ShippingOrderDB.address_community_id == community_id,
ShippingOrderDB.status == OrderStatus.CREATED
).count()
others_status_count = db.query(
ShippingOrderDB.status,
func.count(ShippingOrderDB.orderid)
).filter(
ShippingOrderDB.address_community_id == community_id,
ShippingOrderDB.status != OrderStatus.CREATED,
ShippingOrderDB.deliveryman_user_id == current_user.userid
).group_by(
ShippingOrderDB.status
).all()
result = [{
"status": OrderStatus.CREATED,
"count": created_count
}]
for status, count in others_status_count:
result.append({
"status": status,
"count": count
})
return success_response(data=result)
# 提供一个接口,传入 community_id 返回每栋楼栋的订单数量
@router.get("/community_building/count", response_model=ResponseModel)
async def get_community_building_order_count(
community_id: int,
status: str,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""获取社区每栋楼栋的订单数量"""
# 查询当前社区所有楼栋
community_buildings = db.query(
CommunityBuildingDB
).filter(
CommunityBuildingDB.community_id == community_id
).all()
if not community_buildings:
return error_response(code=404, message="社区不存在")
# 查询每个楼栋的订单数量
query = db.query(
ShippingOrderDB.address_community_building_id,
func.count(ShippingOrderDB.orderid)
).filter(
ShippingOrderDB.address_community_building_id.in_(
[building.id for building in community_buildings]
)
).filter(
ShippingOrderDB.status.in_(status.split(","))
)
# 如果订单状态不是待接单,则需要过滤快递员
if OrderStatus.CREATED not in status:
query = query.filter(
ShippingOrderDB.deliveryman_user_id == current_user.userid
)
building_order_count = query.group_by(
ShippingOrderDB.address_community_building_id
).all()
# 没有订单的楼栋订单数量为0
result = []
building_order_count_dict = dict(building_order_count)
for building in community_buildings:
if building.id not in building_order_count_dict:
result.append({
"building_id": building.id,
"building_name": building.building_name,
"order_count": 0
})
else:
result.append({
"building_id": building.id,
"building_name": building.building_name,
"order_count": building_order_count_dict[building.id]
})
return success_response(data=result)
@router.get("/user/list", response_model=ResponseModel)
async def get_user_orders(
status: Optional[OrderStatus] = None,
skip: int = 0,
limit: int = 20,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""获取用户订单列表"""
try:
# 查询订单
query = db.query(
ShippingOrderDB
).filter(
ShippingOrderDB.userid == current_user.userid
)
# 添加状态过滤
if status:
query = query.filter(ShippingOrderDB.status == status)
# 获取总数
total = query.count()
# 分页查询
results = query.order_by(
ShippingOrderDB.create_time.desc()
).offset(skip).limit(limit).all()
orders = []
for order in results:
# 查询订单包裹信息
packages = db.query(
ShippingOrderPackageDB
).filter(
ShippingOrderPackageDB.orderid == order.orderid
).all()
# 格式化包裹信息
package_list = [{
"id": package.id,
"station_id": package.station_id,
"station_name": package.station_name,
"pickup_codes": package.pickup_codes
} for package in packages]
#查询子订单
sub_orders = db.query(PointProductOrderDB).filter(
PointProductOrderDB.delivery_order_id == order.orderid
).all()
orders.append({
"orderid": order.orderid,
"userid": order.userid,
"status": order.status,
"package_count": order.package_count,
"create_time": order.create_time,
"delivery_method": order.delivery_method,
"original_amount": order.original_amount,
"coupon_discount_amount": order.coupon_discount_amount,
"point_discount_amount": order.point_discount_amount,
"final_amount": order.final_amount,
"packages": package_list,
"sub_orders": [PointProductOrderInfo.model_validate(sub_order) for sub_order in sub_orders],
"address": {
"name": order.address_customer_name,
"phone": order.address_customer_phone,
"gender": order.address_customer_gender,
"community_id": order.address_community_id,
"community_name": order.address_community_name,
"building_id": order.address_community_building_id,
"building_name": order.address_community_building_name,
"address_detail": order.address_detail
},
"complete_images": order.optimized_complete_images,
"received_time": order.received_time,
"pickup_time": order.pickup_time,
"completed_time": order.completed_time
})
return success_response(data={
"total": total,
"items": orders
})
except Exception as e:
return error_response(code=500, message=f"获取订单列表失败: {str(e)}")
@router.post("/{orderid}/user/cancel", response_model=ResponseModel)
async def cancel_order(
orderid: str,
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 = "用户主动取消"
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
# 检查子订单是否全部取消
sub_orders = db.query(PointProductOrderDB).filter(
PointProductOrderDB.delivery_order_id == order.orderid
).all()
if sub_orders:
for sub_order in sub_orders:
sub_order.status = PointProductOrderStatus.CANCELLED
#返还积分
point_manager = PointManager(db)
point_manager.add_points(
user_id=current_user.userid,
points=sub_order.order_point_amount,
description=f"兑换订单取消返还",
order_id=order.orderid
)
# 如果使用了积分,返还积分
if order.point_discount_amount > 0:
# 返还积分
return_points = int(order.point_discount_amount * settings.POINT_RATIO)
point_manager = PointManager(db)
point_manager.add_points(
user_id=current_user.userid,
points=return_points,
description=f"配送订单取消返还",
order_id=order.orderid
)
db.commit()
# 发送模板消息
if current_user.mp_openid:
await mp_client.send_template_message(
openid=current_user.mp_openid,
template_id=settings.DELIVERY_ORDER_CANCELLED_TEMPLATE_ID,
data={
"character_string1": order.orderid,
"time19": CommonUtils.get_current_time(),
"thing5": "用户主动取消订单"
}
)
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.get("/deliveryman/list", response_model=ResponseModel)
async def get_deliveryman_orders(
status: Optional[str] = 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
)
# 状态筛选
if status:
statuses = status.split(",")
query = query.filter(ShippingOrderDB.status.in_(statuses))
# 如果订单状态不是待接单,则需要过滤快递员
if OrderStatus.CREATED not in statuses:
query = query.filter(ShippingOrderDB.deliveryman_user_id == deliveryman.userid)
# 楼栋筛选
if building_id:
query = query.filter(ShippingOrderDB.address_community_building_id == building_id)
# 获取总数
total = query.count()
# 获取分页数据
results = query.order_by(
ShippingOrderDB.create_time.desc()
).offset(skip).limit(limit).all()
# 格式化返回数据
orders = []
for order 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]
# 查询子订单
sub_orders = db.query(PointProductOrderDB).filter(
PointProductOrderDB.delivery_order_id == order.orderid
).all()
orders.append({
"orderid": order.orderid,
"userid": order.userid,
"status": order.status,
"package_count": order.package_count,
"create_time": order.create_time,
"delivery_method": order.delivery_method,
"packages": package_list,
"sub_orders": [PointProductOrderInfo.model_validate(sub_order) for sub_order in sub_orders],
"address": {
"name": order.address_customer_name,
"phone": order.address_customer_phone,
"gender": order.address_customer_gender,
"community_id": order.address_community_id,
"community_name": order.address_community_name,
"building_id": order.address_community_building_id,
"building_name": order.address_community_building_name,
"address_detail": order.address_detail
},
"complete_images": order.optimized_complete_images,
"received_time": order.received_time,
"pickup_time": order.pickup_time,
"completed_time": order.completed_time
})
return success_response(data={
"total": total,
"items": orders
})
@router.post("/{orderid}/deliveryman/cancel", response_model=ResponseModel)
async def deliveryman_cancel_order(
orderid: str,
cancel_data: OrderCancel,
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 and order.status != OrderStatus.RECEIVED:
return error_response(code=400, message="只有未接单或者已接单的订单才能取消")
try:
# 更新订单状态、取消原因和取消用户
order.status = OrderStatus.CANCELLED
order.cancel_reason = cancel_data.reason
order.cancel_user_id = deliveryman.userid
# 如果使用了优惠券,返还优惠券
if order.coupon_id:
coupon = db.query(UserCouponDB).filter(
UserCouponDB.id == order.coupon_id
).first()
if coupon:
coupon.status = CouponStatus.UNUSED
# 检查子订单是否全部取消
sub_orders = db.query(PointProductOrderDB).filter(
PointProductOrderDB.delivery_order_id == order.orderid,
PointProductOrderDB.status != PointProductOrderStatus.CANCELLED
).all()
if sub_orders:
for sub_order in sub_orders:
sub_order.status = PointProductOrderStatus.CANCELLED
#返还积分
point_manager = PointManager(db)
point_manager.add_points(
user_id=order.userid,
points=sub_order.order_point_amount,
description=f"兑换订单取消返还",
order_id=order.orderid
)
# 如果使用了积分,返还积分
if order.point_discount_amount > 0:
# 返还积分
return_points = int(order.point_discount_amount * settings.POINT_RATIO)
point_manager = PointManager(db)
point_manager.add_points(
user_id=order.userid,
points=return_points,
description=f"配送订单取消返还",
order_id=order.orderid
)
db.commit()
# 发送模板消息
if order.userid:
order_user = db.query(UserDB).filter(
UserDB.userid == order.userid
).first()
if order_user.mp_openid:
await mp_client.send_template_message(
openid=order_user.mp_openid,
template_id=settings.DELIVERY_ORDER_CANCELLED_TEMPLATE_ID,
data={
"character_string1": order.orderid,
"time19": CommonUtils.get_current_time(),
"thing5": order.cancel_reason
}
)
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}/deliveryman/complete", response_model=ResponseModel)
async def complete_order(
orderid: str,
complete_data: OrderComplete,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_deliveryman_user)
):
"""完成订单"""
# 查询订单
order = db.query(ShippingOrderDB).filter(
ShippingOrderDB.orderid == orderid,
ShippingOrderDB.deliveryman_user_id == current_user.userid
).first()
if not order:
return error_response(code=404, message="订单不存在")
# 检查订单状态
if order.status != OrderStatus.DELIVERING:
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)
# 更新完成时间
order.completed_time = datetime.now()
# 计算配送员分账金额
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"配送订单收益",
transaction_id=orderid
)
db.commit()
# 发送模板消息
if order.userid:
order_user = db.query(UserDB).filter(
UserDB.userid == order.userid
).first()
if order_user.mp_openid:
deliveryman_user = db.query(UserDB).filter(
UserDB.userid == order.deliveryman_user_id
).first()
await mp_client.send_template_message(
openid=order_user.mp_openid,
template_id=settings.DELIVERY_ORDER_COMPLETED_TEMPLATE_ID,
data={
"character_string13": order.orderid,
"thing3": deliveryman_user.nickname,
"time5" : CommonUtils.get_current_time()
},
miniprogram={
"appid": settings.WECHAT_APPID,
"path": f"/pages/order/detail/index?id={order.orderid}"
}
)
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}/deliveryman/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="订单已被其他配送员接单")
# 检查子订单是否全部处理
sub_orders = db.query(PointProductOrderDB).filter(
PointProductOrderDB.delivery_order_id == order.orderid
).all()
try:
# 更新订单状态和配送员ID
order.status = OrderStatus.RECEIVED
order.deliveryman_user_id = deliveryman.userid
order.received_time = datetime.now()
db.commit()
# 发送模板消息
if order.userid:
order_user = db.query(UserDB).filter(
UserDB.userid == order.userid
).first()
if order_user.mp_openid:
deliveryman_user = db.query(UserDB).filter(
UserDB.userid == order.deliveryman_user_id
).first()
await mp_client.send_template_message(
openid=order_user.mp_openid,
template_id=settings.DELIVERY_ORDER_RECEIVED_TEMPLATE_ID,
data={
"character_string9": order.orderid,
"time8": CommonUtils.get_current_time(),
"thing3": deliveryman_user.nickname
},
miniprogram={
"appid": settings.WECHAT_APPID,
"path": f"/pages/order/detail/index?id={order.orderid}"
}
)
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}/deliveryman/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="只有已接单的订单才能标记为已取货")
# 检查子订单是否全部处理
sub_orders = db.query(PointProductOrderDB).filter(
PointProductOrderDB.delivery_order_id == order.orderid
).all()
for sub_order in sub_orders:
if sub_order.status != PointProductOrderStatus.PENDING:
return error_response(code=400, message="请先处理兑换商品子订单")
try:
# 更新订单状态为配送中
order.status = OrderStatus.DELIVERING
order.pickup_time = datetime.now()
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.get("/admin/list", response_model=ResponseModel)
async def get_admin_orders(
status: Optional[OrderStatus] = None,
user_id: Optional[int] = None,
order_id: Optional[str] = None,
skip: int = 0,
limit: int = 20,
db: Session = Depends(get_db),
admin_user: UserDB = Depends(get_admin_user)
):
"""获取订单列表(管理员接口)
Args:
status: 订单状态过滤
user_id: 用户ID过滤
order_id: 订单号过滤
skip: 跳过记录数
limit: 返回记录数
"""
try:
# 构建基础查询
query = db.query(ShippingOrderDB)
# 添加用户ID过滤
if user_id:
query = query.filter(ShippingOrderDB.userid == user_id)
# 添加状态过滤
if status:
query = query.filter(ShippingOrderDB.status == status)
# 添加订单号过滤
if order_id:
query = query.filter(ShippingOrderDB.orderid == order_id)
# 获取总数
total = query.count()
# 分页查询
results = query.order_by(
ShippingOrderDB.create_time.desc()
).offset(skip).limit(limit).all()
orders = []
for order 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,
"userid": order.userid,
"status": order.status,
"package_count": order.package_count,
"create_time": order.create_time,
"delivery_method": order.delivery_method,
"original_amount": order.original_amount,
"coupon_discount_amount": order.coupon_discount_amount,
"point_discount_amount": order.point_discount_amount,
"final_amount": order.final_amount,
"packages": package_list,
"address": {
"name": order.address_customer_name,
"phone": order.address_customer_phone,
"community_name": order.address_community_name,
"building_name": order.address_community_building_name,
"address_detail": order.address_detail
}
})
return success_response(data={
"total": total,
"items": orders
})
except Exception as e:
logging.exception(f"获取订单列表失败: {str(e)}")
return error_response(code=500, message=f"获取订单列表失败: {str(e)}")
# 获取配送员订单数量汇总
@router.get("/deliveryman/summary", response_model=ResponseModel)
async def get_deliveryman_order_summary(
db: Session = Depends(get_db),
deliveryman: UserDB = Depends(get_deliveryman_user)
):
"""获取配送员订单数量汇总"""
# 查询配送员总订单数量
total = db.query(ShippingOrderDB).filter(
ShippingOrderDB.deliveryman_user_id == deliveryman.userid,
ShippingOrderDB.status == OrderStatus.COMPLETED
).count()
today = datetime.now().date()
yesterday = today - timedelta(days=1)
today_start = datetime.combine(today, datetime.min.time())
today_end = datetime.combine(today, datetime.max.time())
yesterday_start = datetime.combine(yesterday, datetime.min.time())
yesterday_end = datetime.combine(yesterday, datetime.max.time())
# 查询配送员昨日、今日订单数量
yesterday_total = db.query(ShippingOrderDB).filter(
ShippingOrderDB.deliveryman_user_id == deliveryman.userid,
ShippingOrderDB.status == OrderStatus.COMPLETED,
ShippingOrderDB.create_time.between(yesterday_start, yesterday_end)
).count()
today_total = db.query(ShippingOrderDB).filter(
ShippingOrderDB.deliveryman_user_id == deliveryman.userid,
ShippingOrderDB.status == OrderStatus.COMPLETED,
ShippingOrderDB.create_time.between(today_start, today_end)
).count()
return success_response(data={
"total_count": total,
"yesterday_count": yesterday_total,
"today_count": today_total
})