refactor(weibo): 移除兑换待确认状态
This commit is contained in:
@@ -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}`;"))
|
||||
|
||||
@@ -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:
|
||||
"""旧状态没有独立业务含义,降级时保持「已充值」。"""
|
||||
@@ -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,
|
||||
|
||||
@@ -4,7 +4,6 @@ export type WeiboCollabStatus =
|
||||
| 'incomplete'
|
||||
| 'to_recharge'
|
||||
| 'recharged'
|
||||
| 'exchange_pending'
|
||||
| 'done'
|
||||
| 'weidou_error'
|
||||
| 'failed'
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user