From a31d161699c6deff2ec813d6f7e790258b3df5d7 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Sat, 25 Jan 2025 21:35:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=A2=84=E4=B8=8B=E5=8D=95?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=A2=9E=E5=8A=A0=E7=A7=AF=E5=88=86?= =?UTF-8?q?=E6=8A=B5=E6=89=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/endpoints/order.py | 211 +++++++++++++++++++++++-------------- app/models/order.py | 16 ++- app/models/point.py | 16 ++- 3 files changed, 155 insertions(+), 88 deletions(-) diff --git a/app/api/endpoints/order.py b/app/api/endpoints/order.py index a575d41..78643fa 100644 --- a/app/api/endpoints/order.py +++ b/app/api/endpoints/order.py @@ -13,7 +13,8 @@ from app.models.order import ( OrderPriceInfo, OrderStatus, OrderCancel, - OrderComplete + OrderComplete, + OrderPriceResult ) from app.models.database import get_db from app.api.deps import get_current_user, get_deliveryman_user @@ -27,9 +28,91 @@ 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, @@ -38,51 +121,44 @@ async def create_shipping_order( ): """创建配送订单""" + # 计算订单价格 + price_result = calculate_price(order.price_request, current_user, db) + price_info = price_result.price_info + # 生成订单号 orderid = generate_order_id() - # 计算订单价格 - package_count = sum( - len(package.pickup_codes.split(',')) - for package in order.price_request.packages - if package.pickup_codes - ) - # 计算原始金额 - original_amount = settings.ORDER_BASE_PRICE - if package_count > settings.ORDER_EXTRA_PACKAGE_THRESHOLD: - extra_packages = package_count - settings.ORDER_EXTRA_PACKAGE_THRESHOLD - original_amount += extra_packages * settings.ORDER_EXTRA_PACKAGE_PRICE + original_amount = price_info.original_amount # 计算优惠券折扣 - coupon_discount = 0 - coupon_id = None + coupon_discount = price_info.coupon_discount_amount + coupon_id = price_info.coupon_id + # 查询用户优惠券 - user_coupon = db.query(UserCouponDB).filter( - UserCouponDB.user_id == current_user.userid, - UserCouponDB.status == CouponStatus.UNUSED, - UserCouponDB.expire_time > datetime.now(timezone.utc) - ).first() - - if user_coupon: - coupon_discount = user_coupon.coupon_amount - coupon_id = user_coupon.id - # 更新优惠券状态 - user_coupon.status = CouponStatus.USED - - # 计算最终金额 - final_amount = max(0, original_amount - coupon_discount) + 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=package_count, + 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=final_amount, + final_amount=price_info.final_amount, + status=OrderStatus.CREATED, delivery_method=order.delivery_method ) db.add(db_order) @@ -97,6 +173,29 @@ async def create_shipping_order( 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) @@ -309,58 +408,6 @@ async def get_deliveryman_orders( "items": orders }) - -@router.post("/calculate-price", response_model=ResponseModel) -async def calculate_order_price( - request: OrderPriceCalculateRequest, - db: Session = Depends(get_db), - current_user: UserDB = Depends(get_current_user) -): - """计算订单价格""" - # 计算所有包裹中的取件码总数 - package_count = sum( - len(package.pickup_codes.split(',')) - for package in request.packages - if package.pickup_codes - ) - - # 计算原始金额 - original_amount = settings.ORDER_BASE_PRICE - if package_count > settings.ORDER_EXTRA_PACKAGE_THRESHOLD: - extra_packages = package_count - settings.ORDER_EXTRA_PACKAGE_THRESHOLD - original_amount += extra_packages * settings.ORDER_EXTRA_PACKAGE_PRICE - - # 计算优惠券折扣 - coupon_discount = 0 - used_coupon_id = None - - # 查询用户所有可用的优惠券 - available_coupons = db.query(UserCouponDB).filter( - UserCouponDB.user_id == current_user.userid, - UserCouponDB.status == CouponStatus.UNUSED - ).order_by( - UserCouponDB.coupon_amount.desc() # 优先使用面额大的优惠券 - ).all() - - # 选择最优惠的券 - if available_coupons: - user_coupon = available_coupons[0] # 取面额最大的优惠券 - coupon_discount = user_coupon.coupon_amount - used_coupon_id = user_coupon.id - - # 计算最终金额 - final_amount = max(0, original_amount - coupon_discount) - - price_info = OrderPriceInfo( - package_count=package_count, - original_amount=original_amount, - coupon_discount_amount=coupon_discount, - coupon_id=used_coupon_id, - final_amount=final_amount - ) - - return success_response(data=price_info) - @router.post("/{orderid}/cancel", response_model=ResponseModel) async def cancel_order( orderid: str, diff --git a/app/models/order.py b/app/models/order.py index 42d196b..f1b736a 100644 --- a/app/models/order.py +++ b/app/models/order.py @@ -28,6 +28,7 @@ class ShippingOrderDB(Base): package_count = Column(Integer, nullable=False) original_amount = Column(Float, nullable=False) coupon_discount_amount = Column(Float, default=0) + point_discount_amount = Column(Float, default=0) coupon_id = Column(Integer, ForeignKey("user_coupons.id"), nullable=True) final_amount = Column(Float, nullable=False) status = Column(Enum(OrderStatus), nullable=False, default=OrderStatus.CREATED) @@ -80,6 +81,7 @@ class OrderInfo(BaseModel): package_count: int original_amount: float coupon_discount_amount: float + point_discount_amount: float = 0 coupon_id: Optional[int] final_amount: float status: OrderStatus @@ -121,9 +123,11 @@ def generate_order_id() -> str: class OrderPriceInfo(BaseModel): package_count: int original_amount: float - coupon_discount_amount: float + coupon_discount_amount: float = 0 + points_discount_amount: float = 0 + points_used: Optional[int] = None coupon_id: Optional[int] = None - final_amount: float + final_amount: float # 添加取消订单请求模型 class OrderCancel(BaseModel): @@ -131,4 +135,10 @@ class OrderCancel(BaseModel): # 完成订单请求模型 class OrderComplete(BaseModel): - images: Optional[List[str]] = Field(None, max_items=5) # 最多5张图片,可选 \ No newline at end of file + images: Optional[List[str]] = Field(None, max_items=5) # 最多5张图片,可选 + +class OrderPriceResult(BaseModel): + """订单价格计算结果""" + price_info: OrderPriceInfo + used_coupon_id: Optional[int] = None # 使用的优惠券ID + used_points: Optional[int] = None # 使用的积分数 \ No newline at end of file diff --git a/app/models/point.py b/app/models/point.py index 7069877..82661eb 100644 --- a/app/models/point.py +++ b/app/models/point.py @@ -1,16 +1,24 @@ -from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, DECIMAL +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Enum from sqlalchemy.sql import func from pydantic import BaseModel, Field from typing import Optional from datetime import datetime from .database import Base +import enum + +class PointRecordType(str, enum.Enum): + CONSUME_DEDUCT = "CONSUME_DEDUCT" # 消费抵扣 + CONSUME_RETURN = "CONSUME_RETURN" # 消费返还 + SYSTEM_SEND = "SYSTEM_SEND" # 系统发放 class PointRecordDB(Base): __tablename__ = "point_records" id = Column(Integer, primary_key=True, autoincrement=True) user_id = Column(Integer, ForeignKey("users.userid"), nullable=False) - points = Column(DECIMAL(10,1), nullable=False) + points = Column(Integer, nullable=False) # 正数表示增加,负数表示减少 + type = Column(Enum(PointRecordType), nullable=False) + order_id = Column(String(32), nullable=True, index=True) # 添加索引但不设置外键 description = Column(String(200), nullable=False) create_time = Column(DateTime(timezone=True), server_default=func.now()) @@ -22,7 +30,9 @@ class PointRecordCreate(BaseModel): class PointRecordInfo(BaseModel): id: int user_id: int - points: float + points: int + type: PointRecordType + order_id: Optional[str] description: str create_time: datetime