fix(approval): 版本号唯一键兜底 + 取号改当前读防并发重复

- V12 迁移:先重排存量重复 (order_type, ref_id) 分组的 version_no
  (V11 把存量一律置 1,而 V11 前重提是新建行),再加
  uk_approval_orders_order_type_ref_version;只重排真有重复的分组,
  避免改动前端已展示过的历史版本号
- _next_version 改锁读(select version_no ... FOR UPDATE,最大值在
  Python 侧算):请求级鉴权的普通 SELECT 已把 REPEATABLE READ 快照
  锚定在请求开始时刻,原 MAX 快照读在并发下会算出同一版本号;
  聚合 + FOR UPDATE 为 MySQL 明确禁止,故不锁聚合结果
- 编辑与月结提交路径把 1062 收敛为 400「提交冲突(版本号生成重复),
  请重试」,其余完整性错误原样上抛;submit_order 内不 catch/rollback
- 驳回清空方案的过时注释改为 V11 起的 append-only 语义(置历史不删行)
- init-dev.sql 同步唯一键与 alembic_version 7f4f7b918bc4,导出 V12 SQL
- 测试:集成 +5(锁读 SQL 形态、两会话快照回归、唯一键 1062、冲突 400、
  正常递增)+ 单元 +1;工作台假审批单改用不同 ref_id 适配唯一键
- 全量回归 897 passed;ruff 与前端 type-check 绿

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-22 13:31:01 +08:00
1 parent 694d21d1fd
commit 9964281c14
12 files changed
+398 -42

No files matched your search

+20 -10
View File
@@ -13,11 +13,13 @@ from datetime import date
from decimal import ROUND_HALF_UP, Decimal
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from app.core.constants import (
MODULE_SETTLEMENT,
)
from app.core.db_errors import is_duplicate_entry
from app.core.exceptions import BadRequestError, NotFoundError
from app.models import (
MonthlySettlement,
@@ -254,16 +256,24 @@ def submit(
if settlement.status not in (SETTLEMENT_STATUS_DRAFT, SETTLEMENT_STATUS_REJECTED):
raise BadRequestError("仅草稿或已驳回状态可提交")
order = approval_service.submit_order(
session,
order_type=_SETTLEMENT_ORDER_TYPE,
ref_id=settlement.id,
refer_no=f"JS{settlement.settlement_month.replace('-', '')}",
submitter=operator,
# 月结明细在生成时已固化;提交时快照让复核者看到的是送审版本,
# 而不是审批期间若发生重生成后的内容
snapshot=approval_service.settlement_content(session, settlement.id),
)
try:
order = approval_service.submit_order(
session,
order_type=_SETTLEMENT_ORDER_TYPE,
ref_id=settlement.id,
refer_no=f"JS{settlement.settlement_month.replace('-', '')}",
submitter=operator,
# 月结明细在生成时已固化;提交时快照让复核者看到的是送审版本,
# 而不是审批期间若发生重生成后的内容
snapshot=approval_service.settlement_content(session, settlement.id),
)
except IntegrityError as exc:
# version_no 唯一键的并发冲突(提交前已锁月结行,此项理论不可达,
# 留作 DB 兜底):审计上下文会统一回滚,这里只把 1062 翻译成
# 可重试的业务提示,其余完整性错误原样上抛
if is_duplicate_entry(exc):
raise BadRequestError("提交冲突(版本号生成重复),请重试") from exc
raise
settlement.approval_order_no = order.order_no
if order.status == SETTLEMENT_STATUS_APPROVED:
mark_locked(session, settlement, operator=operator)