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:
liuwenxin committed 2026-10-23 10:59:33 +08:00
1 parent b9781f7d3f
commit 32b7940067
17 files changed
+917 -512

No files matched your search

+51
View File
@@ -15,6 +15,7 @@
import logging
from dataclasses import dataclass
from datetime import datetime
from sqlalchemy import Row
from sqlalchemy.exc import IntegrityError
@@ -38,6 +39,7 @@ from app.schemas.customer import (
CustomerOut,
CustomerOwnershipIn,
CustomerQuery,
CustomerRatingIn,
CustomerUpdateIn,
OwnershipChangeLogOut,
)
@@ -61,6 +63,8 @@ def _to_out(customer: Customer, owner_name: str | None) -> CustomerOut:
owner_personnel_id=customer.owner_personnel_id,
owner_personnel_name=owner_name,
remark=customer.remark,
rating_score=customer.rating_score,
rating_time=customer.rating_time,
)
@@ -263,6 +267,53 @@ def update_customer(
return out
def rate_customer(
session: Session,
customer_id: int,
payload: CustomerRatingIn,
*,
operator: User,
meta: RequestMeta | None = None,
) -> CustomerOut:
"""保存客户评级(评级计算器「保存评分」)。
评分是客户档案属性而非审计事件:只存总分与评级时间,五项明细与所用
等级配置不落库(对照表 #25 的定位口径)——等级映射是可变判定表,回溯
「当时命中的等级」需要快照设计,超出档案属性的范围;评级时间已足够
回答「这个分是什么时候打的」。
:raises NotFoundError: 客户不存在
"""
# 客户名进审计摘要,预取放在审计上下文之前(同 change_ownership 口径)
row = customer_repository.get_with_owner_name(session, customer_id)
customer_name = row[0].company_name if row else f"#{customer_id}"
with audit_service.audit(
session,
operator=operator,
record=AuditRecord(
module=MODULE_CUSTOMER,
action=ACTION_UPDATE,
summary=f"保存客户评级 {customer_name}:{payload.score} 分",
request_data=payload.model_dump(),
),
meta=meta,
):
if row is None:
raise NotFoundError("客户不存在")
customer, owner_name = row
customer.rating_score = payload.score
# 评级时间取应用层本地时间(库列 TZ=Asia/Shanghai,与 TimestampMixin
# 的服务器默认值同一时区口径),不用数据库 NOW():行已在本会话
customer.rating_time = datetime.now()
# 响应在 commit 之前构造(同 user_service 顶部说明)
out = _to_out(customer, owner_name)
session.commit()
logger.info("保存客户评级 | customer_id=%s | score=%s", customer_id, payload.score)
return out
def delete_customer(
session: Session,
customer_id: int,