wip: 客户评级弹窗化/公司信息精简/参数中文化/品牌站点同步(基于 927592b)
This commit is contained in:
1 parent
927592b472
commit
27533eb465
43 files changed
+2959
-434
No files matched your search
@@ -6,7 +6,7 @@
|
||||
from sqlalchemy import ColumnElement, Row, Select, func, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models import Customer, OwnershipChangeLog, Personnel
|
||||
from app.models import Customer, CustomerRating, OwnershipChangeLog, Personnel
|
||||
from app.models.customer import OWNER_TYPE_CUSTOMER
|
||||
from app.repositories import ownership_log_repository
|
||||
from app.repositories.query_utils import fuzzy
|
||||
@@ -31,7 +31,7 @@ def count_business_refs(session: Session, customer_id: int) -> int:
|
||||
|
||||
|
||||
def get_with_owner_name(session: Session, customer_id: int) -> Row | None:
|
||||
"""查单个客户并带出归属人员姓名(详情、编辑回显与归属变更共用)。"""
|
||||
"""查单个客户并带出归属人员姓名与评级快照(详情/编辑回显/归属变更共用)。"""
|
||||
stmt = _base_select().where(Customer.id == customer_id)
|
||||
return session.execute(stmt).first()
|
||||
|
||||
@@ -53,12 +53,14 @@ def list_customers(
|
||||
keyword: str | None = None,
|
||||
customer_type: str | None = None,
|
||||
owner_personnel_id: int | None = None,
|
||||
order_field: str | None = None,
|
||||
order_type: str | None = None,
|
||||
) -> list[Row]:
|
||||
"""分页查询客户,行内容为 (客户, 归属人员姓名)。
|
||||
"""分页查询客户,行内容为 (客户, 归属人员姓名, 评级总分, 评级等级)。
|
||||
|
||||
归属人员外键非空但统一用 outerjoin:人员行不可考(被物理删除的脏数据)
|
||||
不应让客户行整个查不出来(同 personnel_repository 的韧性口径)。
|
||||
排序按 id 倒序:客户无排序字段,新增的靠前符合列表页习惯。
|
||||
归属人员/评级行均用 outerjoin:关联行不可考(人员被物理删除、客户
|
||||
未评级)不应让客户行整个查不出来(同 personnel_repository 韧性口径)。
|
||||
默认排序按 id 倒序:新增的靠前符合列表页习惯;评级排序见 ``_apply_order``。
|
||||
"""
|
||||
stmt = _apply_filters(
|
||||
_base_select(),
|
||||
@@ -66,7 +68,8 @@ def list_customers(
|
||||
customer_type=customer_type,
|
||||
owner_personnel_id=owner_personnel_id,
|
||||
)
|
||||
stmt = stmt.order_by(Customer.id.desc()).offset(offset).limit(limit)
|
||||
stmt = _apply_order(stmt, order_field=order_field, order_type=order_type)
|
||||
stmt = stmt.offset(offset).limit(limit)
|
||||
return list(session.execute(stmt).all())
|
||||
|
||||
|
||||
@@ -120,12 +123,38 @@ def count_ownership_logs(session: Session, *, customer_id: int) -> int:
|
||||
|
||||
|
||||
def _base_select() -> Select:
|
||||
"""客户主表外联归属人员姓名。"""
|
||||
return select(Customer, Personnel.personnel_name).outerjoin(
|
||||
Personnel, Customer.owner_personnel_id == Personnel.id
|
||||
"""客户主表外联归属人员姓名与评级快照(等级名/总分,列表评级列免 N+1)。"""
|
||||
return (
|
||||
select(
|
||||
Customer,
|
||||
Personnel.personnel_name,
|
||||
CustomerRating.total_score,
|
||||
CustomerRating.grade_name,
|
||||
)
|
||||
.outerjoin(Personnel, Customer.owner_personnel_id == Personnel.id)
|
||||
.outerjoin(CustomerRating, CustomerRating.customer_id == Customer.id)
|
||||
)
|
||||
|
||||
|
||||
def _apply_order(stmt: Select, *, order_field: str | None, order_type: str | None) -> Select:
|
||||
"""应用列表排序。默认 id 倒序;order_field=rating 按评级总分排序。
|
||||
|
||||
等级由总分判定、与总分同向,故按总分即等价按等级排序。MySQL 没有
|
||||
``NULLS LAST`` 语法,用布尔键 ``total_score IS NULL`` 升序模拟 ——
|
||||
未评级行恒在末尾,正序/倒序一致;同分以 id 倒序兜底保证次序稳定。
|
||||
非法字段/方向不落到 SQL(schema Literal 已在入口拦 400),此处静默
|
||||
回退默认排序作为纵深防御。
|
||||
"""
|
||||
if order_field == "rating" and order_type in ("asc", "desc"):
|
||||
score_order = (
|
||||
CustomerRating.total_score.asc()
|
||||
if order_type == "asc"
|
||||
else CustomerRating.total_score.desc()
|
||||
)
|
||||
return stmt.order_by(CustomerRating.total_score.is_(None), score_order, Customer.id.desc())
|
||||
return stmt.order_by(Customer.id.desc())
|
||||
|
||||
|
||||
def _apply_filters(
|
||||
stmt: Select,
|
||||
*,
|
||||
|
||||
Reference in new issue
Block a user