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.order_additional_fee import OrderAdditionalFeeDB, OrderAdditionalFeeInfo, AdditionalFeeResult 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, CouponType 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 from datetime import time from app.core.wecombot import WecomBot from app.models.config import ConfigDB from app.core.mpmessage import sent_order_status_change_message from fastapi import BackgroundTasks from app.core.coupon_manager import CouponManager from app.core.redis_client import redis_client from app.models.timeperiod import TimePeriodDB from app.models.community_timeperiod import CommunityTimePeriodDB from app.models.community_profit_sharing import CommunityProfitSharing from app.core.qcloud import QCloudManager from app.core.wechat import WeChatClient router = APIRouter() def calculate_price(price_request: OrderPriceCalculateRequest,user: UserDB,db: Session) -> OrderPriceResult: """ 计算订单价格,自动使用优惠券或积分抵扣 Args: price_request: 价格计算请求 user: 用户信息 Returns: OrderPriceResult: 包含价格信息和使用的优惠券/积分信息 """ # 计算所有包裹中的取件码总数 pickup_code_count = 0 pickup_images_count = 0 package_count = 0 if price_request.pickup_images: pickup_images_count = price_request.pickup_images_count if price_request.packages: pickup_code_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 ) package_count = pickup_code_count + pickup_images_count result = OrderPriceResult( price_info=OrderPriceInfo( package_count=package_count, pickup_images_count=pickup_images_count, pickup_code_count=pickup_code_count, coupon_discount_amount=0, points_discount_amount=0, original_amount=0, base_delivery_amount=0, more_station_price=0, final_amount=0 ) ) # 默认定价 base_price = settings.ORDER_BASE_PRICE extra_package_price = settings.ORDER_EXTRA_PACKAGE_PRICE extra_package_threshold = settings.ORDER_EXTRA_PACKAGE_THRESHOLD base_more_station_price = 0 # 获取小区定价 if price_request.community_id > 0: community = db.query(CommunityDB).filter( CommunityDB.id == price_request.community_id ).first() if community: base_price = float(community.base_price) extra_package_price = float(community.extra_package_price) extra_package_threshold = int(community.extra_package_threshold) base_more_station_price = float(community.more_station_price) # 是否有多驿站 more_station_price = float(community.more_station_price) * (len(price_request.packages) - 1) if len(price_request.packages) > 1 else 0 base_delivery_amount = round(base_price + extra_package_price * max(0, package_count - extra_package_threshold), 2) original_amount = round(more_station_price + base_delivery_amount, 2) result.price_info.package_count = package_count result.price_info.pickup_images_count = pickup_images_count result.price_info.pickup_code_count = pickup_code_count result.price_info.original_amount = original_amount result.price_info.more_station_price = more_station_price result.price_info.final_amount = original_amount result.price_info.base_delivery_amount = base_delivery_amount remaining_amount = original_amount # 1. 查找用户可用的优惠券(按金额从大到小排序) available_coupon = db.query(UserCouponDB).filter( UserCouponDB.user_id == user.userid, UserCouponDB.coupon_type == CouponType.CASH, 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 = round(remaining_amount, 2) # 计算价格详情 result.price_detail_text = f"* 基础费{round(base_price, 1)}元(含1个驿站和{extra_package_threshold}件包裹), 超件{round(extra_package_price, 1)}元/件\r\n* 超过1个驿站, 增加配送路程, 加收{round(base_more_station_price, 1)}元/驿站" return result def calculate_delivery_share(order: ShippingOrderDB, db: Session) -> float: # 获取对应小区的分润 sharing = db.query(CommunityProfitSharing).filter( CommunityProfitSharing.community_id == order.address_community_id ).first() if sharing: return round(order.original_amount_with_additional_fee * (float(sharing.delivery_rate) / 100.0), 2) else: return 0 def format_delivery_time(delivery_date: datetime, time_period_name: str) -> str: if delivery_date == datetime.now().date(): return f"今日 {time_period_name}" elif delivery_date == datetime.now().date() + timedelta(days=1): return f"明日 {time_period_name}" elif delivery_date == datetime.now().date() - timedelta(days=1): return f"昨日 {time_period_name}" else: return f"{delivery_date.strftime('%m-%d')} {time_period_name}" def has_consecutive_weekdays(weekdays): if not weekdays or len(weekdays) <= 1: return True days = sorted(weekdays) # 处理环绕情况 if 1 in days and 7 in days and abs(days.index(1) - days.index(7)) == 1: # 特殊处理周日和周一的连接 temp = days.copy() temp.remove(1 if days.index(1) == 0 else 7) return all(temp[i] - temp[i-1] == 1 for i in range(1, len(temp))) # 普通情况 return all(days[i] - days[i-1] == 1 for i in range(1, len(days))) @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) ): try: # 检查delivery_date是否是今天以前的日期,则提示重新选择 if request.delivery_date and request.delivery_date < datetime.now().date(): return error_response(code=400, message="配送日期不能选择过去的时间") # 检查是否在服务时间 community = db.query(CommunityDB).filter( CommunityDB.id == request.community_id ).first() if community: # 检查是否在服务时间 if community.weekdays and request.delivery_date: if request.delivery_date.isoweekday() not in community.weekdays: #排序 sorted_weekdays = sorted(community.weekdays) if has_consecutive_weekdays(sorted_weekdays): message = f"服务时间为: 周{sorted_weekdays[0]}-{sorted_weekdays[-1]}" else: message = f"服务时间为: " for day in sorted_weekdays: message += f"周{day}, " message = message[:-2] return error_response(code=400, message=message) # 检查是否有未支付的订单 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) except Exception as e: logging.exception(f"预下单失败: {str(e)}") return error_response(code=500, message=f"预下单失败: {str(e)}") @router.post("", response_model=ResponseModel) async def create_order( background_tasks: BackgroundTasks, 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() # 是否为新人订单 is_first_order = db.query(ShippingOrderDB).filter( ShippingOrderDB.userid == current_user.userid, ShippingOrderDB.status != OrderStatus.CANCELLED ).count() == 0 # 创建订单 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, pickup_images_count=price_info.pickup_images_count, pickup_code_count=price_info.pickup_code_count, original_amount=original_amount, coupon_discount_amount=coupon_discount, point_discount_amount=price_info.points_discount_amount, more_station_price=price_info.more_station_price, coupon_id=coupon_id, final_amount=price_info.final_amount, status=OrderStatus.UNPAID if price_info.final_amount > 0 else OrderStatus.CREATED, delivery_method=order.delivery_method, delivery_date=order.delivery_date, is_first_order=is_first_order ) # 获取社区配送时段 community_time_period = db.query(CommunityTimePeriodDB, TimePeriodDB.name.label('time_period_name'), TimePeriodDB.from_time.label('time_period_from_time'), TimePeriodDB.to_time.label('time_period_to_time')).filter( CommunityTimePeriodDB.id == order.community_time_period_id ).join(TimePeriodDB, CommunityTimePeriodDB.time_period_id == TimePeriodDB.id).first() if community_time_period: db_order.time_period_id = community_time_period.CommunityTimePeriodDB.time_period_id db_order.time_period_name = community_time_period.time_period_name db_order.time_period_from_time = community_time_period.time_period_from_time db_order.time_period_to_time = community_time_period.time_period_to_time # 获取取件图片 if order.price_request.pickup_images: db_order.pickup_images = order.price_request.pickup_images db.add(db_order) # 创建订单包裹 if order.price_request.packages: for package in order.price_request.packages: # 如果包裹有取件码,则创建包裹 if len(package.pickup_codes) > 0: if not package.station_name: station = db.query(StationDB).filter( StationDB.id == package.station_id ).first() if station: station_name = station.name else: station_name = "未知驿站" else: station_name = package.station_name # 对package.pickup_codes中分割的取件码,进行排序 sorted_pickup_codes = CommonUtils.sort_strings_by_first_number(package.pickup_codes.split(",")) db_package = ShippingOrderPackageDB( orderid=orderid, station_id=package.station_id, station_name=station_name, pickup_codes=','.join(sorted_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 coupon.used_time = datetime.now() # 如果使用了积分,扣减用户积分并记录 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() # 发送企业微信消息 wecom_bot = WecomBot() order_info = OrderInfo.model_validate(db_order) background_tasks.add_task( wecom_bot.send_order_notification, db, order_info, OrderStatus.CREATED ) #发送订单创建成功的公众号消息 if current_user.mp_openid: 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 } background_tasks.add_task( sent_order_status_change_message, openid=current_user.mp_openid, template_id=settings.DELIVERY_ORDER_CREATED_TEMPLATE_ID, data=data, orderid=db_order.orderid ) # 添加到新订单队列 if db_order.address_community_id: background_tasks.add_task( redis_client.push_new_order_to_queue, db_order.address_community_id, db_order.orderid, db ) # 今日订单加入今日选中的配送时段的 Redis 消息队列 background_tasks.add_task( redis_client.push_order_to_community_period_queue, db_order.delivery_date, order.community_time_period_id, db_order.orderid ) return success_response( message="订单创建成功", data={ "order": OrderInfo.model_validate(db_order), "packages": [OrderPackageInfo.model_validate(p) for p in packages], "delivery_time": format_delivery_time(db_order.delivery_date, db_order.time_period_name), "success_text" : f"预计 {format_delivery_time(db_order.delivery_date, db_order.time_period_name)} 送达,请注意查收" } ) 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), current_user: UserDB = Depends(get_current_user) ): """获取订单详情""" # 使用 join 查询获取订单和相关地址信息 order = db.query( ShippingOrderDB ).filter( ShippingOrderDB.orderid == orderid ).first() if not order: return error_response(code=404, message="订单不存在") # 如果有配送员 id,则获取配送员信息 if order.deliveryman_user_id: deliveryman_user = db.query(UserDB).filter( UserDB.userid == order.deliveryman_user_id ).first() if deliveryman_user: 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 # 查询包裹信息 packages = db.query( ShippingOrderPackageDB ).filter( ShippingOrderPackageDB.orderid == orderid ).all() if packages: # 构建包裹信息,包含驿站名称 package_list = [{ "id": p.id, "orderid": p.orderid, "station_id": p.station_id, "station_name": p.station_name, "pickup_codes": CommonUtils.sort_strings_by_first_number(p.pickup_codes.split(",")), "create_time": p.create_time } for p in packages] else: package_list = [] # 获取加价请求 request = db.query(OrderAdditionalFeeDB, UserDB.nickname.label("deliveryman_name"), UserDB.phone.label("deliveryman_phone"), UserDB.avatar.label("deliveryman_avatar")).join(UserDB, OrderAdditionalFeeDB.deliveryman_id == UserDB.userid).filter( OrderAdditionalFeeDB.orderid == order.orderid, OrderAdditionalFeeDB.result == AdditionalFeeResult.PENDING ).first() order_additional_fee = None if request: order_additional_fee = { "id": request.OrderAdditionalFeeDB.id, "orderid": request.OrderAdditionalFeeDB.orderid, "order_user_id": request.OrderAdditionalFeeDB.order_user_id, "deliveryman_id": request.OrderAdditionalFeeDB.deliveryman_id, "deliveryman_name": request.deliveryman_name, "deliveryman_phone": request.deliveryman_phone, "deliveryman_avatar": request.deliveryman_avatar, "reason": request.OrderAdditionalFeeDB.reason, "photo_urls": request.OrderAdditionalFeeDB.photo_urls, "additional_fee_amount": request.OrderAdditionalFeeDB.additional_fee_amount, "result": request.OrderAdditionalFeeDB.result, } # 计算配送时间 delivery_time = format_delivery_time(order.delivery_date, order.time_period_name) # 计算配送员配送费用 deliveryman_share = order.delivery_share if order.delivery_share > 0 else calculate_delivery_share(order, db) base_delivery_amount = order.original_amount - (order.more_station_price if order.more_station_price is not None else 0) # 构建响应数据 order_data = { "orderid": order.orderid, "userid": order.userid, "pickup_images": order.optimized_pickup_images, "package_count": order.package_count, "pickup_code_count": order.pickup_code_count, "pickup_images_count": order.pickup_images_count, "original_amount": order.original_amount, "base_delivery_amount": base_delivery_amount, "coupon_discount_amount": order.coupon_discount_amount, "point_discount_amount": order.point_discount_amount, "more_station_price": order.more_station_price, "additional_fee_amount": order.additional_fee_amount, "coupon_id": order.coupon_id, "final_amount": order.final_amount, "deliveryman_share": deliveryman_share, "status": order.status, "complete_images": order.optimized_complete_images, "packages": package_list, "is_first_order": order.is_first_order, "cancel_reason": order.cancel_reason, "order_additional_fee": order_additional_fee, "create_time": order.create_time, "complete_time": order.completed_time, "pickup_time": order.pickup_time, "received_time": order.received_time, "delivery_date": order.delivery_date, "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, # 配送时段 "time_period_id": order.time_period_id, "time_period_name": order.time_period_name, "time_period_from_time": order.time_period_from_time, "time_period_to_time": order.time_period_to_time, # 配送时间 "delivery_time": delivery_time } return success_response(data=order_data) # 提供一个接口,传入community_id,返回订单状态数量 @router.get("/status/count", response_model=ResponseModel) async def deliveryman_get_order_status_count( community_id: int, time_period_id: Optional[int] = None, db: Session = Depends(get_db), current_user: UserDB = Depends(get_current_user) ): """获取社区订单状态数量""" status_list = [OrderStatus.CREATED, OrderStatus.RECEIVED, OrderStatus.DELIVERING, OrderStatus.COMPLETED] result = [] for status in status_list: query = db.query( ShippingOrderDB.status ).filter( ShippingOrderDB.address_community_id == community_id, ShippingOrderDB.status == status ) # 如果传入了时间周期id,则过滤时间周期 if time_period_id: query = query.filter( ShippingOrderDB.time_period_id == time_period_id ) # 不是待接单的订单,需要过滤配送员 if status != OrderStatus.CREATED: query = query.filter( ShippingOrderDB.deliveryman_user_id == current_user.userid ) # 待接单的订单,只显示今天以及今天以前的订单 # if status == OrderStatus.CREATED: # query = query.filter( # ShippingOrderDB.delivery_date <= datetime.now().date() # ) count = query.count() result.append({ "status": status, "count": count }) return success_response(data=result) # 提供一个接口,传入 community_id 返回每栋楼栋的订单数量 @router.get("/community_building/count", response_model=ResponseModel) async def deliveryman_get_community_building_order_count( community_id: int, status: str, time_period_id: Optional[int] = None, 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(",")) ) # 如果传入了时间周期id,则过滤时间周期 if time_period_id: query = query.filter( ShippingOrderDB.time_period_id == time_period_id ) # 如果订单状态不是待接单,则需要过滤快递员 if OrderStatus.CREATED not in status: query = query.filter( ShippingOrderDB.deliveryman_user_id == current_user.userid ) # 待接单的订单,只显示今天以及今天以前的订单 # if OrderStatus.CREATED in status: # query = query.filter( # ShippingOrderDB.delivery_date <= datetime.now().date() # ) 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() # 格式化包裹信息 if packages: package_list = [{ "id": package.id, "station_id": package.station_id, "station_name": package.station_name, "pickup_codes": package.pickup_codes } for package in packages] else: package_list = [] #查询子订单 sub_orders = db.query(PointProductOrderDB).filter( PointProductOrderDB.delivery_order_id == order.orderid ).all() orders.append({ "orderid": order.orderid, "userid": order.userid, "status": order.status, "pickup_images": order.optimized_pickup_images, "package_count": order.package_count, "pickup_code_count": order.pickup_code_count, "pickup_images_count": order.pickup_images_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, "is_first_order": order.is_first_order, "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( background_tasks: BackgroundTasks, 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.UNPAID]: return error_response(code=400, message="当前订单状态不可取消") if order.deliveryman_user_id: 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 coupon.used_time = None # 检查子订单是否全部取消 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 order.final_amount > 0: wechat = WeChatClient() await wechat.apply_refund( order_id=order.orderid, total_amount=int(float(order.final_amount) * 100) if not settings.DEBUG else 1, # 转换为分 reason="用户取消订单" ) # 发送企业微信消息 wecom_bot = WecomBot() order_info = OrderInfo.model_validate(order) background_tasks.add_task( wecom_bot.send_order_notification, db, order_info, OrderStatus.CANCELLED ) # 发送模板消息 if current_user.mp_openid: data={ "character_string1": order.orderid, "time19": CommonUtils.get_current_time(), "thing5": "用户主动取消订单" } background_tasks.add_task( sent_order_status_change_message, openid=current_user.mp_openid, template_id=settings.DELIVERY_ORDER_CANCELLED_TEMPLATE_ID, data=data, orderid=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.get("/deliveryman/list", response_model=ResponseModel) async def deliveryman_orders( status: Optional[str] = None, building_id: Optional[int] = None, community_id: Optional[int] = None, time_period_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 OrderStatus.CREATED in statuses: # query = query.filter(ShippingOrderDB.delivery_date <= datetime.now().date()) # 楼栋筛选 if building_id: query = query.filter(ShippingOrderDB.address_community_building_id == building_id) # 小区筛选 if community_id: query = query.filter(ShippingOrderDB.address_community_id == community_id) # 配送时段筛选 if time_period_id: query = query.filter(ShippingOrderDB.time_period_id == time_period_id) # 获取总数 total = query.count() # 获取分页数据 results = query.order_by( ShippingOrderDB.delivery_date.desc(), ShippingOrderDB.time_period_from_time.asc() ).offset(skip).limit(limit).all() # 格式化返回数据 orders = [] for order in results: # 查询订单包裹信息 packages = db.query( ShippingOrderPackageDB ).filter( ShippingOrderPackageDB.orderid == order.orderid ).all() # 格式化包裹信息 if packages: package_list = [{ "id": package.id, "station_id": package.station_id, "station_name": package.station_name, "pickup_codes": CommonUtils.sort_strings_by_first_number(package.pickup_codes.split(",")) } for package in packages] else: package_list = [] # 查询子订单 sub_orders = db.query(PointProductOrderDB).filter( PointProductOrderDB.delivery_order_id == order.orderid ).all() # 获取加价请求 requests = db.query(OrderAdditionalFeeDB, UserDB.nickname.label("deliveryman_name"), UserDB.phone.label("deliveryman_phone"), UserDB.avatar.label("deliveryman_avatar")).join(UserDB, OrderAdditionalFeeDB.deliveryman_id == UserDB.userid).filter( OrderAdditionalFeeDB.orderid == order.orderid ).all() order_additional_fees = [] if requests: for fee in requests: order_additional_fees.append({ "id": fee.OrderAdditionalFeeDB.id, "orderid": fee.OrderAdditionalFeeDB.orderid, "order_user_id": fee.OrderAdditionalFeeDB.order_user_id, "deliveryman_id": fee.OrderAdditionalFeeDB.deliveryman_id, "deliveryman_name": fee.deliveryman_name, "deliveryman_phone": fee.deliveryman_phone, "deliveryman_avatar": fee.deliveryman_avatar, "reason": fee.OrderAdditionalFeeDB.reason, "photo_urls": fee.OrderAdditionalFeeDB.photo_urls, "additional_fee_amount": fee.OrderAdditionalFeeDB.additional_fee_amount, "result": fee.OrderAdditionalFeeDB.result, }) delivery_time = format_delivery_time(order.delivery_date, order.time_period_name) orders.append({ "orderid": order.orderid, "userid": order.userid, "status": order.status, "pickup_images": order.optimized_pickup_images, "package_count": order.package_count, "pickup_code_count": order.pickup_code_count, "pickup_images_count": order.pickup_images_count, "create_time": order.create_time, "delivery_method": order.delivery_method, "delivery_time": delivery_time, "original_amount": order.original_amount, "coupon_discount_amount": order.coupon_discount_amount, "point_discount_amount": order.point_discount_amount, "final_amount": order.final_amount, "is_first_order": order.is_first_order, "packages": package_list, "order_additional_fees": order_additional_fees, "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, "time_period_id": order.time_period_id, "time_period_name": order.time_period_name, "time_period_from_time": order.time_period_from_time, "time_period_to_time": order.time_period_to_time }) return success_response(data={ "total": total, "items": orders }) @router.post("/{orderid}/deliveryman/cancel", response_model=ResponseModel) async def deliveryman_cancel_order( background_tasks: BackgroundTasks, 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 coupon.used_time = None # 检查子订单是否全部取消 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.final_amount > 0: wechat = WeChatClient() await wechat.apply_refund( order_id=order.orderid, total_amount=int(float(order.final_amount) * 100) if not settings.DEBUG else 1, # 转换为分 reason="配送员取消订单" ) # 发送企业微信消息 wecom_bot = WecomBot() order_info = OrderInfo.model_validate(order) background_tasks.add_task( wecom_bot.send_order_notification, db, order_info, OrderStatus.CANCELLED ) # 发送模板消息 if order.userid: order_user = db.query(UserDB).filter( UserDB.userid == order.userid ).first() if order_user.mp_openid: data={ "character_string1": order.orderid, "time19": CommonUtils.get_current_time(), "thing5": order.cancel_reason } background_tasks.add_task( sent_order_status_change_message, openid=order_user.mp_openid, template_id=settings.DELIVERY_ORDER_CANCELLED_TEMPLATE_ID, data=data, orderid=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/complete", response_model=ResponseModel) async def deliveryman_complete_order( background_tasks: BackgroundTasks, 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() # 订单用户 order_user = db.query(UserDB).filter( UserDB.userid == order.userid ).first() if not order: return error_response(code=404, message="订单不存在") # 检查订单状态 if order.status != OrderStatus.DELIVERING: return error_response(code=400, message="只有配送中的订单才能标记为完成") try: order.status = OrderStatus.COMPLETED # 无需支付,直接完成 # 保存完成图片 if complete_data.images: order.complete_images = ",".join(complete_data.images) # 更新完成时间 order.completed_time = datetime.now() # 使用账户管理器处理分账 if order.delivery_share > 0: account_manager = AccountManager(db) account_manager.change_balance( user_id=order.deliveryman_user_id, amount= order.delivery_share, description=f"配送订单{order.orderid[-4:]}收益", transaction_id=orderid ) db.commit() # 有邀请人,给邀请人积分奖励 if order_user.referral_code: # 查询邀请人 invite_user = db.query(UserDB).filter( UserDB.user_code == order_user.referral_code ).first() if invite_user: points = settings.FIRST_ORDER_REFERRAL_POINT if order.is_first_order else settings.COMMON_ORDER_REFERRAL_POINT desc = f"蜜友首单奖励" if order.is_first_order else f"蜜友下单奖励" # 邀请人赠送积分 point_manager = PointManager(db) point_manager.add_points( user_id=invite_user.userid, points=points, description=desc, order_id=order.orderid ) # 发送企业微信消息 wecom_bot = WecomBot() order_info = OrderInfo.model_validate(order) background_tasks.add_task( wecom_bot.send_order_notification, db, order_info, OrderStatus.COMPLETED ) qcloud = QCloudManager() background_tasks.add_task( qcloud.send_sms_order_complete, order.address_customer_phone ) # 发送模板消息 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() data={ "character_string13": order.orderid, "thing3": deliveryman_user.nickname, "time5" : CommonUtils.get_current_time() } background_tasks.add_task( sent_order_status_change_message, openid=order_user.mp_openid, template_id=settings.DELIVERY_ORDER_COMPLETED_TEMPLATE_ID, data=data, orderid=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 deliveryman_receive_order( background_tasks: BackgroundTasks, orderid: str, db: Session = Depends(get_db), deliveryman: UserDB = Depends(get_deliveryman_user) ): """接单(仅配送员可用)""" if not deliveryman.is_auth: return error_response(code=400, message="请先完成实名认证") # 查询订单 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() # 接单就确认收益 order.delivery_share = calculate_delivery_share(order, db) db.commit() # 发送企业微信消息 wecom_bot = WecomBot() order_info = OrderInfo.model_validate(order) background_tasks.add_task( wecom_bot.send_order_notification, db, order_info, OrderStatus.RECEIVED ) # 发送模板消息 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() data={ "character_string9": order.orderid, "time8": CommonUtils.get_current_time(), "thing3": deliveryman_user.nickname } background_tasks.add_task( sent_order_status_change_message, openid=order_user.mp_openid, template_id=settings.DELIVERY_ORDER_RECEIVED_TEMPLATE_ID, data=data, orderid=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 deliveryman_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("/platform/list", response_model=ResponseModel) async def get_orders( db: Session = Depends(get_db), status: Optional[OrderStatus] = None, user_id: Optional[int] = None, order_id: Optional[str] = None, skip: int = 0, limit: int = 10): """获取订单列表""" 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 ).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] item = { "orderid": order.orderid, "userid": order.userid, "status": order.status, "pickup_images": order.optimized_pickup_images, "package_count": order.package_count, "pickup_code_count": order.pickup_code_count, "pickup_images_count": order.pickup_images_count, "create_time": order.create_time, "delivery_method": order.delivery_method, "original_amount": order.original_amount, "delivery_date": order.delivery_date, "time_period_name": order.time_period_name, "coupon_discount_amount": order.coupon_discount_amount, "point_discount_amount": order.point_discount_amount, "cancel_reason": order.cancel_reason, "additional_fee_amount": order.additional_fee_amount, "is_delivery_cancel": order.cancel_user_id == order.deliveryman_user_id, "complete_images": order.optimized_complete_images, "completed_time": order.completed_time, "final_amount": order.final_amount, "is_first_order": order.is_first_order, "packages": package_list, "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 }, } # 查询配送员 deliveryman = db.query(UserDB).filter( UserDB.userid == order.deliveryman_user_id ).first() if deliveryman: item["deliveryman"] = { "name": deliveryman.nickname, "phone": deliveryman.phone } orders.append(item) 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("/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 ).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() item = { "orderid": order.orderid, "userid": order.userid, "status": order.status, "pickup_images": order.optimized_pickup_images, "package_count": order.package_count, "pickup_code_count": order.pickup_code_count, "pickup_images_count": order.pickup_images_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, "complete_images": order.optimized_complete_images, "completed_time": order.completed_time, "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 }, } # 查询配送员 deliveryman = db.query(UserDB).filter( UserDB.userid == order.deliveryman_user_id ).first() if deliveryman: item["deliveryman"] = { "name": deliveryman.nickname, "phone": deliveryman.phone } orders.append(item) 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 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.completed_time.between(yesterday_start, yesterday_end) ).count() today_total = db.query(ShippingOrderDB).filter( ShippingOrderDB.deliveryman_user_id == deliveryman.userid, ShippingOrderDB.status == OrderStatus.COMPLETED, ShippingOrderDB.completed_time.between(today_start, today_end) ).count() return success_response(data={ "total_count": total, "yesterday_count": yesterday_total, "today_count": today_total }) @router.get("/deliveryman/check_new_order", response_model=ResponseModel) async def deliveryman_check_new_orders( db: Session = Depends(get_db), deliveryman: UserDB = Depends(get_deliveryman_user) ): """检查新订单""" # 从Redis获取新订单ID列表 order_ids = redis_client.pop_new_orders_from_queue(deliveryman.userid) if not order_ids: return success_response(data={ "has_new_order": False, "order_ids": [] }) else: return success_response(data={ "has_new_order": len(order_ids) > 0, "order_ids": order_ids })