fix(business): 删除保护补齐与单价小数位口径修复(e2e 发现)
- 客户删除保护:被业务引用 → 400(T4 占位注释在业务表就位后漏补,FK 500 暴露) - 人员删除保护:业务归属(负责/协助)引用纳入检查(同样漏检 500) - schema 单价类列(DECIMAL 14,4)小数位放行至 4 位:回显 '5000.0000' 原样提交被两位校验永久拒收(编辑任何带精度单价的历史行都失败) - 补 2 个集成用例锁定;文案同步「客户归属或业务归属引用」 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
299b72483c
commit
fba8cfc215
8 files changed
+167
-23
No files matched your search
@@ -294,14 +294,14 @@ def delete_customer(
|
||||
operator: User,
|
||||
meta: RequestMeta | None = None,
|
||||
) -> None:
|
||||
"""删除客户(物理删除)。
|
||||
"""删除客户(物理删除):被业务引用 → 400 引导处理业务单据。
|
||||
|
||||
**被业务数据引用的保护检查待业务数据表(V5)就位后补充**:customers
|
||||
当前仅被 ownership_change_logs 以无外键的固化 ID 引用,而留痕本就要求
|
||||
「被引用方删除后记录仍完整可读」(见 models/customer.py 的固化论证),
|
||||
故现在删除不会产生悬挂外键。
|
||||
businesses.customer_id 为 RESTRICT 外键,引用检查必须先于删除——
|
||||
否则并发窗口内的 FK 冲突会以 500 暴露(T4 时该检查占位、业务表就位
|
||||
后由本处补齐,留痕表是无外键固化不参与拦截)。
|
||||
|
||||
:raises NotFoundError: 客户不存在
|
||||
:raises BadRequestError: 客户已被业务单据引用
|
||||
"""
|
||||
customer = customer_repository.get_by_id(session, customer_id)
|
||||
with audit_service.audit(
|
||||
@@ -316,10 +316,17 @@ def delete_customer(
|
||||
):
|
||||
if customer is None:
|
||||
raise NotFoundError("客户不存在")
|
||||
if customer_repository.count_business_refs(session, customer_id):
|
||||
raise BadRequestError("该客户已存在关联业务,请先处理相关业务单据")
|
||||
|
||||
company_name = customer.company_name # 固化:对象随删除失效
|
||||
customer_repository.delete(session, customer)
|
||||
session.commit()
|
||||
try:
|
||||
customer_repository.delete(session, customer)
|
||||
session.commit()
|
||||
except IntegrityError as exc:
|
||||
# 先查后删的并发窗口:新业务挂上了该客户,FK RESTRICT 兜底为 400
|
||||
session.rollback()
|
||||
raise BadRequestError("该客户已存在关联业务,请先处理相关业务单据") from exc
|
||||
logger.info("删除客户 | customer_id=%s | company=%s", customer_id, company_name)
|
||||
|
||||
|
||||
|
||||
@@ -46,8 +46,9 @@ from app.services.audit_service import AuditRecord, RequestMeta
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 删除保护的统一文案:两种引用共享同一提示,引导管理员走「离职」而非删除
|
||||
_DELETE_BLOCKED = "存在团队成员或客户归属引用,请改为离职而非删除"
|
||||
# 删除保护的统一文案:三种引用(团队成员/客户归属/业务归属)共享同一提示,
|
||||
# 引导管理员走「离职」而非删除
|
||||
_DELETE_BLOCKED = "存在团队成员、客户归属或业务归属引用,请改为离职而非删除"
|
||||
|
||||
|
||||
def list_personnel(session: Session, query: PersonnelQuery) -> PageResult[PersonnelOut]:
|
||||
@@ -229,9 +230,13 @@ 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)
|
||||
or personnel_repository.count_business_roles_refs(session, ids)
|
||||
):
|
||||
# 业务归属引用(负责/协助)随业务表就位一并纳入删除保护——
|
||||
# 漏检会让删除以 FK RESTRICT 的 500 暴露而非可读的 400
|
||||
raise BadRequestError(_DELETE_BLOCKED)
|
||||
|
||||
name = personnel.personnel_name # 固化:对象随删除失效
|
||||
|
||||
Reference in new issue
Block a user