增加优惠券活动接口
This commit is contained in:
parent
683aa8e2c2
commit
3dc82e0986
209
app/api/endpoints/coupon_activity.py
Normal file
209
app/api/endpoints/coupon_activity.py
Normal 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)}")
|
||||||
@ -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:
|
||||||
|
|||||||
@ -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 配置
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
62
app/models/coupon_activity.py
Normal file
62
app/models/coupon_activity.py
Normal 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
|
||||||
13
app/models/coupon_receive_record.py
Normal file
13
app/models/coupon_receive_record.py
Normal 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())
|
||||||
Loading…
Reference in New Issue
Block a user