fix(business): 编辑/流转/归属加锁与终态编辑门槛,has_docs 单点收口
- get_with_names 加 for_update 参数:update/change_status/change_ownership 以锁定读取行,与发货/回款写路径同一锁口径——「有发货禁换客户」的 快照读校验不再被并发新增发货旁路(此前 delete 有锁、唯独编辑无锁) - update_business 补终态门槛(明细/汇总是 M3 核算基线,与发货/回款 服务禁写终态同口径);前端编辑按钮按终态禁用并加 tooltip - 新增 business_repository.has_docs(单条 OR exists):替换 update 换客户与 delete 两处内联双查询,M3 新增挂靠单据类型时只改一处 - 出参组装复用首查 row:change_ownership 的姓名侧按「变更项取校验 函数返回的新人名、未变项沿用首查名」处理(首查名是变更前快照) - 新增终态编辑 400 且明细未动的集成用例 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
a0086554b1
commit
6d612daf30
4 files changed
+108
-33
No files matched your search
@@ -363,9 +363,9 @@ def update_business(
|
||||
发货/回款语义断裂(明细同理随业务走)。
|
||||
|
||||
:raises NotFoundError: 业务不存在
|
||||
:raises BadRequestError: 归属人员不合法 / 有发货或回款时换客户
|
||||
:raises BadRequestError: 业务已完成或已终止 / 归属人员不合法 / 有发货或回款时换客户
|
||||
"""
|
||||
row = business_repository.get_with_names(session, business_id)
|
||||
row = business_repository.get_with_names(session, business_id, for_update=True)
|
||||
business = row[0] if row else None
|
||||
with audit_service.audit(
|
||||
session,
|
||||
@@ -380,18 +380,17 @@ def update_business(
|
||||
):
|
||||
if row is None:
|
||||
raise NotFoundError("业务不存在")
|
||||
business, _, _, _ = row
|
||||
business, company_name, owner_name, assistant_name = row
|
||||
|
||||
# 终态门槛与发货/回款写准入同口径:明细与汇总是 M3 核算基线,
|
||||
# 已完成/已终止的业务禁改(发货/回款服务以同一理由禁写终态业务)
|
||||
if business.status != BUSINESS_STATUS_ACTIVE:
|
||||
raise BadRequestError("业务已完成或已终止,禁止编辑业务")
|
||||
|
||||
if payload.customer_id != business.customer_id:
|
||||
has_docs = session.query(Shipment.id).filter(
|
||||
Shipment.business_id == business_id
|
||||
).first() is not None or (
|
||||
session.query(Receipt.id).filter(Receipt.business_id == business_id).first()
|
||||
is not None
|
||||
)
|
||||
if has_docs:
|
||||
if business_repository.has_docs(session, business_id):
|
||||
raise BadRequestError("业务已有发货或回款记录,不能更换客户")
|
||||
_ensure_customer(session, payload.customer_id)
|
||||
customer = _ensure_customer(session, payload.customer_id)
|
||||
if payload.owner_personnel_id is not None:
|
||||
_ensure_personnel_active(session, payload.owner_personnel_id, "负责")
|
||||
if payload.assistant_personnel_id is not None:
|
||||
@@ -408,7 +407,9 @@ def update_business(
|
||||
session, business_id, _build_item_rows(business_id, payload.items)
|
||||
)
|
||||
_recalc_totals(session, business)
|
||||
out = _to_out(business_repository.get_with_names(session, business_id))
|
||||
# 锁定行上已赋值,同一身份映射实例的 _to_out 即反映新值;名称字段
|
||||
# 从首查 row 取(换客户时 _ensure_customer 已给出新公司名)
|
||||
out = _to_out((business, customer.company_name, owner_name, assistant_name))
|
||||
session.commit()
|
||||
logger.info("修改业务 | business_id=%s", business_id)
|
||||
return out
|
||||
@@ -442,12 +443,7 @@ def delete_business(
|
||||
):
|
||||
if business is None:
|
||||
raise NotFoundError("业务不存在")
|
||||
if (
|
||||
session.query(Shipment.id).filter(Shipment.business_id == business_id).first()
|
||||
is not None
|
||||
or session.query(Receipt.id).filter(Receipt.business_id == business_id).first()
|
||||
is not None
|
||||
):
|
||||
if business_repository.has_docs(session, business_id):
|
||||
raise BadRequestError("业务已有发货或回款记录,请改为已终止而非删除")
|
||||
|
||||
business_no = business.business_no
|
||||
@@ -484,7 +480,7 @@ def change_status(
|
||||
:raises NotFoundError: 业务不存在
|
||||
:raises BadRequestError: 非执行中状态流转 / 目标与当前相同
|
||||
"""
|
||||
row = business_repository.get_with_names(session, business_id)
|
||||
row = business_repository.get_with_names(session, business_id, for_update=True)
|
||||
with audit_service.audit(
|
||||
session,
|
||||
operator=operator,
|
||||
@@ -522,7 +518,8 @@ def change_status(
|
||||
operator_name=operator.username,
|
||||
),
|
||||
)
|
||||
out = _to_out(business_repository.get_with_names(session, business_id))
|
||||
# 锁定行上已赋值,同一身份映射实例的 _to_out 即反映新状态
|
||||
out = _to_out(row)
|
||||
session.commit()
|
||||
logger.info(
|
||||
"业务状态变更 | business_id=%s | %s -> %s",
|
||||
@@ -549,7 +546,7 @@ def change_ownership(
|
||||
:raises NotFoundError: 业务不存在
|
||||
:raises BadRequestError: 归属人员不存在或已离职 / 归属未变化
|
||||
"""
|
||||
row = business_repository.get_with_names(session, business_id)
|
||||
row = business_repository.get_with_names(session, business_id, for_update=True)
|
||||
with audit_service.audit(
|
||||
session,
|
||||
operator=operator,
|
||||
@@ -563,11 +560,19 @@ def change_ownership(
|
||||
):
|
||||
if row is None:
|
||||
raise NotFoundError("业务不存在")
|
||||
business, _, _, _ = row
|
||||
if payload.new_owner_personnel_id is not None:
|
||||
business, _, old_owner_name, old_assistant_name = row
|
||||
# 归属变更的出参姓名来自校验函数的返回行(锁定读):首查姓名是变更前
|
||||
# 固化标量,新归属人名必须按新 ID 重取
|
||||
new_owner = (
|
||||
_ensure_personnel_active(session, payload.new_owner_personnel_id, "负责")
|
||||
if payload.new_assistant_personnel_id is not None:
|
||||
if payload.new_owner_personnel_id is not None
|
||||
else None
|
||||
)
|
||||
new_assistant = (
|
||||
_ensure_personnel_active(session, payload.new_assistant_personnel_id, "协助")
|
||||
if payload.new_assistant_personnel_id is not None
|
||||
else None
|
||||
)
|
||||
|
||||
owner_changed = business.owner_personnel_id != payload.new_owner_personnel_id
|
||||
assistant_changed = business.assistant_personnel_id != payload.new_assistant_personnel_id
|
||||
@@ -592,7 +597,16 @@ def change_ownership(
|
||||
operator_name=operator.username,
|
||||
),
|
||||
)
|
||||
out = _to_out(business_repository.get_with_names(session, business_id))
|
||||
# 出参组装:ID 在锁定行上已赋值(_to_out 读内存值即反映新值),姓名
|
||||
# 一侧「变更项用新 ID 的人名、未变项沿用首查名」——首查名是变更前快照
|
||||
out = _to_out(
|
||||
(
|
||||
business,
|
||||
row[1],
|
||||
new_owner.personnel_name if new_owner is not None else old_owner_name,
|
||||
new_assistant.personnel_name if new_assistant is not None else old_assistant_name,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
logger.info(
|
||||
"调整业务归属 | business_id=%s | owner %s->%s | assistant %s->%s",
|
||||
|
||||
Reference in new issue
Block a user