增加优惠券活动接口

This commit is contained in:
aaron 2025-02-20 22:53:49 +08:00
parent 683aa8e2c2
commit 3dc82e0986
6 changed files with 297 additions and 4 deletions

View File

@ -0,0 +1,209 @@
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from sqlalchemy import func, and_
from app.models.coupon_activity import (
CouponActivityDB,
CouponActivityCreate,
CouponActivityUpdate,
CouponActivityInfo
)
from app.models.coupon_receive_record import CouponReceiveRecordDB
from app.models.coupon import CouponDB, UserCouponDB, CouponStatus, CouponInfo
from app.models.database import get_db
from app.api.deps import get_current_user, get_admin_user
from app.models.user import UserDB
from app.core.response import success_response, error_response, ResponseModel
from typing import Optional, List
from datetime import datetime, time
router = APIRouter()
@router.post("", response_model=ResponseModel)
async def create_coupon_activity(
activity: CouponActivityCreate,
db: Session = Depends(get_db),
admin: UserDB = Depends(get_admin_user)
):
"""创建优惠券活动(管理员)"""
# 检查优惠券是否存在
for coupon_id in activity.coupon_config.keys():
coupon = db.query(CouponDB).filter(CouponDB.id == coupon_id).first()
if not coupon:
return error_response(code=404, message=f"优惠券ID {coupon_id} 不存在")
# 检查数量是否大于0
if activity.coupon_config[coupon_id] <= 0:
return error_response(code=400, message=f"优惠券ID {coupon_id} 的数量必须大于0")
db_activity = CouponActivityDB(**activity.model_dump())
db.add(db_activity)
try:
db.commit()
db.refresh(db_activity)
return success_response(data=CouponActivityInfo.model_validate(db_activity))
except Exception as e:
db.rollback()
return error_response(code=500, message=f"创建失败: {str(e)}")
@router.get("/{activity_id}", response_model=ResponseModel)
async def get_coupon_activity(
activity_id: int,
db: Session = Depends(get_db)
):
"""获取优惠券活动详情"""
activity = db.query(CouponActivityDB).filter(
CouponActivityDB.id == activity_id
).first()
if not activity:
return error_response(code=404, message="活动不存在")
# 获取活动对应的优惠券
coupons = db.query(CouponDB).filter(
CouponDB.id.in_(activity.coupon_config.keys())
).all()
result = CouponActivityInfo.model_validate(activity)
result.coupons = [CouponInfo.model_validate(coupon) for coupon in coupons]
return success_response(data=result)
@router.get("", response_model=ResponseModel)
async def get_coupon_activities(
is_active: Optional[bool] = None,
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
db: Session = Depends(get_db)
):
"""获取优惠券活动列表"""
query = db.query(CouponActivityDB)
if is_active is not None:
query = query.filter(CouponActivityDB.is_active == is_active)
total = query.count()
activities = query.order_by(CouponActivityDB.create_time.desc())\
.offset(skip)\
.limit(limit)\
.all()
return success_response(data={
"total": total,
"items": [CouponActivityInfo.model_validate(a) for a in activities]
})
@router.post("/{activity_id}/receive", response_model=ResponseModel)
async def receive_coupons(
activity_id: int,
db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user)
):
"""领取优惠券"""
# 查询活动
activity = db.query(CouponActivityDB).filter(
CouponActivityDB.id == activity_id,
CouponActivityDB.is_active == True
).first()
if not activity:
return error_response(code=404, message="活动不存在或已结束")
# 检查领取时间
current_time = datetime.now().time()
if current_time < activity.daily_start_time or current_time > activity.daily_end_time:
return error_response(code=400, message="不在领取时间范围内")
# 检查今日领取次数
today = datetime.now().date()
receive_count = db.query(func.count(CouponReceiveRecordDB.id)).filter(
CouponReceiveRecordDB.user_id == current_user.userid,
CouponReceiveRecordDB.activity_id == activity_id,
CouponReceiveRecordDB.receive_date == today
).scalar()
if receive_count >= activity.daily_limit:
return error_response(code=400, message="今日领取次数已达上限")
try:
# 发放优惠券
for coupon_id, count in activity.coupon_config.items():
coupon = db.query(CouponDB).filter(CouponDB.id == coupon_id).first()
if coupon:
# 循环发放指定数量的优惠券
for _ in range(count):
# 当天晚上12点过期
expire_time = datetime.combine(today, datetime.max.time())
user_coupon = UserCouponDB(
user_id=current_user.userid,
coupon_id=coupon.id,
coupon_name=coupon.name,
coupon_amount=coupon.amount,
expire_time=expire_time,
status=CouponStatus.UNUSED
)
db.add(user_coupon)
# 检查是否领取过优惠券
receive_record = db.query(CouponReceiveRecordDB).filter(
CouponReceiveRecordDB.user_id == current_user.userid,
CouponReceiveRecordDB.activity_id == activity_id,
CouponReceiveRecordDB.receive_date == today
).first()
if receive_record:
receive_record.receive_count += 1
else:
record = CouponReceiveRecordDB(
user_id=current_user.userid,
activity_id=activity_id,
receive_date=today,
receive_count=1
)
db.add(record)
db.commit()
return success_response(message="领取成功")
except Exception as e:
db.rollback()
return error_response(code=500, message=f"领取失败: {str(e)}")
@router.put("/{activity_id}", response_model=ResponseModel)
async def update_coupon_activity(
activity_id: int,
activity: CouponActivityUpdate,
db: Session = Depends(get_db),
admin: UserDB = Depends(get_admin_user)
):
"""更新优惠券活动(管理员)"""
db_activity = db.query(CouponActivityDB).filter(
CouponActivityDB.id == activity_id
).first()
if not db_activity:
return error_response(code=404, message="活动不存在")
# 检查优惠券是否存在
if activity.coupon_config:
for coupon_id, count in activity.coupon_config.items():
coupon = db.query(CouponDB).filter(CouponDB.id == coupon_id).first()
if not coupon:
return error_response(code=404, message=f"优惠券ID {coupon_id} 不存在")
# 检查数量是否大于0
if count <= 0:
return error_response(code=400, message=f"优惠券ID {coupon_id} 的数量必须大于0")
update_data = activity.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(db_activity, key, value)
try:
db.commit()
db.refresh(db_activity)
return success_response(data=CouponActivityInfo.model_validate(db_activity))
except Exception as e:
db.rollback()
return error_response(code=500, message=f"更新失败: {str(e)}")

