PayrollManagementSystem/backend/app/repositories/rating_repository.py
liuwenxin 111ac5425a feat(customer): 客户列表展示等级徽标并统一等级不落库口径
评级计算器并入客户管理后,列表只有分数看不出档位,本批补上等级徽标:
- 抽出 utils/rating.ts 的 resolveGrade(按 sort_order 升序取首个
  min_score ≤ 总分的等级,单边判定避免区间边界歧义),列表页与弹窗共用
- 等级映射按需拉取并缓存在内存,等级配置改动后回调父页重拉,避免
  「同一分数在弹窗是 B、在列表是 C」的两处口径分歧
- 徽标色调按等级名首字母映射,自定义等级名落回中性灰(等级名可配置,
  不能靠写死枚举判语义色)
- 悬浮提示给出等级区间与评级时间

同时订正三处与实现不符的注释:rate_customer 实际只写 rating_score 与
rating_time,等级由分数按当前映射实时推导,三处却写成「后端判定并固化等级
名快照」,v13 迁移 docstring 还引用了并不存在的 v14(d5b8f2a46c71)。
纯注释改动,无逻辑变更。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-23 16:18:47 +08:00

71 lines
2.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""客户评级等级配置数据访问。
本层只 flush 不 commit —— 事务边界由 services 层控制。
"""
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.models import CustomerRatingGrade
# ==================== 读取 ====================
def list_all(session: Session, *, for_update: bool = False) -> list[CustomerRatingGrade]:
"""全量等级,按 sort_order 升序、id 兜底(同序次序稳定)。
不分页:等级映射是判定表,数量个位数,分页无意义。
for_update 供区间重叠判定使用:先查后写在无锁时,两管理员并发提交
重叠区间会互判不重叠而双双入库(区间重叠无唯一键可拦)——锁全表行
串行化并发的重叠判定(先到者提交后,后到者在锁下读到新区间即判重叠)。
只读直出(list_grades)不加锁。
"""
stmt = select(CustomerRatingGrade).order_by(
CustomerRatingGrade.sort_order, CustomerRatingGrade.id
)
if for_update:
stmt = stmt.with_for_update()
return list(session.execute(stmt).scalars())
def get_by_id(session: Session, grade_id: int) -> CustomerRatingGrade | None:
"""按主键查询。
等级表无外键引用(客户档案只存总分,等级由分数按当前映射实时推导,
不存等级 id),无需 ``for_update`` 锁行 —— product_repository 锁行防的
是并发引用悬空,此处不存在该风险:删掉一个等级,只是以后不再判到它。
"""
return session.get(CustomerRatingGrade, grade_id)
def get_by_name(session: Session, grade_name: str) -> CustomerRatingGrade | None:
"""按等级名称查询。
grade_name 是 uk_customer_rating_grades_grade_name 唯一键的键值,先查
用于给出即时业务提示;并发窗口仍存在,最终由 IntegrityError 兜底
(见 rating_service)。
"""
stmt = select(CustomerRatingGrade).where(CustomerRatingGrade.grade_name == grade_name)
return session.execute(stmt).scalar_one_or_none()
# ==================== 写入 ====================
def create(session: Session, grade: CustomerRatingGrade) -> CustomerRatingGrade:
"""新增等级。只 flush 以取得自增 id。"""
session.add(grade)
session.flush()
return grade
def delete(session: Session, grade: CustomerRatingGrade) -> None:
"""物理删除等级。
等级表无外键引用(客户档案只存总分,不存等级 id),调用方无需引用保护
校验:删掉等级只是「以后不再判到它」,已存的总分不受影响。
"""
session.delete(grade)
session.flush()