refactor(backend): 仓储层散参数收敛为查询 DTO
仓储层的 list_*/count_* 此前以 keyword/status/offset/limit 四五个散参数 传参,调用点重复铺开、易漏传且不可扩展;按项目「参数 >4 封装 DTO」的 约定统一收敛为各模块的 Query 对象(如 RoleQuery、BusinessQuery), 服务层与测试调用同步调整,行为不变。 - repositories 层查询方法签名改为 (session, query),分页与筛选条件 由 Query 承载,_apply_filters 同步收口 - schemas 层补齐各模块 Query 模型(approval/business_role/report 等) - services 层调用点同步改为传 Query - tests 随之调整;新增用例:待办角色流转(财务通过后领导出现待办)、 参数预览顺序稳定 验证:全量 pytest 900 passed(11:08);单元 526 passed;ruff check 绿。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
c48d24ab6d
commit
5ece9f6dab
45 files changed
+1045
-1028
No files matched your search
@@ -14,6 +14,7 @@
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy import Row
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
@@ -107,20 +108,8 @@ def _is_duplicate_entry(exc: IntegrityError) -> bool:
|
||||
|
||||
def list_customers(session: Session, query: CustomerQuery) -> PageResult[CustomerOut]:
|
||||
"""分页查询客户(附归属人员姓名)。"""
|
||||
rows = customer_repository.list_customers(
|
||||
session,
|
||||
offset=query.offset,
|
||||
limit=query.page_size,
|
||||
keyword=query.keyword,
|
||||
customer_type=query.customer_type,
|
||||
owner_personnel_id=query.owner_personnel_id,
|
||||
)
|
||||
total = customer_repository.count_customers(
|
||||
session,
|
||||
keyword=query.keyword,
|
||||
customer_type=query.customer_type,
|
||||
owner_personnel_id=query.owner_personnel_id,
|
||||
)
|
||||
rows = customer_repository.list_customers(session, query)
|
||||
total = customer_repository.count_customers(session, query)
|
||||
return PageResult[CustomerOut](
|
||||
records=[_to_out(customer, owner_name) for customer, owner_name in rows],
|
||||
total=total,
|
||||
@@ -317,6 +306,71 @@ def delete_customer(
|
||||
logger.info("删除客户 | customer_id=%s | company=%s", customer_id, company_name)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _CustomerOwnershipPrefetch:
|
||||
"""客户归属变更的审计前只读预取(摘要要客户名与新旧归属人名)。"""
|
||||
|
||||
row: Row | None
|
||||
new_person: Personnel | None
|
||||
old_label: str
|
||||
new_label: str
|
||||
customer_name: str
|
||||
|
||||
|
||||
def _prefetch_customer_ownership(
|
||||
session: Session, customer_id: int, payload: CustomerOwnershipIn
|
||||
) -> _CustomerOwnershipPrefetch:
|
||||
"""审计上下文之前的只读预取(预取不写库,失败路径也要有可读摘要)。"""
|
||||
row = customer_repository.get_with_owner_name(session, customer_id)
|
||||
old_person = (
|
||||
personnel_repository.get_by_id(session, row[0].owner_personnel_id)
|
||||
if row is not None and row[0].owner_personnel_id is not None
|
||||
else None
|
||||
)
|
||||
new_person = (
|
||||
personnel_repository.get_by_id(session, payload.new_owner_personnel_id)
|
||||
if payload.new_owner_personnel_id is not None
|
||||
else None
|
||||
)
|
||||
return _CustomerOwnershipPrefetch(
|
||||
row=row,
|
||||
new_person=new_person,
|
||||
old_label=old_person.personnel_name if old_person else "无",
|
||||
new_label=new_person.personnel_name if new_person else "无",
|
||||
customer_name=row[0].company_name if row else f"#{customer_id}",
|
||||
)
|
||||
|
||||
|
||||
def _validate_customer_ownership(
|
||||
session: Session, customer: Customer, payload: CustomerOwnershipIn
|
||||
) -> int | None:
|
||||
"""校验新归属人(须在职)并判定归属是否变化,返回变更前归属人 ID。"""
|
||||
if payload.new_owner_personnel_id is not None:
|
||||
_ensure_owner_personnel(session, payload.new_owner_personnel_id)
|
||||
old_owner_id = customer.owner_personnel_id
|
||||
if old_owner_id == payload.new_owner_personnel_id:
|
||||
raise BadRequestError("归属未变化")
|
||||
return old_owner_id
|
||||
|
||||
|
||||
def _customer_ownership_log(
|
||||
customer: Customer,
|
||||
payload: CustomerOwnershipIn,
|
||||
old_owner_id: int | None,
|
||||
operator: User,
|
||||
) -> OwnershipChangeLog:
|
||||
"""客户归属变更留痕行。"""
|
||||
return OwnershipChangeLog(
|
||||
owner_type=OWNER_TYPE_CUSTOMER,
|
||||
customer_id=customer.id,
|
||||
old_owner_personnel_id=old_owner_id,
|
||||
new_owner_personnel_id=payload.new_owner_personnel_id,
|
||||
change_reason=payload.reason,
|
||||
operator_id=operator.id,
|
||||
operator_name=operator.username,
|
||||
)
|
||||
|
||||
|
||||
def change_ownership(
|
||||
session: Session,
|
||||
customer_id: int,
|
||||
@@ -335,57 +389,34 @@ def change_ownership(
|
||||
"""
|
||||
# 只读预取在审计上下文之前:摘要需要客户名与新旧归属人名;校验仍在
|
||||
# 上下文内进行,失败同样要留下可追溯的记录
|
||||
row = customer_repository.get_with_owner_name(session, customer_id)
|
||||
old_person = (
|
||||
personnel_repository.get_by_id(session, row[0].owner_personnel_id)
|
||||
if row is not None and row[0].owner_personnel_id is not None
|
||||
else None
|
||||
)
|
||||
new_person = (
|
||||
personnel_repository.get_by_id(session, payload.new_owner_personnel_id)
|
||||
if payload.new_owner_personnel_id is not None
|
||||
else None
|
||||
)
|
||||
old_label = old_person.personnel_name if old_person else "无"
|
||||
new_label = new_person.personnel_name if new_person else "无"
|
||||
customer_name = row[0].company_name if row else f"#{customer_id}"
|
||||
prefetch = _prefetch_customer_ownership(session, customer_id, payload)
|
||||
with audit_service.audit(
|
||||
session,
|
||||
operator=operator,
|
||||
record=AuditRecord(
|
||||
module=MODULE_CUSTOMER,
|
||||
action=ACTION_UPDATE,
|
||||
summary=f"调整客户归属 {customer_name}:{old_label} → {new_label}",
|
||||
summary=(
|
||||
f"调整客户归属 {prefetch.customer_name}:"
|
||||
f"{prefetch.old_label} → {prefetch.new_label}"
|
||||
),
|
||||
request_data=payload.model_dump(exclude_none=True),
|
||||
),
|
||||
meta=meta,
|
||||
):
|
||||
if row is None:
|
||||
if prefetch.row is None:
|
||||
raise NotFoundError("客户不存在")
|
||||
customer, _ = row
|
||||
customer, _ = prefetch.row
|
||||
|
||||
if payload.new_owner_personnel_id is not None:
|
||||
_ensure_owner_personnel(session, payload.new_owner_personnel_id)
|
||||
old_owner_id = customer.owner_personnel_id
|
||||
if old_owner_id == payload.new_owner_personnel_id:
|
||||
raise BadRequestError("归属未变化")
|
||||
old_owner_id = _validate_customer_ownership(session, customer, payload)
|
||||
|
||||
# 主表更新与留痕插入同事务:只在本方法内一次 commit(见模块 docstring)
|
||||
customer.owner_personnel_id = payload.new_owner_personnel_id
|
||||
customer_repository.add_ownership_log(
|
||||
session,
|
||||
OwnershipChangeLog(
|
||||
owner_type=OWNER_TYPE_CUSTOMER,
|
||||
customer_id=customer.id,
|
||||
old_owner_personnel_id=old_owner_id,
|
||||
new_owner_personnel_id=payload.new_owner_personnel_id,
|
||||
change_reason=payload.reason,
|
||||
operator_id=operator.id,
|
||||
operator_name=operator.username,
|
||||
),
|
||||
session, _customer_ownership_log(customer, payload, old_owner_id, operator)
|
||||
)
|
||||
# 响应在 commit 之前构造(同 user_service 顶部说明);归属人名以新值为准
|
||||
out = _to_out(customer, new_person.personnel_name if new_person else None)
|
||||
out = _to_out(customer, prefetch.new_person.personnel_name if prefetch.new_person else None)
|
||||
session.commit()
|
||||
logger.info(
|
||||
"调整客户归属 | customer_id=%s | old=%s | new=%s",
|
||||
|
||||
Reference in new issue
Block a user