fix(huya): 作废订单后失效旧积分快照

This commit is contained in:
yml
2026-11-12 16:08:13 +08:00
parent 2ae90ef78f
commit 56e5d0ea1c
6 changed files with 438 additions and 2 deletions
@@ -0,0 +1,115 @@
"""清理虎牙外部单协作中作废订单遗留的积分核账快照。
作废只代表订单不再参与后续核账,但历史版本没有清掉作废前写入的组积分合计,
导致作废行以及尚未重算的同组订单继续显示旧的需求/结余。这里做一次幂等修复:
1. 所有 ``void`` 行清空积分快照;
2. 同组仍在核账状态、且快照早于最近一次作废动作的订单,也清空快照;
3. 资料完整且 CK 仍有效的 ``recharged / points_error`` 行退回 ``to_recharge``,
提示重新核账。
实时状态切换已在服务层同步失效快照;本迁移只修复升级前已经产生的历史数据。
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20261112_0078"
down_revision: str | None = "20261112_0077"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
TABLE = "huya_external_orders"
STALE_MESSAGE = "订单组成员已作废或恢复,原积分统计已失效,请重新核账"
def upgrade() -> None:
bind = op.get_bind()
inspector = sa.inspect(bind)
if not inspector.has_table(TABLE) or not inspector.has_table("audit_logs"):
return
# 作废行不再参与任何后续核账,旧快照只保留在审计历史里。
bind.execute(
sa.text(
f"""
UPDATE {TABLE}
SET points_checked_at = NULL,
points_balance = NULL,
points_required_total = NULL,
points_surplus = NULL,
points_threshold = NULL,
points_check_reason = 'stale_group',
points_check_message = :message,
points_check_detail = NULL
WHERE status = 'void'
AND (
points_checked_at IS NOT NULL
OR points_balance IS NOT NULL
OR points_required_total IS NOT NULL
OR points_surplus IS NOT NULL
OR points_threshold IS NOT NULL
OR points_check_detail IS NOT NULL
)
"""
),
{"message": STALE_MESSAGE},
)
# 只处理“快照早于最近一次作废”的同组行;作废后又重新核账过的结果必须保留。
bind.execute(
sa.text(
f"""
UPDATE {TABLE} AS orders
JOIN (
SELECT void_order.bind_game_account,
void_order.bind_game_role,
MAX(audit.created_at) AS voided_at
FROM {TABLE} AS void_order
JOIN audit_logs AS audit
ON audit.target = CONCAT('huya_external_order:', void_order.id)
AND audit.action = 'huya:collab:status'
AND JSON_UNQUOTE(JSON_EXTRACT(audit.detail, '$.to')) = 'void'
WHERE void_order.status = 'void'
AND void_order.bind_game_account <> ''
AND void_order.bind_game_role <> ''
GROUP BY void_order.bind_game_account, void_order.bind_game_role
) AS void_groups
ON orders.bind_game_account = void_groups.bind_game_account
AND orders.bind_game_role = void_groups.bind_game_role
SET orders.status = CASE
WHEN orders.status IN ('recharged', 'points_error')
AND orders.ck_status = 'valid'
AND orders.cookie IS NOT NULL
AND orders.cookie <> ''
AND orders.gift_name <> ''
AND COALESCE(JSON_LENGTH(orders.home_images), 0) > 0
THEN 'to_recharge'
ELSE orders.status
END,
orders.recharged_at = NULL,
orders.scripted_at = NULL,
orders.done_at = NULL,
orders.points_checked_at = NULL,
orders.points_balance = NULL,
orders.points_required_total = NULL,
orders.points_surplus = NULL,
orders.points_threshold = NULL,
orders.points_check_reason = 'stale_group',
orders.points_check_message = :message,
orders.points_check_detail = NULL
WHERE orders.status IN ('to_recharge', 'recharged', 'points_error')
AND orders.points_checked_at IS NOT NULL
AND orders.points_checked_at <= void_groups.voided_at
"""
),
{"message": STALE_MESSAGE},
)
def downgrade() -> None:
"""数据修复不可逆;旧快照仍保留在审计日志中,无法可靠恢复当前展示字段。"""