feat: 订单模块 v0.1 实现与样板二维平面图全量采集

- 后端分层架构: api(Controller)/services(业务)/repositories(数据)/models(ORM)
- 订单模块: 创建(契约对齐目标站{contract_id,drawing_data,pic_url})、搜索、
  详情、更新、删除、状态机(意向单→正式单→生产中→完工/取消,非法迁移拒绝)、
  7节点生产进度、审计日志
- 认证模块: JWT签发(HS256/24h)、PBKDF2密码、注册登录
- 样板库API: 分类树/模板搜索/模板详情, drawing_data 与采集样本同构
- 254张样板二维平面图全部采集并静态挂载 /static/windoor-images/
- 种子脚本: 管理员+分类树+254套样板入库
- 统一响应envelope与付费墙(code=10086)/权限(code=210)语义
- 端到端冒烟测试7/7通过, 更新API.md §12自研接口规范
This commit is contained in:
HHY
2026-09-20 08:16:03 -07:00
parent 7554f2e3c7
commit d3e6de585c
288 changed files with 1835 additions and 9 deletions
@@ -0,0 +1 @@
"""华门窗包。"""
@@ -0,0 +1,98 @@
"""订单数据访问层。"""
import datetime
from sqlalchemy import func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from huamenchuang.models.order import Order, OrderLog, OrderProcess
class OrderRepository:
def __init__(self, db: AsyncSession) -> None:
self.db = db
async def next_order_no(self, company_id: int) -> str:
"""生成订单号:DO+YYYYMMDD+4位流水(同日同企业递增)。"""
today = datetime.date.today().strftime("%Y%m%d")
prefix = f"DO{today}"
stmt = select(Order.order_no).where(
Order.company_id == company_id, Order.order_no.like(f"{prefix}%")
).order_by(Order.order_no.desc()).limit(1)
last = (await self.db.execute(stmt)).scalar_one_or_none()
seq = int(last[-4:]) + 1 if last else 1
return f"{prefix}{seq:04d}"
async def create(self, order: Order) -> Order:
self.db.add(order)
await self.db.flush()
return order
async def get(self, order_id: int, company_id: int) -> Order | None:
stmt = (
select(Order)
.options(selectinload(Order.processes))
.where(Order.id == order_id, Order.company_id == company_id)
)
return (await self.db.execute(stmt)).scalar_one_or_none()
async def search(self, company_id: int, **f) -> tuple[list[Order], int]:
"""按条件分页搜索。f: page,page_size,keyword,status,contract_id,start_date,end_date,order_by"""
conds = [Order.company_id == company_id]
if f.get("status") is not None:
conds.append(Order.status == f["status"])
if f.get("contract_id") is not None:
conds.append(Order.contract_id == f["contract_id"])
if kw := (f.get("keyword") or "").strip():
like = f"%{kw}%"
conds.append(or_(
Order.order_no.like(like),
Order.customer_name.like(like),
Order.phone.like(like),
))
try:
if f.get("start_date"):
conds.append(Order.created_at >= datetime.datetime.fromisoformat(f["start_date"]))
if f.get("end_date"):
conds.append(Order.created_at <= datetime.datetime.fromisoformat(f["end_date"]))
except ValueError:
pass # 日期格式错误时忽略该条件,避免 500
where = *conds,
total = (await self.db.execute(select(func.count()).select_from(Order).where(*where))).scalar_one()
order_map = {
"created_desc": Order.created_at.desc(),
"sort_asc": Order.sort.asc(),
"money_desc": Order.money.desc(),
}
stmt = (
select(Order)
.where(*where)
.order_by(order_map.get(f.get("order_by") or "", Order.created_at.desc()))
.offset((f["page"] - 1) * f["page_size"])
.limit(f["page_size"])
)
items = list((await self.db.execute(stmt)).scalars())
return items, total
async def add_log(self, order_id: int, user_id: int, action: str, detail: str = "") -> None:
self.db.add(OrderLog(order_id=order_id, user_id=user_id, action=action, detail=detail))
async def list_logs(self, order_id: int, limit: int = 50) -> list[OrderLog]:
stmt = select(OrderLog).where(OrderLog.order_id == order_id).order_by(OrderLog.id.desc()).limit(limit)
return list((await self.db.execute(stmt)).scalars())
async def upsert_process(self, order_id: int, name: str, status: int) -> OrderProcess | None:
stmt = select(OrderProcess).where(OrderProcess.order_id == order_id, OrderProcess.name == name)
proc = (await self.db.execute(stmt)).scalar_one_or_none()
if proc is None:
proc = OrderProcess(order_id=order_id, name=name, status=status)
self.db.add(proc)
else:
proc.status = status
if status == 2:
proc.finish_time = datetime.datetime.utcnow()
await self.db.flush()
return proc
async def delete(self, order: Order) -> None:
await self.db.delete(order)
@@ -0,0 +1,19 @@
"""用户数据访问层。"""
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from huamenchuang.models.user import User
class UserRepository:
def __init__(self, db: AsyncSession) -> None:
self.db = db
async def get_by_phone(self, phone_number: str) -> User | None:
stmt = select(User).where(User.phone_number == phone_number)
return (await self.db.execute(stmt)).scalar_one_or_none()
async def create(self, user: User) -> User:
self.db.add(user)
await self.db.flush()
return user