feat(customer): 客户评级并入客户管理并落库
- customers 表新增 rating_score/rating_time(V13 迁移 + V10 SQL + init-dev 同步)
- 新端点 PUT /customers/{id}/rating:评分是档案属性,只存总分与评级时间(五项明细不落库)
- 列表排序 order_by=rating_score,未评分客户升序/降序都垫底,id desc 兜底稳定分页
- 前端 RatingCalculatorDialog 并入客户管理(评分列 + 评级计算按钮),删除独立页与路由
- 测试:评级保存/越界/排序垫底 3 例,test_customers.py 18 passed;schema 同步 216 passed
This commit is contained in:
1 parent
b9781f7d3f
commit
32b7940067
17 files changed
+917
-512
No files matched your search
@@ -52,10 +52,12 @@ def list_customers(session: Session, query: CustomerQuery) -> list[Row]:
|
||||
|
||||
归属人员外键非空但统一用 outerjoin:人员行不可考(被物理删除的脏数据)
|
||||
不应让客户行整个查不出来(同 personnel_repository 的韧性口径)。
|
||||
排序按 id 倒序:客户无排序字段,新增的靠前符合列表页习惯。
|
||||
排序键由查询白名单决定(见 ``CustomerQuery.order_by``):
|
||||
id 倒序 = 新建档靠前,rating_score 按分数排、未评分垫底。
|
||||
"""
|
||||
stmt = _apply_filters(_base_select(), query)
|
||||
stmt = stmt.order_by(Customer.id.desc()).offset(query.offset).limit(query.page_size)
|
||||
stmt = _apply_order(stmt, query)
|
||||
stmt = stmt.offset(query.offset).limit(query.page_size)
|
||||
return list(session.execute(stmt).all())
|
||||
|
||||
|
||||
@@ -131,6 +133,23 @@ def _keyword_clause(keyword: str) -> ColumnElement[bool]:
|
||||
)
|
||||
|
||||
|
||||
def _apply_order(stmt: Select, query: CustomerQuery) -> Select:
|
||||
"""应用排序。排序键已由 schema 白名单收口,此处只做键 → 列的映射。
|
||||
|
||||
MySQL 把 NULL 视为最小值:ASC 时未评分(null)会排最前,但「没打分」
|
||||
不该压在「打了低分」之上 —— ASC 分支用 ``is_(None)`` 布尔键把 null
|
||||
挪到末尾;DESC 分支 null 天然在最后,无需处理。尾部再按 id 倒序兜底,
|
||||
保证同分行的次序稳定(分页不跳行)。
|
||||
"""
|
||||
if query.order_by == "rating_score":
|
||||
score = Customer.rating_score
|
||||
if query.order == "asc":
|
||||
stmt = stmt.order_by(score.is_(None), score.asc())
|
||||
else:
|
||||
stmt = stmt.order_by(score.desc())
|
||||
return stmt.order_by(Customer.id.desc())
|
||||
|
||||
|
||||
# ==================== 写入 ====================
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user