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, 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 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( 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, 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( 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, 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, "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": { "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, "address_detail": address.address_detail } }) return success_response(data={ "total": total, "items": orders }) except Exception as e: return error_response(code=500, message=f"获取订单列表失败: {str(e)}") @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, "userid": order.userid, "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, "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)}") @router.get("/admin/list", response_model=ResponseModel) async def get_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, AddressDB, CommunityBuildingDB, CommunityDB ).join( AddressDB, ShippingOrderDB.addressid == AddressDB.id ).join( CommunityBuildingDB, AddressDB.community_building_id == CommunityBuildingDB.id ).join( CommunityDB, CommunityBuildingDB.community_id == CommunityDB.id ) # 添加用户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, 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, "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": { "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, "address_detail": address.address_detail } }) return success_response(data={ "total": total, "items": orders }) except Exception as e: return error_response(code=500, message=f"获取订单列表失败: {str(e)}")