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)) # 微信支付交易号 # 退款相关字段 refund_amount = Column(Float, default=0) # 退款金额 refund_reason = Column(String(200), nullable=True) # 退款原因 refund_user_id = Column(Integer, ForeignKey("users.userid"), nullable=True) # 退款操作人 refund_time = Column(DateTime(timezone=True), nullable=True) # 退款时间 @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 delivery_date: Optional[date] = None 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 # 退款相关字段 refund_amount: float = 0 refund_reason: Optional[str] = None refund_user_id: Optional[int] = None refund_time: Optional[datetime] = None 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_amount: 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 # 价格详情文本 class RefundOrderAmountRequest(BaseModel): order_id: str amount: float reason: str