添加积分商品和积分商品订单。
This commit is contained in:
parent
553f8b2e0c
commit
ecdc84822f
@ -627,6 +627,10 @@ async def get_deliveryman_orders(
|
|||||||
if status:
|
if status:
|
||||||
statuses = status.split(",")
|
statuses = status.split(",")
|
||||||
query = query.filter(ShippingOrderDB.status.in_(statuses))
|
query = query.filter(ShippingOrderDB.status.in_(statuses))
|
||||||
|
|
||||||
|
# 如果订单状态不是待接单,则需要过滤快递员
|
||||||
|
if OrderStatus.CREATED not in statuses:
|
||||||
|
query = query.filter(ShippingOrderDB.deliveryman_user_id == deliveryman.userid)
|
||||||
|
|
||||||
# 楼栋筛选
|
# 楼栋筛选
|
||||||
if building_id:
|
if building_id:
|
||||||
|
|||||||
100
app/api/endpoints/point_product.py
Normal file
100
app/api/endpoints/point_product.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
from fastapi import APIRouter, Depends, Query
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from app.models.point_product import (
|
||||||
|
PointProductDB,
|
||||||
|
PointProductCreate,
|
||||||
|
PointProductUpdate,
|
||||||
|
PointProductInfo
|
||||||
|
)
|
||||||
|
from app.models.database import get_db
|
||||||
|
from app.api.deps import get_admin_user
|
||||||
|
from app.models.user import UserDB
|
||||||
|
from app.core.response import success_response, error_response, ResponseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post("", response_model=ResponseModel)
|
||||||
|
async def create_point_product(
|
||||||
|
product: PointProductCreate,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
admin: UserDB = Depends(get_admin_user)
|
||||||
|
):
|
||||||
|
"""创建积分商品(管理员)"""
|
||||||
|
db_product = PointProductDB(**product.model_dump())
|
||||||
|
db.add(db_product)
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_product)
|
||||||
|
return success_response(data=PointProductInfo.model_validate(db_product))
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
return error_response(code=500, message=f"创建失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.get("", response_model=ResponseModel)
|
||||||
|
async def get_point_products(
|
||||||
|
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(PointProductDB)
|
||||||
|
|
||||||
|
if is_active is not None:
|
||||||
|
query = query.filter(PointProductDB.is_active == is_active)
|
||||||
|
|
||||||
|
total = query.count()
|
||||||
|
|
||||||
|
products = query.order_by(PointProductDB.create_time.desc())\
|
||||||
|
.offset(skip)\
|
||||||
|
.limit(limit)\
|
||||||
|
.all()
|
||||||
|
|
||||||
|
return success_response(data={
|
||||||
|
"total": total,
|
||||||
|
"items": [PointProductInfo.model_validate(p) for p in products]
|
||||||
|
})
|
||||||
|
|
||||||
|
@router.get("/{product_id}", response_model=ResponseModel)
|
||||||
|
async def get_point_product(
|
||||||
|
product_id: int,
|
||||||
|
db: Session = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""获取积分商品详情"""
|
||||||
|
product = db.query(PointProductDB).filter(
|
||||||
|
PointProductDB.id == product_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not product:
|
||||||
|
return error_response(code=404, message="商品不存在")
|
||||||
|
|
||||||
|
return success_response(data=PointProductInfo.model_validate(product))
|
||||||
|
|
||||||
|
@router.put("/{product_id}", response_model=ResponseModel)
|
||||||
|
async def update_point_product(
|
||||||
|
product_id: int,
|
||||||
|
product: PointProductUpdate,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
admin: UserDB = Depends(get_admin_user)
|
||||||
|
):
|
||||||
|
"""更新积分商品(管理员)"""
|
||||||
|
db_product = db.query(PointProductDB).filter(
|
||||||
|
PointProductDB.id == product_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not db_product:
|
||||||
|
return error_response(code=404, message="商品不存在")
|
||||||
|
|
||||||
|
update_data = product.model_dump(exclude_unset=True)
|
||||||
|
for key, value in update_data.items():
|
||||||
|
setattr(db_product, key, value)
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_product)
|
||||||
|
return success_response(data=PointProductInfo.model_validate(db_product))
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
return error_response(code=500, message=f"更新失败: {str(e)}")
|
||||||
172
app/api/endpoints/point_product_order.py
Normal file
172
app/api/endpoints/point_product_order.py
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
from fastapi import APIRouter, Depends, Query
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from app.models.point_product_order import (
|
||||||
|
PointProductOrderDB,
|
||||||
|
PointProductOrderCreate,
|
||||||
|
PointProductOrderUpdate,
|
||||||
|
PointProductOrderInfo,
|
||||||
|
PointProductOrderStatus
|
||||||
|
)
|
||||||
|
from app.models.point_product import PointProductDB
|
||||||
|
from app.models.database import get_db
|
||||||
|
from app.api.deps import get_current_user, get_admin_user, get_deliveryman_user
|
||||||
|
from app.models.user import UserDB
|
||||||
|
from app.core.response import success_response, error_response, ResponseModel
|
||||||
|
from app.core.point_manager import PointManager
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.post("", response_model=ResponseModel)
|
||||||
|
async def create_point_product_order(
|
||||||
|
order: PointProductOrderCreate,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""创建积分商品订单"""
|
||||||
|
# 查询商品信息
|
||||||
|
product = db.query(PointProductDB).filter(
|
||||||
|
PointProductDB.id == order.product_id,
|
||||||
|
PointProductDB.is_active == True
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not product:
|
||||||
|
return error_response(code=404, message="商品不存在或已下架")
|
||||||
|
|
||||||
|
# 计算所需总积分
|
||||||
|
total_points = product.point_amount * order.quantity
|
||||||
|
|
||||||
|
# 检查用户积分是否足够
|
||||||
|
if current_user.points < total_points:
|
||||||
|
return error_response(code=400, message="积分不足")
|
||||||
|
|
||||||
|
# 创建订单
|
||||||
|
db_order = PointProductOrderDB(
|
||||||
|
orderid=PointProductOrderDB.generate_order_id(),
|
||||||
|
user_id=current_user.userid,
|
||||||
|
product_id=product.id,
|
||||||
|
product_name=product.name,
|
||||||
|
product_image=product.product_image,
|
||||||
|
product_description=product.description,
|
||||||
|
product_point_amount=product.point_amount,
|
||||||
|
quantity=order.quantity,
|
||||||
|
order_point_amount=total_points
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 扣减用户积分
|
||||||
|
point_manager = PointManager(db)
|
||||||
|
point_manager.deduct_points(
|
||||||
|
user_id=current_user.userid,
|
||||||
|
points=total_points,
|
||||||
|
description=f"兑换商品: {product.name}",
|
||||||
|
order_id=db_order.orderid
|
||||||
|
)
|
||||||
|
|
||||||
|
# 保存订单
|
||||||
|
db.add(db_order)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_order)
|
||||||
|
|
||||||
|
return success_response(data=PointProductOrderInfo.model_validate(db_order))
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
return error_response(code=500, message=f"创建订单失败: {str(e)}")
|
||||||
|
|
||||||
|
@router.get("", response_model=ResponseModel)
|
||||||
|
async def get_point_product_orders(
|
||||||
|
status: Optional[PointProductOrderStatus] = None,
|
||||||
|
skip: int = Query(0, ge=0),
|
||||||
|
limit: int = Query(20, ge=1, le=100),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""获取积分商品订单列表"""
|
||||||
|
query = db.query(PointProductOrderDB).filter(
|
||||||
|
PointProductOrderDB.user_id == current_user.userid
|
||||||
|
)
|
||||||
|
|
||||||
|
if status:
|
||||||
|
query = query.filter(PointProductOrderDB.status == status)
|
||||||
|
|
||||||
|
total = query.count()
|
||||||
|
|
||||||
|
orders = query.order_by(PointProductOrderDB.create_time.desc())\
|
||||||
|
.offset(skip)\
|
||||||
|
.limit(limit)\
|
||||||
|
.all()
|
||||||
|
|
||||||
|
return success_response(data={
|
||||||
|
"total": total,
|
||||||
|
"items": [PointProductOrderInfo.model_validate(o) for o in orders]
|
||||||
|
})
|
||||||
|
|
||||||
|
@router.get("/{orderid}", response_model=ResponseModel)
|
||||||
|
async def get_point_product_order(
|
||||||
|
orderid: str,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current_user: UserDB = Depends(get_current_user)
|
||||||
|
):
|
||||||
|
"""获取积分商品订单详情"""
|
||||||
|
order = db.query(PointProductOrderDB).filter(
|
||||||
|
PointProductOrderDB.orderid == orderid,
|
||||||
|
PointProductOrderDB.user_id == current_user.userid
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not order:
|
||||||
|
return error_response(code=404, message="订单不存在")
|
||||||
|
|
||||||
|
return success_response(data=PointProductOrderInfo.model_validate(order))
|
||||||
|
|
||||||
|
|
||||||
|
# 接受订单
|
||||||
|
@router.post("/{orderid}/accept", response_model=ResponseModel)
|
||||||
|
async def accept_point_product_order(
|
||||||
|
orderid: str,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
deliveryman: UserDB = Depends(get_deliveryman_user)
|
||||||
|
):
|
||||||
|
order = db.query(PointProductOrderDB).filter(
|
||||||
|
PointProductOrderDB.orderid == orderid,
|
||||||
|
PointProductOrderDB.status == PointProductOrderStatus.PENDING
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not order:
|
||||||
|
return error_response(code=404, message="订单不存在")
|
||||||
|
|
||||||
|
if order.status != PointProductOrderStatus.PENDING:
|
||||||
|
return error_response(code=400, message="订单状态不正确")
|
||||||
|
|
||||||
|
# 更新订单状态
|
||||||
|
order.status = PointProductOrderStatus.CONFIRMED
|
||||||
|
db.commit()
|
||||||
|
db.refresh(order)
|
||||||
|
|
||||||
|
return success_response(data=PointProductOrderInfo.model_validate(order))
|
||||||
|
|
||||||
|
# 拒绝订单
|
||||||
|
@router.post("/{orderid}/reject", response_model=ResponseModel)
|
||||||
|
async def reject_point_product_order(
|
||||||
|
orderid: str,
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
deliveryman: UserDB = Depends(get_deliveryman_user)
|
||||||
|
):
|
||||||
|
order = db.query(PointProductOrderDB).filter(
|
||||||
|
PointProductOrderDB.orderid == orderid,
|
||||||
|
PointProductOrderDB.status == PointProductOrderStatus.PENDING
|
||||||
|
).first()
|
||||||
|
|
||||||
|
if not order:
|
||||||
|
return error_response(code=404, message="订单不存在")
|
||||||
|
|
||||||
|
if order.status != PointProductOrderStatus.PENDING:
|
||||||
|
return error_response(code=400, message="订单状态不正确")
|
||||||
|
|
||||||
|
# 更新订单状态
|
||||||
|
order.status = PointProductOrderStatus.CANCELLED
|
||||||
|
db.commit()
|
||||||
|
db.refresh(order)
|
||||||
|
|
||||||
|
return success_response(data=PointProductOrderInfo.model_validate(order))
|
||||||
|
|
||||||
@ -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
|
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.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
|
||||||
@ -39,6 +39,8 @@ app.include_router(user.router, prefix="/api/user", tags=["用户"])
|
|||||||
app.include_router(bank_card.router, prefix="/api/bank-cards", tags=["用户银行卡"])
|
app.include_router(bank_card.router, prefix="/api/bank-cards", tags=["用户银行卡"])
|
||||||
app.include_router(withdraw.router, prefix="/api/withdraw", tags=["提现"])
|
app.include_router(withdraw.router, prefix="/api/withdraw", tags=["提现"])
|
||||||
app.include_router(point.router, prefix="/api/point", tags=["用户积分"])
|
app.include_router(point.router, prefix="/api/point", tags=["用户积分"])
|
||||||
|
app.include_router(point_product.router, prefix="/api/point-products", tags=["积分商品"])
|
||||||
|
app.include_router(point_product_order.router, prefix="/api/point-product-orders", tags=["积分商品订单"])
|
||||||
app.include_router(account.router, prefix="/api/account", tags=["账户"])
|
app.include_router(account.router, prefix="/api/account", tags=["账户"])
|
||||||
app.include_router(address.router, prefix="/api/address", tags=["配送地址"])
|
app.include_router(address.router, prefix="/api/address", tags=["配送地址"])
|
||||||
app.include_router(community.router, prefix="/api/community", tags=["社区"])
|
app.include_router(community.router, prefix="/api/community", tags=["社区"])
|
||||||
|
|||||||
58
app/models/point_product.py
Normal file
58
app/models/point_product.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String, DateTime, Boolean, JSON
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional, List
|
||||||
|
from datetime import datetime
|
||||||
|
from .database import Base
|
||||||
|
from app.core.imageprocessor import process_image, ImageFormat
|
||||||
|
|
||||||
|
class PointProductDB(Base):
|
||||||
|
__tablename__ = "point_products"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
name = Column(String(100), nullable=False)
|
||||||
|
product_image = Column(String(200), nullable=False)
|
||||||
|
tags = Column(String(100), nullable=True) # 商品标签,JSON数组
|
||||||
|
description = Column(String(500), nullable=False)
|
||||||
|
point_amount = Column(Integer, nullable=False) # 所需积分
|
||||||
|
is_active = Column(Boolean, nullable=False, default=True) # 是否上架
|
||||||
|
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def optimized_product_image(self):
|
||||||
|
"""获取优化后的商品图片"""
|
||||||
|
if self.product_image:
|
||||||
|
return process_image(self.product_image).thumbnail(400, 400).format(ImageFormat.WEBP).build()
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Pydantic 模型
|
||||||
|
class PointProductCreate(BaseModel):
|
||||||
|
name: str = Field(..., max_length=100)
|
||||||
|
product_image: str = Field(..., max_length=200)
|
||||||
|
tags: Optional[str] = None
|
||||||
|
description: str = Field(..., max_length=500)
|
||||||
|
point_amount: int = Field(..., gt=0)
|
||||||
|
is_active: bool = Field(default=True)
|
||||||
|
|
||||||
|
class PointProductUpdate(BaseModel):
|
||||||
|
name: Optional[str] = Field(None, max_length=100)
|
||||||
|
product_image: Optional[str] = Field(None, max_length=200)
|
||||||
|
tags: Optional[List[str]] = None
|
||||||
|
description: Optional[str] = Field(None, max_length=500)
|
||||||
|
point_amount: Optional[int] = Field(None, gt=0)
|
||||||
|
is_active: Optional[bool] = None
|
||||||
|
|
||||||
|
class PointProductInfo(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
product_image: str
|
||||||
|
optimized_product_image: Optional[str]
|
||||||
|
tags: Optional[str]
|
||||||
|
description: str
|
||||||
|
point_amount: int
|
||||||
|
is_active: bool
|
||||||
|
create_time: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
73
app/models/point_product_order.py
Normal file
73
app/models/point_product_order.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Enum, Float
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
from .database import Base
|
||||||
|
import enum
|
||||||
|
from app.core.utils import CommonUtils
|
||||||
|
|
||||||
|
class PointProductOrderStatus(str, enum.Enum):
|
||||||
|
PENDING = "PENDING" # 待确认
|
||||||
|
CONFIRMED = "CONFIRMED" # 已确认
|
||||||
|
CANCELLED = "CANCELLED" # 已取消
|
||||||
|
DELIVERED = "DELIVERED" # 已送达
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status_text(self) -> str:
|
||||||
|
status_map = {
|
||||||
|
PointProductOrderStatus.PENDING: "待确认",
|
||||||
|
PointProductOrderStatus.CONFIRMED: "已确认",
|
||||||
|
PointProductOrderStatus.CANCELLED: "已取消",
|
||||||
|
PointProductOrderStatus.DELIVERED: "已送达"
|
||||||
|
}
|
||||||
|
return status_map.get(self, "未知状态")
|
||||||
|
|
||||||
|
class PointProductOrderDB(Base):
|
||||||
|
__tablename__ = "point_product_orders"
|
||||||
|
|
||||||
|
orderid = Column(String(32), primary_key=True)
|
||||||
|
user_id = Column(Integer, ForeignKey("users.userid"), index=True)
|
||||||
|
delivery_order_id = Column(String(32), ForeignKey("shipping_orders.orderid"), nullable=True)
|
||||||
|
product_id = Column(Integer, ForeignKey("point_products.id"))
|
||||||
|
product_name = Column(String(100), nullable=False)
|
||||||
|
product_image = Column(String(200), nullable=False)
|
||||||
|
product_description = Column(String(500), nullable=False)
|
||||||
|
product_point_amount = Column(Integer, nullable=False) # 单件商品所需积分
|
||||||
|
quantity = Column(Integer, nullable=False, default=1)
|
||||||
|
order_point_amount = Column(Integer, nullable=False) # 订单总积分
|
||||||
|
status = Column(Enum(PointProductOrderStatus), nullable=False, default=PointProductOrderStatus.PENDING)
|
||||||
|
create_time = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
update_time = Column(DateTime(timezone=True), onupdate=func.now())
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_order_id() -> str:
|
||||||
|
"""生成订单号"""
|
||||||
|
return CommonUtils.generate_order_id("P") # P代表积分商品订单
|
||||||
|
|
||||||
|
# Pydantic 模型
|
||||||
|
class PointProductOrderCreate(BaseModel):
|
||||||
|
product_id: int
|
||||||
|
quantity: int = Field(default=1, gt=0)
|
||||||
|
|
||||||
|
class PointProductOrderUpdate(BaseModel):
|
||||||
|
status: PointProductOrderStatus
|
||||||
|
delivery_order_id: Optional[str] = None
|
||||||
|
|
||||||
|
class PointProductOrderInfo(BaseModel):
|
||||||
|
orderid: str
|
||||||
|
user_id: int
|
||||||
|
delivery_order_id: Optional[str]
|
||||||
|
product_id: int
|
||||||
|
product_name: str
|
||||||
|
product_image: str
|
||||||
|
product_description: str
|
||||||
|
product_point_amount: int
|
||||||
|
quantity: int
|
||||||
|
order_point_amount: int
|
||||||
|
status: PointProductOrderStatus
|
||||||
|
create_time: datetime
|
||||||
|
update_time: Optional[datetime]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
Loading…
Reference in New Issue
Block a user