View File

@ -259,13 +259,19 @@ async def create_order(
"appid": settings.WECHAT_APPID, "appid": settings.WECHAT_APPID,
"path": f"/pages/order/detail/index?id={db_order.orderid}" "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( return success_response(
message="订单创建成功", message="订单创建成功",
data={ data={
"order": OrderInfo.model_validate(db_order), "order": OrderInfo.model_validate(db_order),
"packages": [OrderPackageInfo.model_validate(p) for p in packages], "packages": [OrderPackageInfo.model_validate(p) for p in packages],
"success_text" : settings.ORDER_SUCCESS_TEXT "success_text" : success_text
} }
) )
except Exception as e: except Exception as e:

View File

@ -19,7 +19,8 @@ class Settings(BaseSettings):
ORDER_DELIVERYMAN_SHARE_RATIO: float = 0.8 # 配送员分账比例 ORDER_DELIVERYMAN_SHARE_RATIO: float = 0.8 # 配送员分账比例
#订单创建成功文案 #订单创建成功文案
ORDER_SUCCESS_TEXT: str = "订单预计今晚前送达,请注意查收" ORDER_SUCCESS_TODAY_TEXT: str = "订单预计今晚前送达,请注意查收"
ORDER_SUCCESS_TOMORROW_TEXT: str = "订单预计明晚前送达,请注意查收"
ORDER_PREORDER_PRICE_TEXT: str = "基础费3元 (含5件包裹) 超出部分0.5元/件" ORDER_PREORDER_PRICE_TEXT: str = "基础费3元 (含5件包裹) 超出部分0.5元/件"
# JWT 配置 # JWT 配置

View File

