test(base): 基础数据管理模块集成用例(48 例)与审计盲区修复

- 覆盖:401/引用校验/删除保护/子表全量替换/类别与组合唯一/归属变更留痕/审计双记录
- 缺陷修复:personnel/product/customer 三 service 的前置校验移入审计上下文,
  校验失败(400)同样留痕(成功与失败都记的既定原则)
- conftest 兜底清理扩展到 M2 五表(留痕无外键,按 operator_name + 前缀双轨清理)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-13 11:22:34 +08:00
1 parent 15de81dd2c
commit ca88ee7a0b
12 files changed
+1090 -29

No files matched your search

+8 -5
View File
@@ -166,10 +166,6 @@ def create_customer(
:raises BadRequestError: 客户分类不合法 / 归属人员不存在或已离职 /
「公司名称+负责人」已存在(含并发兜底)
"""
_ensure_customer_type(payload.customer_type)
owner_name: str | None = None
if payload.owner_personnel_id is not None:
owner_name = _ensure_owner_personnel(session, payload.owner_personnel_id).personnel_name
with audit_service.audit(
session,
operator=operator,
@@ -181,6 +177,12 @@ def create_customer(
),
meta=meta,
):
# 值域与归属校验在审计上下文内:失败(400)也要留痕 ——
# 与 personnel_service._validate_refs 移入上下文的口径一致
_ensure_customer_type(payload.customer_type)
owner_name: str | None = None
if payload.owner_personnel_id is not None:
owner_name = _ensure_owner_personnel(session, payload.owner_personnel_id).personnel_name
if (
customer_repository.get_by_company_owner(
session, payload.company_name, payload.owner_name
@@ -233,7 +235,6 @@ def update_customer(
:raises NotFoundError: 客户不存在
:raises BadRequestError: 客户分类不合法 / 「公司名称+负责人」已存在(含并发兜底)
"""
_ensure_customer_type(payload.customer_type)
# 只读预取在审计上下文之前:目标不存在(NotFound 失败)时也能留下可读记录,
# 摘要与回包都要用客户行;归属姓名本端点不变更,一并带出
row = customer_repository.get_with_owner_name(session, customer_id)
@@ -249,6 +250,8 @@ def update_customer(
),
meta=meta,
):
# 值域校验在审计上下文内:分类非法的 400 也要留痕(同 create 口径)
_ensure_customer_type(payload.customer_type)
if row is None:
raise NotFoundError("客户不存在")
customer, owner_name = row
+16 -7
View File
@@ -67,7 +67,10 @@ def list_personnel(session: Session, query: PersonnelQuery) -> PageResult[Person
business_role_id=query.business_role_id,
)
return PageResult[PersonnelOut](
records=[_to_out(personnel, role_name, manager_name) for personnel, role_name, manager_name in rows],
records=[
_to_out(personnel, role_name, manager_name)
for personnel, role_name, manager_name in rows
],
total=total,
page_num=query.page_num,
page_size=query.page_size,
@@ -107,7 +110,6 @@ def create_personnel(
:raises BadRequestError: 岗位不存在或已停用 / 直属经理不合法
"""
role, manager_name = _validate_refs(session, payload.business_role_id, payload.manager_id, None)
with audit_service.audit(
session,
operator=operator,
@@ -119,6 +121,12 @@ def create_personnel(
),
meta=meta,
):
# 引用校验放在审计上下文内:失败同样要留痕(与 update_personnel 口径
# 一致)——「谁在何时试图把人挂到停用岗位/离职经理上」是审计对象,
# 校验留在上下文外会让这类失败操作无据可查
role, manager_name = _validate_refs(
session, payload.business_role_id, payload.manager_id, None
)
personnel = Personnel(
personnel_name=payload.personnel_name,
business_role_id=payload.business_role_id,
@@ -221,10 +229,9 @@ def delete_personnel(
raise NotFoundError("人员不存在")
ids = [personnel_id]
if (
personnel_repository.count_team_members(session, ids)
or personnel_repository.count_customer_ownerships(session, ids)
):
if personnel_repository.count_team_members(
session, ids
) or personnel_repository.count_customer_ownerships(session, ids):
raise BadRequestError(_DELETE_BLOCKED)
name = personnel.personnel_name # 固化:对象随删除失效
@@ -302,7 +309,9 @@ def replace_regions(
if row is None:
raise NotFoundError("人员不存在")
rows = [
PersonnelRegion(personnel_id=personnel_id, region_name=item.region_name, remark=item.remark)
PersonnelRegion(
personnel_id=personnel_id, region_name=item.region_name, remark=item.remark
)
for item in payload.items
]
personnel_repository.replace_regions(session, personnel_id, rows)
+9 -6
View File
@@ -146,7 +146,6 @@ def create_product(
:raises BadRequestError: 产品类别不合法 / 已存在同名称同规格的产品
"""
_ensure_category(payload.product_category)
with audit_service.audit(
session,
operator=operator,
@@ -158,8 +157,13 @@ def create_product(
),
meta=meta,
):
# 值域校验在审计上下文内:类别非法的 400 也要留痕(成功与失败都记)
_ensure_category(payload.product_category)
spec_model = payload.spec_model or "" # 归一空串:组合唯一键对 NULL 不去重
if product_repository.get_by_name_spec(session, payload.product_name, spec_model) is not None:
if (
product_repository.get_by_name_spec(session, payload.product_name, spec_model)
is not None
):
raise BadRequestError("已存在同名称同规格的产品")
product = Product(
@@ -182,9 +186,7 @@ def create_product(
session.rollback()
_raise_if_duplicate(exc)
logger.info(
"新增产品 | product_id=%s | category=%s", product.id, payload.product_category
)
logger.info("新增产品 | product_id=%s | category=%s", product.id, payload.product_category)
return out
@@ -202,7 +204,6 @@ def update_product(
:raises BadRequestError: 产品类别不合法 / 已存在同名称同规格的产品
"""
product = product_repository.get_by_id(session, product_id)
_ensure_category(payload.product_category)
with audit_service.audit(
session,
operator=operator,
@@ -214,6 +215,8 @@ def update_product(
),
meta=meta,
):
# 值域校验在审计上下文内:类别非法的 400 也要留痕(同 create 口径)
_ensure_category(payload.product_category)
if product is None:
raise NotFoundError("产品不存在")