This commit is contained in:
aaron 2025-03-22 09:48:27 +08:00
parent e79785bce6
commit e77c600262
6 changed files with 105 additions and 58 deletions

View File

@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import and_ from sqlalchemy import and_
from typing import List, Optional from typing import List, Optional
from app.models.address import AddressDB, AddressCreate, AddressUpdate, AddressInfo from app.models.address import AddressDB, AddressCreate, AddressUpdate, AddressInfo, AddressType
from app.models.community import CommunityDB from app.models.community import CommunityDB
from app.models.database import get_db from app.models.database import get_db
from app.api.deps import get_current_user from app.api.deps import get_current_user
@ -21,6 +21,7 @@ async def create_address(
if address.is_default: if address.is_default:
db.query(AddressDB).filter( db.query(AddressDB).filter(
and_( and_(
AddressDB.address_type == address.address_type,
AddressDB.user_id == current_user.userid, AddressDB.user_id == current_user.userid,
AddressDB.is_default == True AddressDB.is_default == True
) )
@ -50,6 +51,7 @@ async def create_address(
@router.get("", response_model=ResponseModel) @router.get("", response_model=ResponseModel)
async def get_addresses( async def get_addresses(
community_id: Optional[int] = None, community_id: Optional[int] = None,
address_type : Optional[AddressType] = AddressType.PICKUP,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user) current_user: UserDB = Depends(get_current_user)
): ):
@ -60,6 +62,9 @@ async def get_addresses(
AddressDB.user_id == current_user.userid AddressDB.user_id == current_user.userid
) )
if address_type:
addresses.filter(AddressDB.address_type == address_type)
if community_id is not None: if community_id is not None:
addresses = addresses.filter(AddressDB.community_id == community_id) addresses = addresses.filter(AddressDB.community_id == community_id)
@ -143,12 +148,14 @@ async def delete_address(
@router.post("/{address_id}/set-default", response_model=ResponseModel) @router.post("/{address_id}/set-default", response_model=ResponseModel)
async def set_default_address( async def set_default_address(
address_id: int, address_id: int,
address_type: AddressType = AddressType.PICKUP,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user: UserDB = Depends(get_current_user) current_user: UserDB = Depends(get_current_user)
): ):
"""设置默认地址""" """设置默认地址"""
db.query(AddressDB).filter( db.query(AddressDB).filter(
and_( and_(
AddressDB.address_type == address_type,
AddressDB.user_id == current_user.userid, AddressDB.user_id == current_user.userid,
AddressDB.is_default == True AddressDB.is_default == True
) )

View File

@ -8,6 +8,7 @@ from app.models.merchant_order import (
MerchantOrderInfo, MerchantOrderInfo,
MerchantOrderStatus MerchantOrderStatus
) )
from app.models.address import AddressDB, AddressType
from app.models.merchant_product import MerchantProductDB from app.models.merchant_product import MerchantProductDB
from app.models.database import get_db from app.models.database import get_db
from app.api.deps import get_current_user, get_admin_user, get_merchant_user from app.api.deps import get_current_user, get_admin_user, get_merchant_user
@ -69,12 +70,16 @@ async def create_merchant_order(
order_id = CommonUtils.generate_order_id('M') order_id = CommonUtils.generate_order_id('M')
verify_code = CommonUtils.generate_verify_code() verify_code = CommonUtils.generate_verify_code()
# 创建订单 # 创建订单
pay_amount = float(product.sale_price) pay_amount = float(product.sale_price) * order.qty
db_order = MerchantOrderDB( db_order = MerchantOrderDB(
order_id=order_id, order_id=order_id,
user_id=current_user.userid, user_id=current_user.userid,
merchant_product_id=order.merchant_product_id, merchant_product_id=order.merchant_product_id,
unit_price=product.sale_price,
qty=order.qty,
address_id=order.address_id,
order_amount=pay_amount, order_amount=pay_amount,
pay_amount=pay_amount, pay_amount=pay_amount,
gift_points=int(float(product.sale_price) * (float(product.gift_points_rate) / 100) * settings.POINT_RATIO), gift_points=int(float(product.sale_price) * (float(product.gift_points_rate) / 100) * settings.POINT_RATIO),
@ -285,69 +290,41 @@ async def get_order_detail(
current_user: UserDB = Depends(get_current_user) current_user: UserDB = Depends(get_current_user)
): ):
"""获取订单详情""" """获取订单详情"""
query = db.query( order = db.query(MerchantOrderDB).filter(
MerchantOrderDB,
MerchantProductDB.name.label('product_name'),
MerchantProductDB.image_url.label('product_image'),
MerchantProductDB.tags.label('product_tags'),
MerchantDB.name.label('merchant_name'),
MerchantDB.latitude.label('merchant_latitude'),
MerchantDB.longitude.label('merchant_longitude'),
MerchantDB.phone.label('merchant_phone')
)
if longitude is not None and latitude is not None:
query = query.add_columns(
text("ST_Distance_Sphere(point(merchants.longitude, merchants.latitude), "
"point(:lon, :lat)) as distance").params(lon=longitude, lat=latitude)
)
else:
query = query.add_columns(text("NULL as distance"))
order = query.join(
MerchantProductDB,
MerchantOrderDB.merchant_product_id == MerchantProductDB.id
).join(
MerchantDB,
MerchantProductDB.merchant_id == MerchantDB.id
).filter(
MerchantOrderDB.order_id == order_id MerchantOrderDB.order_id == order_id
).first() ).first()
if not order: if not order:
return error_response(code=404, message="订单不存在") return error_response(code=404, message="订单不存在")
# 检查权限 product = db.query(MerchantProductDB).filter(
if order.MerchantOrderDB.user_id != current_user.userid: MerchantProductDB.id == order.merchant_product_id
return error_response(code=403, message="无权查看此订单") ).first()
if not product:
return error_response(code=404, message="商品不存在")
merchant = db.query(MerchantDB).filter(
MerchantDB.id == product.merchant_id
).first()
if not merchant:
return error_response(code=404, message="商家不存在")
# 构建返回数据
order_data = { order_data = {
"id": order.MerchantOrderDB.id, **order.model_dump(),
"order_id": order.MerchantOrderDB.order_id, **product.model_dump(),
"user_id": order.MerchantOrderDB.user_id, **merchant.model_dump()
"merchant_product_id": order.MerchantOrderDB.merchant_product_id,
"order_amount": order.MerchantOrderDB.order_amount,
"status": order.MerchantOrderDB.status,
"order_verify_code": order.MerchantOrderDB.order_verify_code,
"verify_time": order.MerchantOrderDB.verify_time,
"verify_user_id": order.MerchantOrderDB.verify_user_id,
"create_time": order.MerchantOrderDB.create_time,
"update_time": order.MerchantOrderDB.update_time,
# 商品信息
"product_name": order.product_name,
"product_tags": order.product_tags,
"product_image": process_image(order.product_image).thumbnail(width=800, height=800).format(ImageFormat.WEBP).build(),
# 商家信息
"merchant_name": order.merchant_name,
"merchant_latitude": order.merchant_latitude,
"merchant_longitude": order.merchant_longitude,
"merchant_phone": order.merchant_phone,
# 距离信息
"distance": round(order[8]) if order[8] else None
} }
if order.address_id:
address = db.query(AddressDB).filter(
AddressDB.id == order.address_id
).first()
order_data["address"] = address.model_dump()
return success_response(data=order_data) return success_response(data=order_data)
@router.post("/calculate-price", response_model=ResponseModel) @router.post("/calculate-price", response_model=ResponseModel)

View File

@ -1,9 +1,16 @@
from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean, Enum from sqlalchemy import Column, Integer, String, ForeignKey, DateTime, Boolean, Enum
from sqlalchemy.sql import func from sqlalchemy.sql import func
from sqlalchemy.dialects.mysql import DECIMAL
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from .database import Base from .database import Base
from typing import Optional from typing import Optional
from app.models.user import Gender # 复用用户模型中的性别枚举 from app.models.user import Gender # 复用用户模型中的性别枚举
import enum
# 地址类型枚举
class AddressType(str, enum.Enum):
PICKUP = "PICKUP" # 代取地址
COMMON = "COMMON" # 通用地址
# 数据库模型 # 数据库模型
class AddressDB(Base): class AddressDB(Base):
@ -20,6 +27,9 @@ class AddressDB(Base):
phone = Column(String(11)) phone = Column(String(11))
gender = Column(Enum(Gender), nullable=False, default=Gender.UNKNOWN) gender = Column(Enum(Gender), nullable=False, default=Gender.UNKNOWN)
is_default = Column(Boolean, default=False) is_default = Column(Boolean, default=False)
address_type = Column(Enum(AddressType), nullable=False, default=AddressType.PICKUP) # 地址类型
longitude = Column(DECIMAL(9,6), nullable=True) # 经度精确到小数点后6位
latitude = Column(DECIMAL(9,6), nullable=True) # 纬度精确到小数点后6位
create_time = Column(DateTime(timezone=True), server_default=func.now()) create_time = Column(DateTime(timezone=True), server_default=func.now())
update_time = Column(DateTime(timezone=True), onupdate=func.now()) update_time = Column(DateTime(timezone=True), onupdate=func.now())
@ -32,6 +42,9 @@ class AddressCreate(BaseModel):
phone: str = Field(..., pattern="^1[3-9]\d{9}$") phone: str = Field(..., pattern="^1[3-9]\d{9}$")
gender: Gender = Gender.UNKNOWN gender: Gender = Gender.UNKNOWN
is_default: bool = True is_default: bool = True
address_type: AddressType = AddressType.PICKUP # 地址类型,默认为代取
longitude: Optional[float] = Field(None, ge=-180, le=180) # 经度
latitude: Optional[float] = Field(None, ge=-90, le=90) # 纬度
class AddressUpdate(BaseModel): class AddressUpdate(BaseModel):
community_id: Optional[int] = None community_id: Optional[int] = None
@ -41,6 +54,9 @@ class AddressUpdate(BaseModel):
phone: Optional[str] = Field(None, pattern="^1[3-9]\d{9}$") phone: Optional[str] = Field(None, pattern="^1[3-9]\d{9}$")
gender: Optional[Gender] = None gender: Optional[Gender] = None
is_default: Optional[bool] = None is_default: Optional[bool] = None
address_type: Optional[AddressType] = None # 地址类型
longitude: Optional[float] = Field(None, ge=-180, le=180) # 经度
latitude: Optional[float] = Field(None, ge=-90, le=90) # 纬度
class AddressInfo(BaseModel): class AddressInfo(BaseModel):
id: int id: int
@ -53,6 +69,9 @@ class AddressInfo(BaseModel):
phone: str phone: str
gender: Gender gender: Gender
is_default: bool is_default: bool
address_type: AddressType # 地址类型
longitude: Optional[float] = None # 经度
latitude: Optional[float] = None # 纬度
class Config: class Config:
from_attributes = True from_attributes = True

View File

@ -24,6 +24,9 @@ class MerchantOrderDB(Base):
qrcode_url = Column(String(200)) # 核销码二维码图片地址 qrcode_url = Column(String(200)) # 核销码二维码图片地址
user_id = Column(Integer, ForeignKey("users.userid"), nullable=False) user_id = Column(Integer, ForeignKey("users.userid"), nullable=False)
merchant_product_id = Column(Integer, ForeignKey("merchant_products.id"), nullable=False) merchant_product_id = Column(Integer, ForeignKey("merchant_products.id"), nullable=False)
address_id = Column(Integer, ForeignKey("delivery_addresses.id"), nullable=False) # 收货地址ID
qty = Column(Integer, nullable=False, default=1) # 购买数量
unit_price = Column(DECIMAL(10,2), nullable=False) # 产品单价
order_amount = Column(DECIMAL(10,2), nullable=False) order_amount = Column(DECIMAL(10,2), nullable=False)
pay_amount = Column(DECIMAL(10,2), nullable=False, default=0) pay_amount = Column(DECIMAL(10,2), nullable=False, default=0)
gift_points = Column(Integer, nullable=False, default=0) # 赠送的积分 gift_points = Column(Integer, nullable=False, default=0) # 赠送的积分
@ -39,16 +42,23 @@ class MerchantOrderDB(Base):
refund_transaction_id = Column(String(64)) # 微信退款交易号 refund_transaction_id = Column(String(64)) # 微信退款交易号
refund_time = Column(DateTime(timezone=True), nullable=True) refund_time = Column(DateTime(timezone=True), nullable=True)
class MerchantOrderCreate(BaseModel): class MerchantOrderCreate(BaseModel):
merchant_product_id: int merchant_product_id: int
qty: int = Field(..., gt=0) # 购买数量必须大于0
address_id: int # 收货地址ID
class MerchantOrderVerify(BaseModel): class MerchantOrderVerify(BaseModel):
verify_code: str verify_code: str
class MerchantOrderInfo(BaseModel): class MerchantOrderInfo(BaseModel):
id: int id: int
order_id: str order_id: str
user_id: int user_id: int
merchant_product_id: int merchant_product_id: int
address_id: int # 收货地址ID
qty: int # 购买数量
unit_price: float # 产品单价
order_amount: float order_amount: float
pay_amount: float pay_amount: float
gift_points: int gift_points: int

View File

@ -53,3 +53,37 @@ ADD COLUMN latitude DECIMAL(9,6) COMMENT '纬度精确到小数点后6位';
-- 为merchant_products表添加已售数量字段 -- 为merchant_products表添加已售数量字段
ALTER TABLE merchant_products ALTER TABLE merchant_products
ADD COLUMN sold_total INT NOT NULL DEFAULT 0 COMMENT '已售数量'; ADD COLUMN sold_total INT NOT NULL DEFAULT 0 COMMENT '已售数量';
-- 为delivery_addresses表添加地址类型和经纬度字段
ALTER TABLE delivery_addresses
ADD COLUMN address_type ENUM('PICKUP', 'COMMON') NOT NULL DEFAULT 'PICKUP' COMMENT '地址类型:代取或通用',
ADD COLUMN longitude DECIMAL(9,6) COMMENT '经度精确到小数点后6位',
ADD COLUMN latitude DECIMAL(9,6) COMMENT '纬度精确到小数点后6位';
-- 添加address_id字段先不添加外键约束
ALTER TABLE merchant_orders
ADD COLUMN address_id INT COMMENT '收货地址ID' AFTER merchant_product_id;
-- 添加qty字段
ALTER TABLE merchant_orders
ADD COLUMN qty INT NOT NULL DEFAULT 1 COMMENT '购买数量' AFTER address_id;
-- 添加unit_price字段
ALTER TABLE merchant_orders
ADD COLUMN unit_price DECIMAL(10,2) NOT NULL COMMENT '产品单价' AFTER qty;
-- 更新现有记录的address_id为NULL
UPDATE merchant_orders
SET address_id = NULL
WHERE address_id IS NOT NULL;
-- 添加外键约束
ALTER TABLE merchant_orders
ADD CONSTRAINT fk_merchant_orders_address
FOREIGN KEY (address_id) REFERENCES delivery_addresses(id);
-- 修改address_id为NOT NULL
ALTER TABLE merchant_orders
MODIFY COLUMN address_id INT NOT NULL COMMENT '收货地址ID';

Binary file not shown.