后端: - 产品价格本模块(型材/玻璃/五金/配件, 供报价取价) - 报价单模块(BJ单号, 手动报价/样板算料/转订单) - 售后单(问题描述必填, 对齐原站校验)与状态流转 - 量尺单(洞口尺寸清单)与阳光房订单(order_type=1) - 个人中心: 资料/密码修改/邀请返利(HMC邀请码) 前端: - 新增8页面: 产品/报价单/售后/量尺/阳光房/商城/使用指南/个人中心 - 登录页注册双Tab, 响应式侧边栏(<900px自动折叠) - Element Plus 图标全局注册修复 - 共15个页面覆盖原站核心业务路由
105 lines
5.0 KiB
Python
105 lines
5.0 KiB
Python
"""订单业务逻辑层:状态机、进度初始化、审计日志。"""
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from huamenchuang.core.deps import ModuleLockedError, NotAuthedError
|
|
from huamenchuang.models.order import DEFAULT_PROCESSES, Order, OrderProcess
|
|
from huamenchuang.models.user import User
|
|
from huamenchuang.repositories.order_repo import OrderRepository
|
|
from huamenchuang.schemas.order import OrderCreate, OrderProcessUpdate, OrderSearch, OrderUpdate
|
|
|
|
|
|
class OrderService:
|
|
def __init__(self, db: AsyncSession) -> None:
|
|
self.db = db
|
|
self.repo = OrderRepository(db)
|
|
|
|
async def create_order(self, user: User, payload: OrderCreate) -> Order:
|
|
"""创建订单:对齐目标站 {contract_id, drawing_data, pic_url} 契约。
|
|
指定 contract_id 时自动快照合同客户信息。"""
|
|
customer_id = payload.customer_id
|
|
customer_name = phone = address = ""
|
|
if payload.contract_id:
|
|
from huamenchuang.repositories.contract_repo import ContractRepository
|
|
contract = await ContractRepository(self.db).get(payload.contract_id, user.company_id)
|
|
if contract is None:
|
|
raise KeyError("关联合同不存在")
|
|
customer_id = contract.customer_id
|
|
customer_name, phone, address = contract.customer_name, contract.phone, contract.address
|
|
order = Order(
|
|
order_no=await self.repo.next_order_no(user.company_id),
|
|
order_type=payload.order_type,
|
|
contract_id=payload.contract_id,
|
|
drawing_data=payload.drawing_data,
|
|
pic_url=payload.pic_url,
|
|
customer_id=customer_id,
|
|
customer_name=customer_name,
|
|
phone=phone,
|
|
address=address,
|
|
note=payload.note,
|
|
money=payload.money,
|
|
count=payload.count,
|
|
area=payload.area,
|
|
status=payload.status,
|
|
company_id=user.company_id,
|
|
user_id=user.id,
|
|
)
|
|
order = await self.repo.create(order)
|
|
# 初始化默认生产进度节点(直接 add,避免触发关系惰性加载)
|
|
for i, name in enumerate(DEFAULT_PROCESSES):
|
|
self.db.add(OrderProcess(order_id=order.id, name=name, status=0, sort=i))
|
|
await self.repo.add_log(order.id, user.id, "create", f"创建订单 {order.order_no}")
|
|
return order
|
|
|
|
async def search(self, user: User, q: OrderSearch) -> tuple[list[Order], int]:
|
|
return await self.repo.search(
|
|
user.company_id, page=q.page, page_size=q.page_size, keyword=q.keyword,
|
|
status=q.status, contract_id=q.contract_id,
|
|
start_date=q.start_date, end_date=q.end_date, order_by=q.order_by,
|
|
)
|
|
|
|
async def get_detail(self, user: User, order_id: int) -> Order:
|
|
order = await self.repo.get(order_id, user.company_id)
|
|
if order is None:
|
|
raise KeyError("订单不存在")
|
|
return order
|
|
|
|
async def update_order(self, user: User, order_id: int, payload: OrderUpdate) -> Order:
|
|
order = await self.get_detail(user, order_id)
|
|
data = payload.model_dump(exclude_none=True, exclude={"action", "target_status"})
|
|
for k, v in data.items():
|
|
setattr(order, k, v)
|
|
if payload.action == "change_status":
|
|
self._transition(order, payload.target_status)
|
|
await self.repo.add_log(order.id, user.id, "change_status",
|
|
f"{order.STATUS_LABELS.get(order.status)}→{order.STATUS_LABELS.get(payload.target_status)}")
|
|
else:
|
|
await self.repo.add_log(order.id, user.id, "update", f"更新字段: {', '.join(data) or '-'}")
|
|
return order
|
|
|
|
@staticmethod
|
|
def _transition(order: Order, target: int | None) -> None:
|
|
"""状态机校验:非法迁移直接拒绝。"""
|
|
if target is None:
|
|
raise ValueError("缺少 target_status")
|
|
if target not in Order.TRANSITIONS.get(order.status, set()):
|
|
raise ValueError(f"不允许的状态迁移: {order.status_label}→{order.STATUS_LABELS.get(target)}")
|
|
order.status = target
|
|
|
|
async def update_process(self, user: User, order_id: int, payload: OrderProcessUpdate) -> OrderProcess:
|
|
order = await self.get_detail(user, order_id)
|
|
proc = await self.repo.upsert_process(order.id, payload.name, payload.status)
|
|
await self.repo.add_log(order.id, user.id, "up-order-process", f"{payload.name}={payload.status}")
|
|
return proc # type: ignore[return-value]
|
|
|
|
async def list_logs(self, user: User, order_id: int) -> list:
|
|
await self.get_detail(user, order_id)
|
|
return await self.repo.list_logs(order_id)
|
|
|
|
async def delete_order(self, user: User, order_id: int) -> None:
|
|
order = await self.get_detail(user, order_id)
|
|
await self.repo.add_log(order.id, user.id, "delete", f"删除订单 {order.order_no}")
|
|
await self.repo.delete(order)
|
|
|
|
|
|
__all__ = ["OrderService", "ModuleLockedError", "NotAuthedError"]
|