refactor(weibo): 移除兑换待确认状态

This commit is contained in:
yml
2026-11-12 19:28:53 +08:00
parent 56e5d0ea1c
commit 275ae7f241
6 changed files with 104 additions and 28 deletions
+61 -2
View File
@@ -24,13 +24,19 @@ from sqlalchemy.orm import Session
import web.backend.database as database_module
from tests.support.db import get_mysql_credentials, get_test_engine
from web.backend.database import Base
from web.backend.models import AuditLog, HuyaExternalOrder, User, WeiboAccount
from web.backend.models import (
AuditLog,
HuyaExternalOrder,
User,
WeiboAccount,
WeiboExternalOrder,
)
TESTS_DIR = Path(__file__).resolve().parent
PROJECT_ROOT = TESTS_DIR.parent
VERSIONS_DIR = PROJECT_ROOT / "web" / "backend" / "migrations" / "versions"
HEAD_REVISION = "20261112_0078"
HEAD_REVISION = "20261112_0079"
# 迁移新增、但不声明在模型里的复合查询索引。
EXTRA_INDEXES = (
@@ -408,3 +414,56 @@ class TestMigrationSmoke:
mig_engine.dispose()
with admin_engine.begin() as conn:
conn.execute(text(f"DROP DATABASE IF EXISTS `{mig_db}`;"))
def test_0079_moves_exchange_pending_to_recharged(self):
creds = get_mysql_credentials()
mig_db = f"douyu_login_mig_{secrets.token_hex(4)}"
mig_url = (
f"mysql+pymysql://{quote_plus(creds['user'])}:"
f"{quote_plus(creds['password'])}@{creds['host']}:{creds['port']}/"
f"{mig_db}?charset=utf8mb4"
)
admin_engine = get_test_engine()
with admin_engine.begin() as conn:
conn.execute(
text(
f"CREATE DATABASE `{mig_db}` "
"CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;"
)
)
original_url = database_module.DATABASE_URL
mig_engine = create_engine(mig_url)
try:
database_module.DATABASE_URL = mig_url
cfg = _migration_config()
cfg.set_main_option("sqlalchemy.url", mig_url)
command.upgrade(cfg, "20261112_0078")
with Session(mig_engine) as session:
order = WeiboExternalOrder(
order_no="MIG-0079-PENDING",
status="exchange_pending",
exchange_started_at=datetime(2026, 11, 12, 8, 30, tzinfo=UTC),
)
session.add(order)
session.commit()
command.upgrade(cfg, "20261112_0079")
with mig_engine.connect() as conn:
row = conn.execute(
text(
"SELECT status, recharged_at "
"FROM weibo_external_orders "
"WHERE order_no = 'MIG-0079-PENDING'"
)
).one()
assert row.status == "recharged"
assert row.recharged_at is not None
finally:
database_module.DATABASE_URL = original_url
mig_engine.dispose()
with admin_engine.begin() as conn:
conn.execute(text(f"DROP DATABASE IF EXISTS `{mig_db}`;"))
+3 -4
View File
@@ -143,9 +143,8 @@ def test_exchange_record_unique_match_and_archive(monkeypatch, db):
}
monkeypatch.setattr(collab, "WeiboClient", FakeClient)
collab.check_balance_groups(db, user, [first], threshold=100)
collab.set_status(db, user, first, collab.STATUS_EXCHANGE_PENDING)
collab.set_status(db, user, second, collab.STATUS_EXCHANGE_PENDING)
collab.set_status(db, user, first, collab.STATUS_RECHARGED)
collab.set_status(db, user, second, collab.STATUS_RECHARGED)
result = collab.check_exchange_records(db, user, [first, second])
@@ -155,7 +154,7 @@ def test_exchange_record_unique_match_and_archive(monkeypatch, db):
db.refresh(second)
assert first.status == collab.STATUS_DONE
assert first.exchange_verdict == collab.VERDICT_SUCCESS
assert second.status == collab.STATUS_EXCHANGE_PENDING
assert second.status == collab.STATUS_RECHARGED
assert second.exchange_verdict == collab.VERDICT_UNKNOWN
archived = collab.archive_orders(db, user, [first.id], reason="客户验收")
@@ -0,0 +1,35 @@
"""微博外部单协作移除「兑换待确认」状态,存量订单归入「已充值」。"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
revision: str = "20261112_0079"
down_revision: str | None = "20261112_0078"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
TABLE = "weibo_external_orders"
def upgrade() -> None:
bind = op.get_bind()
if not sa.inspect(bind).has_table(TABLE):
return
bind.execute(
sa.text(
f"""
UPDATE {TABLE}
SET status = 'recharged',
recharged_at = COALESCE(recharged_at, exchange_started_at, updated_at, created_at)
WHERE status = 'exchange_pending'
"""
)
)
def downgrade() -> None:
"""旧状态没有独立业务含义,降级时保持「已充值」。"""
+4 -15
View File
@@ -33,7 +33,6 @@ from .weibo_service import query_weibo_mall, refresh_weibo_account
STATUS_INCOMPLETE = "incomplete"
STATUS_TO_RECHARGE = "to_recharge"
STATUS_RECHARGED = "recharged"
STATUS_EXCHANGE_PENDING = "exchange_pending"
STATUS_DONE = "done"
STATUS_WEIDOU_ERROR = "weidou_error"
STATUS_FAILED = "failed"
@@ -43,7 +42,6 @@ ORDER_STATUSES = (
STATUS_INCOMPLETE,
STATUS_TO_RECHARGE,
STATUS_RECHARGED,
STATUS_EXCHANGE_PENDING,
STATUS_DONE,
STATUS_WEIDOU_ERROR,
STATUS_FAILED,
@@ -54,7 +52,6 @@ ORDER_STATUS_LABELS = {
STATUS_INCOMPLETE: "待补资料",
STATUS_TO_RECHARGE: "待充值",
STATUS_RECHARGED: "已充值",
STATUS_EXCHANGE_PENDING: "兑换待确认",
STATUS_DONE: "兑换成功",
STATUS_WEIDOU_ERROR: "微豆异常",
STATUS_FAILED: "兑换失败",
@@ -65,15 +62,13 @@ STATUS_FLOW = (
STATUS_INCOMPLETE,
STATUS_TO_RECHARGE,
STATUS_RECHARGED,
STATUS_EXCHANGE_PENDING,
STATUS_DONE,
)
STAGE_INDEX = {status: index for index, status in enumerate(STATUS_FLOW)}
NEXT_STATUS = {
STATUS_INCOMPLETE: STATUS_TO_RECHARGE,
STATUS_TO_RECHARGE: STATUS_RECHARGED,
STATUS_RECHARGED: STATUS_EXCHANGE_PENDING,
STATUS_EXCHANGE_PENDING: STATUS_DONE,
STATUS_RECHARGED: STATUS_DONE,
}
BALANCE_GROUP_STATUSES = (
@@ -672,9 +667,9 @@ def set_status(
_assert_mutable(order)
if target not in ORDER_STATUSES:
raise CollabError("非法状态", code="status_invalid")
if target in (STATUS_RECHARGED, STATUS_WEIDOU_ERROR) and not allow_check:
if target == STATUS_WEIDOU_ERROR and not allow_check:
raise CollabError(
"「已充值 / 微豆异常」必须通过余额核账写入",
"「微豆异常」必须通过余额核账写入",
code="balance_check_required",
)
old = order.status
@@ -725,12 +720,6 @@ def set_status(
if rollback:
order.exchange_started_at = None
order.done_at = None
elif target == STATUS_EXCHANGE_PENDING and not order.recharged_at:
raise CollabError("请先完成余额核账", code="recharge_check_required")
elif target == STATUS_EXCHANGE_PENDING:
if order.exchange_started_at is None:
order.exchange_started_at = now
order.done_at = None
elif target == STATUS_DONE:
if order.exchange_started_at is None:
order.exchange_started_at = now
@@ -1227,7 +1216,7 @@ def check_exchange_records(
order.exchange_record_snapshot = _json_safe(record)
order.exchange_verdict = VERDICT_SUCCESS
order.exchange_message = "兑换记录唯一命中"
if order.status == STATUS_EXCHANGE_PENDING:
if order.status == STATUS_RECHARGED:
set_status(
db,
current,
-1
View File
@@ -4,7 +4,6 @@ export type WeiboCollabStatus =
| 'incomplete'
| 'to_recharge'
| 'recharged'
| 'exchange_pending'
| 'done'
| 'weidou_error'
| 'failed'
+1 -6
View File
@@ -70,7 +70,6 @@ const STATUS_META: Record<WeiboCollabStatus, { label: string; color: string }> =
incomplete: { label: '待补资料', color: 'default' },
to_recharge: { label: '待充值', color: 'blue' },
recharged: { label: '已充值', color: 'cyan' },
exchange_pending: { label: '兑换待确认', color: 'purple' },
done: { label: '兑换成功', color: 'success' },
weidou_error: { label: '微豆异常', color: 'orange' },
failed: { label: '兑换失败', color: 'error' },
@@ -87,7 +86,6 @@ const STATUS_ORDER: WeiboCollabStatus[] = [
'incomplete',
'to_recharge',
'recharged',
'exchange_pending',
'done',
'weidou_error',
'failed',
@@ -95,7 +93,7 @@ const STATUS_ORDER: WeiboCollabStatus[] = [
];
const MANUAL_STATUS_OPTIONS = STATUS_ORDER.filter(
(item) => item !== 'recharged' && item !== 'weidou_error',
(item) => item !== 'weidou_error',
).map((value) => ({ value, label: STATUS_META[value].label }));
const VERDICT_META: Record<string, { label: string; color: string }> = {
@@ -1373,9 +1371,6 @@ export default function WeiboCollabPage() {
<Button icon={<SyncOutlined />} onClick={() => void runExchangeCheck([detail.id])}>
查兑换记录
</Button>
<Button onClick={() => openStatus([detail.id], 'exchange_pending')}>
标记兑换发起
</Button>
</>
)}
</Space>