添加积分商品和积分商品订单。

This commit is contained in:
aaron 2025-02-20 14:25:37 +08:00
parent 553f8b2e0c
commit ecdc84822f
6 changed files with 410 additions and 1 deletions

View File

@ -627,6 +627,10 @@ async def get_deliveryman_orders(
if status:
statuses = status.split(",")
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:

View 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)}")

View 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))

View File

@ -1,6 +1,6 @@
from fastapi import FastAPI
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 fastapi.exceptions import RequestValidationError
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(withdraw.router, prefix="/api/withdraw", 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(address.router, prefix="/api/address", tags=["配送地址"])
app.include_router(community.router, prefix="/api/community", tags=["社区"])

View 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

View 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