@ -1,6 +1,6 @@
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from app.api.endpoints import wechat,user, address, community, station, order, coupon, community_building, upload, merchant, merchant_product, merchant_order, point, config, merchant_category, log, account,merchant_pay_order, message, bank_card, withdraw, mp, point_product, point_product_order from app.api.endpoints import wechat,user, address, community, station, order, coupon, community_building, upload, merchant, merchant_product, merchant_order, point, config, merchant_category, log, account,merchant_pay_order, message, bank_card, withdraw, mp, point_product, point_product_order, coupon_activity
from app.models.database import Base, engine from app.models.database import Base, engine
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
@ -47,7 +47,8 @@ app.include_router(community.router, prefix="/api/community", tags=["社区"])
app.include_router(community_building.router, prefix="/api/community/building", tags=["社区楼栋"]) app.include_router(community_building.router, prefix="/api/community/building", tags=["社区楼栋"])
app.include_router(station.router, prefix="/api/station", tags=["驿站"]) app.include_router(station.router, prefix="/api/station", tags=["驿站"])
app.include_router(order.router, prefix="/api/order", tags=["订单"]) app.include_router(order.router, prefix="/api/order", tags=["订单"])
app.include_router(coupon.router, prefix="/api/coupon", tags=["服务券"]) app.include_router(coupon.router, prefix="/api/coupon", tags=["抵扣券"])
app.include_router(coupon_activity.router, prefix="/api/coupon-activities", tags=["抵扣券活动"])
app.include_router(merchant.router, prefix="/api/merchant", tags=["商家"]) app.include_router(merchant.router, prefix="/api/merchant", tags=["商家"])
app.include_router(merchant_category.router, prefix="/api/merchant-categories", tags=["商家分类"]) app.include_router(merchant_category.router, prefix="/api/merchant-categories", tags=["商家分类"])
app.include_router(merchant_product.router, prefix="/api/merchant/product", tags=["商家产品"]) app.include_router(merchant_product.router, prefix="/api/merchant/product", tags=["商家产品"])
@ -78,6 +79,7 @@ async def validation_exception_handler(request, exc):
@app.exception_handler(HTTPException) @app.exception_handler(HTTPException)
async def http_exception_handler(request, exc): async def http_exception_handler(request, exc):
logging.exception(f"HTTP异常: {str(exc)}") logging.exception(f"HTTP异常: {str(exc)}")
return CustomJSONResponse( return CustomJSONResponse(
status_code=exc.status_code, status_code=exc.status_code,
content=str(exc) content=str(exc)

View File

@ -0,0 +1,62 @@
from sqlalchemy import Column, Integer, String, DateTime, Boolean, JSON, Time
from sqlalchemy.sql import func
from pydantic import BaseModel, Field
from typing import Optional, List, Dict
from datetime import datetime, time
from .database import Base
class CouponActivityDB(Base):
__tablename__ = "coupon_activities"
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(100), nullable=False) # 活动名称
description = Column(String(500), nullable=True) # 活动描述
start_time = Column(DateTime(timezone=True), nullable=False) # 活动开始时间
end_time = Column(DateTime(timezone=True), nullable=False) # 活动结束时间
daily_start_time = Column(Time, nullable=False) # 每日开始时间
daily_end_time = Column(Time, nullable=False) # 每日结束时间
daily_limit = Column(Integer, nullable=False, default=1) # 每日可领取次数
is_active = Column(Boolean, nullable=False, default=True) # 是否激活
coupon_config = Column(JSON, nullable=False) # 可领取的优惠券配置 {coupon_id: count}
create_time = Column(DateTime(timezone=True), server_default=func.now())
update_time = Column(DateTime(timezone=True), onupdate=func.now())
# Pydantic 模型
class CouponActivityCreate(BaseModel):
name: str = Field(..., max_length=100)
description: Optional[str] = Field(None, max_length=500)
start_time: datetime
end_time: datetime
daily_start_time: time # 每日开始时间 "HH:MM:SS"
daily_end_time: time # 每日结束时间 "HH:MM:SS"
daily_limit: int = Field(..., gt=0)
coupon_config: Dict[int, int] # {coupon_id: count}
is_active: bool = Field(default=True)
class CouponActivityUpdate(BaseModel):
name: Optional[str] = Field(None, max_length=100)
description: Optional[str] = Field(None, max_length=500)
start_time: Optional[datetime] = None
end_time: Optional[datetime] = None
daily_start_time: Optional[time] = None
daily_end_time: Optional[time] = None
daily_limit: Optional[int] = Field(None, gt=0)
coupon_config: Optional[Dict[int, int]] = None
is_active: Optional[bool] = None
class CouponActivityInfo(BaseModel):
id: int
name: str
description: Optional[str]
start_time: datetime
end_time: datetime
daily_start_time: time
daily_end_time: time
daily_limit: int
coupon_config: Dict[int, int]
is_active: bool
create_time: datetime
update_time: Optional[datetime]
class Config:
from_attributes = True

View File

@ -0,0 +1,13 @@
from sqlalchemy import Column, Integer, DateTime, ForeignKey, Date
from sqlalchemy.sql import func
from .database import Base
class CouponReceiveRecordDB(Base):
__tablename__ = "coupon_receive_records"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.userid"), index=True)
activity_id = Column(Integer, ForeignKey("coupon_activities.id"))
receive_date = Column(Date, nullable=False)
receive_count = Column(Integer, nullable=False, default=1) # 领取次数
create_time = Column(DateTime(timezone=True), server_default=func.now())