deliveryman-api/app/models/order.py
2025-03-13 23:59:21 +08:00

243 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from datetime import datetime
from typing import Optional, List
from sqlalchemy import Column, String, Integer, Float, DateTime, ForeignKey, Enum, Boolean, Time, Date
from sqlalchemy.sql import func
from pydantic import BaseModel, Field
from .database import Base
import enum
from app.models.user import Gender
from app.core.imageprocessor import process_image, ImageFormat
from datetime import time
from datetime import date
class OrderStatus(str, enum.Enum):
CREATED = "CREATED" # 已创建
CANCELLED = "CANCELLED" # 已取消
RECEIVED = "RECEIVED" # 已接单
DELIVERING = "DELIVERING" # 配送中
UNPAID = "UNPAID" # 未支付
COMPLETED = "COMPLETED" # 已完成
# 订单状态转化成中文
@property
def status_text(self) -> str:
status_map = {
OrderStatus.CREATED: "待接单",
OrderStatus.CANCELLED: "已取消",
OrderStatus.RECEIVED: "已接单",
OrderStatus.DELIVERING: "配送中",
OrderStatus.UNPAID: "待支付",
OrderStatus.COMPLETED: "已完成"
}
return status_map.get(self, "未知状态")
class DeliveryMethod(str, enum.Enum):
DELIVERY_AT_DOORSTEP = "DELIVERY_AT_DOORSTEP" # 放在门口
DELIVERY_TO_ROOM = "DELIVERY_TO_ROOM" # 投递到家
# 数据库模型
class ShippingOrderDB(Base):
__tablename__ = "shipping_orders"
orderid = Column(String(32), primary_key=True)
userid = Column(Integer, ForeignKey("users.userid"), index=True)
# 配送时段ID
time_period_id = Column(Integer, nullable=False, default=0)
time_period_name = Column(String(50), nullable=False, default='')
time_period_from_time = Column(Time, nullable=False, default=time(0, 0, 0))
time_period_to_time = Column(Time, nullable=False, default=time(0, 0, 0))
# 配送地址信息
address_customer_name = Column(String(50), nullable=False, default='') # 客户名称快照
address_customer_phone = Column(String(11), nullable=False, default='') # 客户电话快照
address_customer_gender = Column(Enum(Gender), nullable=False, default=Gender.MALE) # 客户性别快照
address_community_id = Column(Integer, nullable=False)
address_community_name = Column(String(50), nullable=False, default='') # 小区名称快照
address_community_building_id = Column(Integer, nullable=False)
address_community_building_name = Column(String(50), nullable=False, default='') # 楼栋名称快照
address_detail = Column(String(100), nullable=False, default='') # 详细地址快照
# 取件图片
pickup_images = Column(String(2000), nullable=True) # 取件图片URL多个URL用逗号分隔
pickup_images_count = Column(Integer, nullable=False, default=0) # 取件图片数量
pickup_code_count = Column(Integer, nullable=False, default=0) # 取件码数量
delivery_method = Column(Enum(DeliveryMethod), nullable=False)
delivery_date = Column(Date, nullable=False, default=datetime.now().date())
delivery_share = Column(Float, nullable=False,default=0) # 配送费分润
package_count = Column(Integer, nullable=False)
original_amount = Column(Float, nullable=False)
coupon_discount_amount = Column(Float, default=0)
point_discount_amount = Column(Float, default=0)
more_station_price = Column(Float, default=0)
additional_fee_amount = Column(Float, default=0)
coupon_id = Column(Integer, ForeignKey("user_coupons.id"), nullable=True)
final_amount = Column(Float, nullable=False)
status = Column(Enum(OrderStatus), nullable=False, default=OrderStatus.CREATED)
cancel_reason = Column(String(200), nullable=True) # 取消原因
received_time = Column(DateTime(timezone=True), nullable=True) # 接单时间
pickup_time = Column(DateTime(timezone=True), nullable=True) # 取件时间
completed_time = Column(DateTime(timezone=True), nullable=True) # 完成时间
complete_images = Column(String(1000), nullable=True) # 完成订单的图片URL多个URL用逗号分隔
create_time = Column(DateTime(timezone=True), server_default=func.now())
is_first_order = Column(Boolean, default=False) # 新人订单
# 配送员信息
deliveryman_user_id = Column(Integer, ForeignKey("users.userid"), nullable=True)
cancel_user_id = Column(Integer, ForeignKey("users.userid"), nullable=True)
pay_status = Column(Boolean, default=False) # 支付状态
prepay_id = Column(String(64)) # 微信支付预支付ID
pay_time = Column(DateTime(timezone=True)) # 支付时间
transaction_id = Column(String(64)) # 微信支付交易号
@property
def optimized_complete_images(self):
if self.complete_images:
return [process_image(image).format(ImageFormat.WEBP).build() for image in self.complete_images.split(",")]
return []
@property
def optimized_pickup_images(self):
if self.pickup_images:
return [process_image(image).format(ImageFormat.WEBP).build() for image in self.pickup_images.split(",")]
return []
@property
def original_amount_with_additional_fee(self):
return self.original_amount + self.additional_fee_amount
class ShippingOrderPackageDB(Base):
__tablename__ = "shipping_order_packages"
id = Column(Integer, primary_key=True, autoincrement=True)
orderid = Column(String(32), ForeignKey("shipping_orders.orderid"), index=True)
station_id = Column(Integer, nullable=True)
station_name = Column(String(50), nullable=False)
pickup_codes = Column(String(500), nullable=False)
create_time = Column(DateTime(timezone=True), server_default=func.now())
# Pydantic 模型
class OrderPackage(BaseModel):
station_id: Optional[int] = None
station_name: Optional[str] = None
pickup_codes: str = Field(..., max_length=100)
# 先定义 OrderPriceCalculateRequest
class OrderPriceCalculateRequest(BaseModel):
community_id: int = 0
pickup_images: Optional[str] = None
pickup_images_count: int = 0
packages: Optional[List[OrderPackage]] = None
# 然后再定义 OrderCreate
class OrderCreate(BaseModel):
addressid: int
price_request: OrderPriceCalculateRequest
community_time_period_id: int = 0
delivery_date: date = Field(
default=datetime.now().date(),
description="配送日期"
)
delivery_method: DeliveryMethod = Field(
default=DeliveryMethod.DELIVERY_AT_DOORSTEP,
description="配送方式:放在门口或投递到家"
)
class OrderInfo(BaseModel):
orderid: str
userid: int
time_period_id: int
time_period_name: str
time_period_from_time: time
time_period_to_time: time
address_customer_name: str
address_customer_phone: str
address_community_id: int
address_community_name: str
address_community_building_id: int
address_community_building_name: str
address_detail: str
address_customer_gender: Gender
pickup_images: Optional[str] = None
pickup_images_count: int
package_count: int
pickup_code_count: int
original_amount: float = 0
coupon_discount_amount: float = 0
point_discount_amount: float = 0
more_station_price: float = 0
additional_fee_amount: float = 0
coupon_id: Optional[int] = None
final_amount: float = 0
status: OrderStatus
complete_images: Optional[str] = None
optimized_complete_images: Optional[List[str]] = None
create_time: datetime
delivery_method: DeliveryMethod
delivery_date: date
delivery_share: float = 0
deliveryman_user_id: Optional[int] = None
cancel_reason: Optional[str] = None
cancel_user_id: Optional[int] = None
received_time: Optional[datetime] = None
pickup_time: Optional[datetime] = None
completed_time: Optional[datetime] = None
is_first_order: bool
# def __init__(self, **data):
# super().__init__(**data)
# # 将逗号分隔的图片URL字符串转换为列表
# if self.complete_images and isinstance(self.complete_images, str):
# self.complete_images = self.complete_images.split(",")
class Config:
from_attributes = True
class OrderPackageInfo(BaseModel):
id: int
orderid: str
station_id: Optional[int] = None
station_name: str
pickup_codes: str
create_time: datetime
class Config:
from_attributes = True
class OrderPriceInfo(BaseModel):
package_count: int = 0
pickup_images_count: int = 0
pickup_code_count: int = 0
original_amount: float = 0
coupon_discount_amount: float = 0
points_discount_amount: float = 0
more_station_price: float = 0
base_delivery_price: float = 0
coupon_id: Optional[int] = None
final_amount: float = 0
# 添加取消订单请求模型
class OrderCancel(BaseModel):
reason: str = Field(..., max_length=200, description="取消原因")
# 完成订单请求模型
class OrderComplete(BaseModel):
images: Optional[List[str]] = Field(None, max_items=5) # 最多5张图片可选
class OrderPriceResult(BaseModel):
"""订单价格计算结果"""
price_info: OrderPriceInfo
used_coupon_id: Optional[int] = None # 使用的优惠券ID
used_points: Optional[int] = None # 使用的积分数
price_detail_text: Optional[str] = None # 价格详情文本