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:
@@ -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
|
||||
Reference in New Issue
Block a user