From 91e56c9eb3d112d6e61ec16f0c3b85552751b5c7 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Sun, 19 Jan 2025 15:03:29 +0800 Subject: [PATCH] =?UTF-8?q?1.=20merchant=20=E5=A2=9E=E5=8A=A0=20user=20?= =?UTF-8?q?=E5=BD=92=E5=B1=9E=202.=20=E9=AA=8C=E8=AF=81=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=90=8E=EF=BC=8C=E7=BB=99=E5=AF=B9=E5=BA=94=E7=9A=84=20user?= =?UTF-8?q?=20=E5=A2=9E=E5=8A=A0=E7=BB=93=E7=AE=97=20balance=20=E5=92=8C?= =?UTF-8?q?=20details=203.=20=E9=85=8D=E9=80=81=E8=AE=A2=E5=8D=95=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=90=8E=EF=BC=8C=E6=A0=B9=E6=8D=AE=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E6=94=AF=E4=BB=98=EF=BC=8C=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E5=88=B0=E4=B8=8D=E5=90=8C=E7=9A=84=E7=8A=B6=E6=80=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/endpoints/merchant_order.py | 35 ++++++++++++---- app/api/endpoints/order.py | 14 ++++--- app/core/account.py | 64 +++++++++++++++++++++++++++++ app/models/merchant.py | 3 ++ app/models/user_account.py | 53 ++++++++++++++++++++++++ 5 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 app/core/account.py create mode 100644 app/models/user_account.py diff --git a/app/api/endpoints/merchant_order.py b/app/api/endpoints/merchant_order.py index d493e07..527225d 100644 --- a/app/api/endpoints/merchant_order.py +++ b/app/api/endpoints/merchant_order.py @@ -17,6 +17,7 @@ from app.core.response import success_response, error_response, ResponseModel from datetime import datetime, timezone from app.models.merchant import MerchantDB from app.models.point import PointRecordDB +from app.core.account import AccountManager router = APIRouter() @@ -151,7 +152,18 @@ async def verify_order( current_user: UserDB = Depends(get_current_user) ): """核销订单""" - order = db.query(MerchantOrderDB).filter( + # 查询订单及相关信息 + order = db.query( + MerchantOrderDB, + MerchantProductDB, + MerchantDB + ).join( + MerchantProductDB, + MerchantOrderDB.merchant_product_id == MerchantProductDB.id + ).join( + MerchantDB, + MerchantProductDB.merchant_id == MerchantDB.id + ).filter( MerchantOrderDB.order_id == order_id, MerchantOrderDB.order_verify_code == verify_code, MerchantOrderDB.verify_time.is_(None) # 未核销 @@ -160,16 +172,25 @@ async def verify_order( if not order: return error_response(code=404, message="订单不存在或已核销") - # 更新核销时间和核销用户 - order.verify_time = datetime.now(timezone.utc) - order.verify_user_id = current_user.userid - order.status = MerchantOrderStatus.VERIFIED # 更新为已核销状态 - try: + # 更新核销时间和核销用户 + order.MerchantOrderDB.verify_time = datetime.now(timezone.utc) + order.MerchantOrderDB.verify_user_id = current_user.userid + order.MerchantOrderDB.status = MerchantOrderStatus.VERIFIED + + # 使用账户管理器处理余额变更 + account_manager = AccountManager(db) + settlement_amount = float(order.MerchantProductDB.settlement_amount) + account_manager.change_balance( + user_id=order.MerchantDB.user_id, + amount=settlement_amount, + description=f"{order.MerchantProductDB.name} 订单核销" + ) + db.commit() return success_response( message="核销成功", - data=MerchantOrderInfo.model_validate(order) + data=MerchantOrderInfo.model_validate(order.MerchantOrderDB) ) except Exception as e: db.rollback() diff --git a/app/api/endpoints/order.py b/app/api/endpoints/order.py index 4ad2263..5767486 100644 --- a/app/api/endpoints/order.py +++ b/app/api/endpoints/order.py @@ -426,20 +426,24 @@ async def complete_order( return error_response(code=400, message="只有已接单的订单才能标记为完成") try: - # 更新订单状态和完成图片 - order.status = OrderStatus.COMPLETED + # 根据订单金额决定状态 + if order.final_amount > 0: + order.status = OrderStatus.UNPAID # 需要支付 + else: + order.status = OrderStatus.COMPLETED # 无需支付,直接完成 + + # 保存完成图片 if complete_data.images: order.complete_images = ",".join(complete_data.images) db.commit() return success_response( - message="订单已完成", + message="订单已完成" if order.final_amount == 0 else "请继续支付", data=OrderInfo.model_validate(order) ) except Exception as e: db.rollback() - return error_response(code=500, message=f"完成订单失败: {str(e)}") - + return error_response(code=500, message=f"操作失败: {str(e)}") @router.post("/{orderid}/receive", response_model=ResponseModel) async def receive_order( diff --git a/app/core/account.py b/app/core/account.py new file mode 100644 index 0000000..a1a9313 --- /dev/null +++ b/app/core/account.py @@ -0,0 +1,64 @@ +from sqlalchemy.orm import Session +from app.models.user_account import UserAccountDB, AccountDetailDB, AccountDetailType +from decimal import Decimal + +class AccountManager: + """用户账户管理类""" + + def __init__(self, db: Session): + self.db = db + + def get_or_create_account(self, user_id: int) -> UserAccountDB: + """获取或创建用户账户""" + account = self.db.query(UserAccountDB).filter( + UserAccountDB.user_id == user_id + ).first() + + if not account: + account = UserAccountDB( + user_id=user_id, + balance=0 + ) + self.db.add(account) + self.db.flush() + + return account + + def change_balance(self, user_id: int, amount: float, description: str) -> UserAccountDB: + """ + 变更用户余额 + + Args: + user_id: 用户ID + amount: 变更金额(正数为收入,负数为支出) + description: 变更说明 + + Returns: + 更新后的账户信息 + + Raises: + Exception: 余额不足等异常 + """ + # 转换为 Decimal 以确保精确计算 + decimal_amount = Decimal(str(amount)) + + # 获取或创建账户 + account = self.get_or_create_account(user_id) + + # 检查余额是否充足(支出时) + if decimal_amount < 0 and account.balance + decimal_amount < 0: + raise Exception("账户余额不足") + + # 更新余额 + account.balance += decimal_amount + + # 记录账户明细 + detail = AccountDetailDB( + user_id=user_id, + amount=decimal_amount, + type=AccountDetailType.INCOME if decimal_amount > 0 else AccountDetailType.EXPENSE, + description=description + ) + self.db.add(detail) + + return account \ No newline at end of file diff --git a/app/models/merchant.py b/app/models/merchant.py index 4a32977..85fb10d 100644 --- a/app/models/merchant.py +++ b/app/models/merchant.py @@ -25,6 +25,7 @@ class MerchantDB(Base): __tablename__ = "merchants" id = Column(Integer, primary_key=True, autoincrement=True) + user_id = Column(Integer, ForeignKey("users.userid"), nullable=False) # 归属用户 name = Column(String(100), nullable=False) business_hours = Column(String(100), nullable=False) # 营业时间,如 "09:00-22:00" address = Column(String(200), nullable=False) @@ -49,6 +50,7 @@ class MerchantImage(BaseModel): from_attributes = True class MerchantCreate(BaseModel): + user_id: int name: str = Field(..., max_length=100) business_hours: str = Field(..., max_length=100) address: str = Field(..., max_length=200) @@ -70,6 +72,7 @@ class MerchantUpdate(BaseModel): class MerchantInfo(BaseModel): id: int + user_id: int name: str business_hours: str address: str diff --git a/app/models/user_account.py b/app/models/user_account.py new file mode 100644 index 0000000..a8a6c3a --- /dev/null +++ b/app/models/user_account.py @@ -0,0 +1,53 @@ +from sqlalchemy import Column, Integer, DECIMAL, DateTime, ForeignKey, Enum, String +from sqlalchemy.sql import func +from .database import Base +from pydantic import BaseModel, Field +import enum +from typing import Optional +from datetime import datetime + +class AccountDetailType(str, enum.Enum): + INCOME = "INCOME" # 收入 + EXPENSE = "EXPENSE" # 支出 + +# 用户账户表 +class UserAccountDB(Base): + __tablename__ = "user_accounts" + + id = Column(Integer, primary_key=True, autoincrement=True) + user_id = Column(Integer, ForeignKey("users.userid"), unique=True, nullable=False) + balance = Column(DECIMAL(10, 2), nullable=False, default=0) + create_time = Column(DateTime(timezone=True), server_default=func.now()) + update_time = Column(DateTime(timezone=True), onupdate=func.now()) + +# 账户明细表 +class AccountDetailDB(Base): + __tablename__ = "account_details" + + id = Column(Integer, primary_key=True, autoincrement=True) + user_id = Column(Integer, ForeignKey("users.userid"), nullable=False) + amount = Column(DECIMAL(10, 2), nullable=False) + type = Column(Enum(AccountDetailType), nullable=False) + description = Column(String(200), nullable=False) + create_time = Column(DateTime(timezone=True), server_default=func.now()) + +# Pydantic 模型 +class AccountInfo(BaseModel): + user_id: int + balance: float + create_time: datetime + update_time: Optional[datetime] + + class Config: + from_attributes = True + +class AccountDetailInfo(BaseModel): + id: int + user_id: int + amount: float + type: AccountDetailType + description: str + create_time: datetime + + class Config: + from_attributes = True \ No newline at end of file