feat(douyu): 拆分鱼翅积分与兑换记录查询
This commit is contained in:
@@ -19,6 +19,8 @@ class FakeClient:
|
|||||||
self.points = points
|
self.points = points
|
||||||
self.fish = fish
|
self.fish = fish
|
||||||
self.records = records if records is not None else []
|
self.records = records if records is not None else []
|
||||||
|
self.records_calls = 0
|
||||||
|
self.orders_calls = 0
|
||||||
|
|
||||||
def elite_user_info(self, **_):
|
def elite_user_info(self, **_):
|
||||||
return {
|
return {
|
||||||
@@ -55,9 +57,11 @@ class FakeClient:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def exchange_records(self, **_):
|
def exchange_records(self, **_):
|
||||||
|
self.records_calls += 1
|
||||||
return {"records": self.records}
|
return {"records": self.records}
|
||||||
|
|
||||||
def exchange_orders(self, **_):
|
def exchange_orders(self, **_):
|
||||||
|
self.orders_calls += 1
|
||||||
return {"orders": []}
|
return {"orders": []}
|
||||||
|
|
||||||
|
|
||||||
@@ -215,6 +219,96 @@ class TestDouyuCollabOrders:
|
|||||||
assert result["points_reason"] == "shortage"
|
assert result["points_reason"] == "shortage"
|
||||||
assert result["order"].status == collab.STATUS_POINTS_ERROR
|
assert result["order"].status == collab.STATUS_POINTS_ERROR
|
||||||
|
|
||||||
|
def test_balance_query_skips_records_and_never_changes_status(self):
|
||||||
|
order = collab.create_order(
|
||||||
|
self.session,
|
||||||
|
self.admin,
|
||||||
|
order_no="DY-106",
|
||||||
|
shop="店铺A",
|
||||||
|
account_username="douyu-account-a",
|
||||||
|
gift_name="礼包A",
|
||||||
|
required_points=100,
|
||||||
|
home_images=["collab/home-5.png"],
|
||||||
|
client=FakeClient(),
|
||||||
|
)
|
||||||
|
collab.set_status(
|
||||||
|
self.session,
|
||||||
|
self.admin,
|
||||||
|
order,
|
||||||
|
status=collab.STATUS_RECHARGED,
|
||||||
|
reason="鱼翅已充值",
|
||||||
|
)
|
||||||
|
order.points_threshold = 10
|
||||||
|
self.session.commit()
|
||||||
|
client = FakeClient(points=50, records=[{"commodityName": "礼包A"}])
|
||||||
|
|
||||||
|
result = collab.query_activity(
|
||||||
|
self.session,
|
||||||
|
self.admin,
|
||||||
|
order,
|
||||||
|
client=client,
|
||||||
|
source="balance_batch",
|
||||||
|
include_records=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert client.records_calls == 0
|
||||||
|
assert client.orders_calls == 0
|
||||||
|
assert result["scope"] == "balances"
|
||||||
|
assert result["verdict"] == collab.VERDICT_UNKNOWN
|
||||||
|
assert result["points_reason"] == "shortage"
|
||||||
|
assert result["order"].status == collab.STATUS_RECHARGED
|
||||||
|
assert result["metric"].source == "balance_batch"
|
||||||
|
assert result["metric"].records_total == 0
|
||||||
|
|
||||||
|
def test_balance_query_keeps_last_exchange_verdict_visible(self):
|
||||||
|
order = collab.create_order(
|
||||||
|
self.session,
|
||||||
|
self.admin,
|
||||||
|
order_no="DY-107",
|
||||||
|
shop="店铺A",
|
||||||
|
account_username="douyu-account-a",
|
||||||
|
gift_name="礼包A",
|
||||||
|
required_points=100,
|
||||||
|
home_images=["collab/home-6.png"],
|
||||||
|
client=FakeClient(),
|
||||||
|
)
|
||||||
|
collab.set_status(
|
||||||
|
self.session,
|
||||||
|
self.admin,
|
||||||
|
order,
|
||||||
|
status=collab.STATUS_RECHARGED,
|
||||||
|
reason="鱼翅已充值",
|
||||||
|
)
|
||||||
|
collab.set_status(
|
||||||
|
self.session,
|
||||||
|
self.admin,
|
||||||
|
order,
|
||||||
|
status=collab.STATUS_SCRIPTED,
|
||||||
|
reason="脚本已上",
|
||||||
|
)
|
||||||
|
record_result = collab.query_activity(
|
||||||
|
self.session,
|
||||||
|
self.admin,
|
||||||
|
order,
|
||||||
|
client=FakeClient(records=[{"commodityName": "礼包A"}]),
|
||||||
|
)
|
||||||
|
assert record_result["order"].status == collab.STATUS_DONE
|
||||||
|
|
||||||
|
collab.query_activity(
|
||||||
|
self.session,
|
||||||
|
self.admin,
|
||||||
|
order,
|
||||||
|
client=FakeClient(),
|
||||||
|
source="balance_batch",
|
||||||
|
include_records=False,
|
||||||
|
)
|
||||||
|
payload = collab.serialize_one(self.session, order)
|
||||||
|
|
||||||
|
assert payload["status"] == collab.STATUS_DONE
|
||||||
|
assert payload["last_verdict"] == collab.VERDICT_SUCCESS
|
||||||
|
assert payload["last_records_matched"] == 1
|
||||||
|
assert payload["last_metric_message"] == "仅查询鱼翅与积分,未查询兑换记录"
|
||||||
|
|
||||||
def test_draft_can_be_created_before_account_resolution(self):
|
def test_draft_can_be_created_before_account_resolution(self):
|
||||||
order = collab.create_order(
|
order = collab.create_order(
|
||||||
self.session,
|
self.session,
|
||||||
@@ -579,6 +673,10 @@ class TestDouyuCollabOrders:
|
|||||||
assert "/api/douyu/external-orders/ck/export" in paths
|
assert "/api/douyu/external-orders/ck/export" in paths
|
||||||
assert "/api/douyu/external-orders/{order_id}/ck/copy" in paths
|
assert "/api/douyu/external-orders/{order_id}/ck/copy" in paths
|
||||||
assert "/api/douyu/external-orders/usernames/export" in paths
|
assert "/api/douyu/external-orders/usernames/export" in paths
|
||||||
|
assert "/api/douyu/external-orders/balances/query" in paths
|
||||||
|
assert "/api/douyu/external-orders/{order_id}/balances/query" in paths
|
||||||
|
assert "/api/douyu/external-orders/activity/query" in paths
|
||||||
|
assert "/api/douyu/external-orders/{order_id}/activity/query" in paths
|
||||||
assert "/api/douyu/collab-shops/{shop_id}" in paths
|
assert "/api/douyu/collab-shops/{shop_id}" in paths
|
||||||
assert "put" in paths["/api/douyu/collab-shops/{shop_id}"]
|
assert "put" in paths["/api/douyu/collab-shops/{shop_id}"]
|
||||||
assert "delete" in paths["/api/douyu/collab-shops/{shop_id}"]
|
assert "delete" in paths["/api/douyu/collab-shops/{shop_id}"]
|
||||||
|
|||||||
@@ -79,6 +79,22 @@ def _publish_change(current: User, *, scope: str, action: str) -> None:
|
|||||||
collab_events.publish(scope=scope, action=action, actor_id=int(current.id))
|
collab_events.publish(scope=scope, action=action, actor_id=int(current.id))
|
||||||
|
|
||||||
|
|
||||||
|
def _query_result_out(db: Session, result: dict) -> dict:
|
||||||
|
return {
|
||||||
|
"order": _one(db, result["order"]),
|
||||||
|
"metric": collab.serialize_metric(result["metric"]),
|
||||||
|
"scope": result["scope"],
|
||||||
|
"verdict": result["verdict"],
|
||||||
|
"message": result["message"],
|
||||||
|
"points_reason": result["points_reason"],
|
||||||
|
"fish_fin_balance": result["fish_fin_balance"],
|
||||||
|
"points_balance": result["points_balance"],
|
||||||
|
"required_total": result["required_total"],
|
||||||
|
"surplus": result["surplus"],
|
||||||
|
"errors": result["errors"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.websocket("/external-orders/ws")
|
@router.websocket("/external-orders/ws")
|
||||||
async def ws_external_orders(websocket: WebSocket):
|
async def ws_external_orders(websocket: WebSocket):
|
||||||
"""协作台账实时失效通知:前端收到后按自己的筛选静默重拉。"""
|
"""协作台账实时失效通知:前端收到后按自己的筛选静默重拉。"""
|
||||||
@@ -400,18 +416,29 @@ def query_external_order_activity(
|
|||||||
order = collab.get_order(db, order_id)
|
order = collab.get_order(db, order_id)
|
||||||
result = collab.query_activity(db, current, order, source="manual")
|
result = collab.query_activity(db, current, order, source="manual")
|
||||||
_publish_change(current, scope="orders", action="order.activity")
|
_publish_change(current, scope="orders", action="order.activity")
|
||||||
return {
|
return _query_result_out(db, result)
|
||||||
"order": _one(db, result["order"]),
|
except collab.CollabError as exc:
|
||||||
"metric": collab.serialize_metric(result["metric"]),
|
raise _http_error(exc) from exc
|
||||||
"verdict": result["verdict"],
|
|
||||||
"message": result["message"],
|
|
||||||
"points_reason": result["points_reason"],
|
@router.post("/external-orders/{order_id}/balances/query")
|
||||||
"fish_fin_balance": result["fish_fin_balance"],
|
def query_external_order_balances(
|
||||||
"points_balance": result["points_balance"],
|
order_id: int,
|
||||||
"required_total": result["required_total"],
|
db: Session = Depends(get_db),
|
||||||
"surplus": result["surplus"],
|
current: User = Depends(require_permission("douyu:collab:edit")),
|
||||||
"errors": result["errors"],
|
):
|
||||||
}
|
"""只刷新鱼翅和手册积分,不查兑换记录、不判定、不改状态。"""
|
||||||
|
try:
|
||||||
|
order = collab.get_order(db, order_id)
|
||||||
|
result = collab.query_activity(
|
||||||
|
db,
|
||||||
|
current,
|
||||||
|
order,
|
||||||
|
source="balance_manual",
|
||||||
|
include_records=False,
|
||||||
|
)
|
||||||
|
_publish_change(current, scope="orders", action="order.balances")
|
||||||
|
return _query_result_out(db, result)
|
||||||
except collab.CollabError as exc:
|
except collab.CollabError as exc:
|
||||||
raise _http_error(exc) from exc
|
raise _http_error(exc) from exc
|
||||||
|
|
||||||
@@ -467,6 +494,42 @@ def set_external_order_status_batch(
|
|||||||
return {"results": results, "success": all(item["success"] for item in results)}
|
return {"results": results, "success": all(item["success"] for item in results)}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/external-orders/balances/query")
|
||||||
|
def query_external_orders_balances(
|
||||||
|
ids: list[int],
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
current: User = Depends(require_permission("douyu:collab:edit")),
|
||||||
|
):
|
||||||
|
"""批量刷新鱼翅和手册积分,不查兑换记录、不判定、不改状态。"""
|
||||||
|
results: list[dict] = []
|
||||||
|
for order_id in ids:
|
||||||
|
try:
|
||||||
|
order = collab.get_order(db, order_id)
|
||||||
|
result = collab.query_activity(
|
||||||
|
db,
|
||||||
|
current,
|
||||||
|
order,
|
||||||
|
source="balance_batch",
|
||||||
|
include_records=False,
|
||||||
|
)
|
||||||
|
results.append(
|
||||||
|
{
|
||||||
|
"id": order_id,
|
||||||
|
"success": True,
|
||||||
|
"message": result["message"],
|
||||||
|
"fish_fin_balance": result["fish_fin_balance"],
|
||||||
|
"points_balance": result["points_balance"],
|
||||||
|
"points_reason": result["points_reason"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
db.rollback()
|
||||||
|
results.append({"id": order_id, "success": False, "message": str(exc)})
|
||||||
|
if any(item["success"] for item in results):
|
||||||
|
_publish_change(current, scope="orders", action="order.balances_batch")
|
||||||
|
return {"results": results, "success": all(item["success"] for item in results)}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/external-orders/activity/query")
|
@router.post("/external-orders/activity/query")
|
||||||
def query_external_orders_activity(
|
def query_external_orders_activity(
|
||||||
ids: list[int],
|
ids: list[int],
|
||||||
|
|||||||
@@ -1042,9 +1042,10 @@ def query_activity(
|
|||||||
*,
|
*,
|
||||||
client: Any | None = None,
|
client: Any | None = None,
|
||||||
source: str = "manual",
|
source: str = "manual",
|
||||||
|
include_records: bool = True,
|
||||||
commit: bool = True,
|
commit: bool = True,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""查询鱼翅、积分和当前账号兑换记录,落一条快照。"""
|
"""查询鱼翅、积分;``include_records`` 时同时查兑换记录并判定。"""
|
||||||
account = account_by_id(db, order.account_id) or account_by_username(
|
account = account_by_id(db, order.account_id) or account_by_username(
|
||||||
db, order.account_username
|
db, order.account_username
|
||||||
)
|
)
|
||||||
@@ -1067,24 +1068,25 @@ def query_activity(
|
|||||||
gold = active_client.gold_account()
|
gold = active_client.gold_account()
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
errors.append(f"鱼翅: {exc}")
|
errors.append(f"鱼翅: {exc}")
|
||||||
try:
|
if include_records:
|
||||||
records = (
|
try:
|
||||||
active_client.exchange_records(
|
records = (
|
||||||
manual_id=str(config.manual_id or ""), limit=100, offset=0
|
active_client.exchange_records(
|
||||||
).get("records")
|
manual_id=str(config.manual_id or ""), limit=100, offset=0
|
||||||
or []
|
).get("records")
|
||||||
)
|
or []
|
||||||
except Exception as exc: # noqa: BLE001
|
)
|
||||||
errors.append(f"兑换记录: {exc}")
|
except Exception as exc: # noqa: BLE001
|
||||||
try:
|
errors.append(f"兑换记录: {exc}")
|
||||||
orders = (
|
try:
|
||||||
active_client.exchange_orders(
|
orders = (
|
||||||
manual_id=str(config.manual_id or ""), page=1, page_size=10
|
active_client.exchange_orders(
|
||||||
).get("orders")
|
manual_id=str(config.manual_id or ""), page=1, page_size=10
|
||||||
or []
|
).get("orders")
|
||||||
)
|
or []
|
||||||
except Exception as exc: # noqa: BLE001
|
)
|
||||||
errors.append(f"锁单: {exc}")
|
except Exception as exc: # noqa: BLE001
|
||||||
|
errors.append(f"锁单: {exc}")
|
||||||
|
|
||||||
manual_score = _to_int(elite.get("manual_score"))
|
manual_score = _to_int(elite.get("manual_score"))
|
||||||
fish = _to_int(gold.get("gold"))
|
fish = _to_int(gold.get("gold"))
|
||||||
@@ -1107,7 +1109,11 @@ def query_activity(
|
|||||||
threshold = normalize_threshold(order.points_threshold)
|
threshold = normalize_threshold(order.points_threshold)
|
||||||
order.points_threshold = threshold
|
order.points_threshold = threshold
|
||||||
|
|
||||||
if errors:
|
matched = 0
|
||||||
|
if not include_records:
|
||||||
|
verdict = VERDICT_UNKNOWN
|
||||||
|
message = ";".join(errors) if errors else "仅查询鱼翅与积分,未查询兑换记录"
|
||||||
|
elif errors:
|
||||||
verdict = VERDICT_UNKNOWN
|
verdict = VERDICT_UNKNOWN
|
||||||
message = ";".join(errors)
|
message = ";".join(errors)
|
||||||
elif not order.gift_name:
|
elif not order.gift_name:
|
||||||
@@ -1160,7 +1166,7 @@ def query_activity(
|
|||||||
points_required_total=required_total,
|
points_required_total=required_total,
|
||||||
points_surplus=surplus,
|
points_surplus=surplus,
|
||||||
records_total=len(records),
|
records_total=len(records),
|
||||||
records_matched=_match_gift(records, order.gift_name),
|
records_matched=matched,
|
||||||
verdict=verdict,
|
verdict=verdict,
|
||||||
message=message,
|
message=message,
|
||||||
source=_text(source, max_length=16) or "manual",
|
source=_text(source, max_length=16) or "manual",
|
||||||
@@ -1183,6 +1189,7 @@ def query_activity(
|
|||||||
action="douyu:collab:activity",
|
action="douyu:collab:activity",
|
||||||
target=_status_audit_target(order),
|
target=_status_audit_target(order),
|
||||||
detail={
|
detail={
|
||||||
|
"scope": "activity" if include_records else "balances",
|
||||||
"verdict": verdict,
|
"verdict": verdict,
|
||||||
"message": message,
|
"message": message,
|
||||||
"fish_fin_balance": fish,
|
"fish_fin_balance": fish,
|
||||||
@@ -1193,40 +1200,41 @@ def query_activity(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# 已上脚本的单,兑换记录命中/未命中是业务事实;其它状态只记快照。
|
# 只有查兑换记录时才做业务判定;余额查询保持只读,不动状态。
|
||||||
if order.status == STATUS_SCRIPTED and verdict == VERDICT_SUCCESS:
|
if include_records:
|
||||||
set_status(
|
if order.status == STATUS_SCRIPTED and verdict == VERDICT_SUCCESS:
|
||||||
db,
|
set_status(
|
||||||
current,
|
db,
|
||||||
order,
|
current,
|
||||||
status=STATUS_DONE,
|
order,
|
||||||
reason=message,
|
status=STATUS_DONE,
|
||||||
expected_version=None,
|
reason=message,
|
||||||
commit=False,
|
expected_version=None,
|
||||||
)
|
commit=False,
|
||||||
elif order.status == STATUS_SCRIPTED and verdict == VERDICT_FAILED:
|
)
|
||||||
set_status(
|
elif order.status == STATUS_SCRIPTED and verdict == VERDICT_FAILED:
|
||||||
db,
|
set_status(
|
||||||
current,
|
db,
|
||||||
order,
|
current,
|
||||||
status=STATUS_FAILED,
|
order,
|
||||||
reason=message,
|
status=STATUS_FAILED,
|
||||||
expected_version=None,
|
reason=message,
|
||||||
commit=False,
|
expected_version=None,
|
||||||
)
|
commit=False,
|
||||||
elif order.status in (STATUS_RECHARGED, STATUS_SCRIPTED) and reason in (
|
)
|
||||||
"shortage",
|
elif order.status in (STATUS_RECHARGED, STATUS_SCRIPTED) and reason in (
|
||||||
"excess",
|
"shortage",
|
||||||
):
|
"excess",
|
||||||
set_status(
|
):
|
||||||
db,
|
set_status(
|
||||||
current,
|
db,
|
||||||
order,
|
current,
|
||||||
status=STATUS_POINTS_ERROR,
|
order,
|
||||||
reason=points_message,
|
status=STATUS_POINTS_ERROR,
|
||||||
expected_version=None,
|
reason=points_message,
|
||||||
commit=False,
|
expected_version=None,
|
||||||
)
|
commit=False,
|
||||||
|
)
|
||||||
|
|
||||||
if commit:
|
if commit:
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -1235,6 +1243,7 @@ def query_activity(
|
|||||||
return {
|
return {
|
||||||
"order": order,
|
"order": order,
|
||||||
"metric": metric,
|
"metric": metric,
|
||||||
|
"scope": "activity" if include_records else "balances",
|
||||||
"verdict": verdict,
|
"verdict": verdict,
|
||||||
"message": message,
|
"message": message,
|
||||||
"points_reason": reason,
|
"points_reason": reason,
|
||||||
@@ -1487,9 +1496,14 @@ def serialized_goods(db: Session) -> list[dict[str, Any]]:
|
|||||||
|
|
||||||
def _metric_views(
|
def _metric_views(
|
||||||
db: Session, order_ids: list[int]
|
db: Session, order_ids: list[int]
|
||||||
) -> dict[int, DouyuExternalOrderMetric]:
|
) -> tuple[dict[int, DouyuExternalOrderMetric], dict[int, DouyuExternalOrderMetric]]:
|
||||||
|
"""返回最近一条快照,以及最近一条带兑换判定的快照。
|
||||||
|
|
||||||
|
余额查询也会落快照,但它的 verdict 是 unknown;列表上的兑换结论应沿用
|
||||||
|
最近一次「查兑换记录」的结果,不能被余额刷新覆盖。
|
||||||
|
"""
|
||||||
if not order_ids:
|
if not order_ids:
|
||||||
return {}
|
return {}, {}
|
||||||
rows = (
|
rows = (
|
||||||
db.query(DouyuExternalOrderMetric)
|
db.query(DouyuExternalOrderMetric)
|
||||||
.filter(DouyuExternalOrderMetric.order_id.in_(order_ids))
|
.filter(DouyuExternalOrderMetric.order_id.in_(order_ids))
|
||||||
@@ -1499,10 +1513,14 @@ def _metric_views(
|
|||||||
)
|
)
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
result: dict[int, DouyuExternalOrderMetric] = {}
|
latest: dict[int, DouyuExternalOrderMetric] = {}
|
||||||
|
verdict_latest: dict[int, DouyuExternalOrderMetric] = {}
|
||||||
for row in rows:
|
for row in rows:
|
||||||
result.setdefault(int(row.order_id), row)
|
order_id = int(row.order_id)
|
||||||
return result
|
latest.setdefault(order_id, row)
|
||||||
|
if not str(row.source or "").startswith("balance"):
|
||||||
|
verdict_latest.setdefault(order_id, row)
|
||||||
|
return latest, verdict_latest
|
||||||
|
|
||||||
|
|
||||||
def account_clusters(
|
def account_clusters(
|
||||||
@@ -1556,6 +1574,7 @@ def serialize_order(
|
|||||||
accounts: Mapping[int, Account] | None = None,
|
accounts: Mapping[int, Account] | None = None,
|
||||||
cluster: Mapping[str, Any] | None = None,
|
cluster: Mapping[str, Any] | None = None,
|
||||||
metric: DouyuExternalOrderMetric | None = None,
|
metric: DouyuExternalOrderMetric | None = None,
|
||||||
|
verdict_metric: DouyuExternalOrderMetric | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
users = users or {}
|
users = users or {}
|
||||||
accounts = accounts or {}
|
accounts = accounts or {}
|
||||||
@@ -1630,16 +1649,16 @@ def serialize_order(
|
|||||||
"account_cluster": dict(cluster) if cluster else None,
|
"account_cluster": dict(cluster) if cluster else None,
|
||||||
"last_metric_at": metric.created_at if metric else None,
|
"last_metric_at": metric.created_at if metric else None,
|
||||||
"last_metric_message": metric.message if metric else "",
|
"last_metric_message": metric.message if metric else "",
|
||||||
"last_verdict": metric.verdict if metric else "",
|
"last_verdict": verdict_metric.verdict if verdict_metric else "",
|
||||||
"last_verdict_label": {
|
"last_verdict_label": {
|
||||||
VERDICT_SUCCESS: "兑换成功",
|
VERDICT_SUCCESS: "兑换成功",
|
||||||
VERDICT_FAILED: "兑换失败",
|
VERDICT_FAILED: "兑换失败",
|
||||||
VERDICT_UNKNOWN: "无法判定",
|
VERDICT_UNKNOWN: "无法判定",
|
||||||
}.get(metric.verdict, "")
|
}.get(verdict_metric.verdict, "")
|
||||||
if metric
|
if verdict_metric
|
||||||
else "",
|
else "",
|
||||||
"last_records_total": metric.records_total if metric else 0,
|
"last_records_total": verdict_metric.records_total if verdict_metric else 0,
|
||||||
"last_records_matched": metric.records_matched if metric else 0,
|
"last_records_matched": verdict_metric.records_matched if verdict_metric else 0,
|
||||||
}
|
}
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
@@ -1761,7 +1780,7 @@ def list_orders(
|
|||||||
db, {int(order.account_id) for order in orders if order.account_id}
|
db, {int(order.account_id) for order in orders if order.account_id}
|
||||||
)
|
)
|
||||||
clusters = account_clusters(db, orders)
|
clusters = account_clusters(db, orders)
|
||||||
metrics = _metric_views(db, [int(order.id) for order in orders])
|
metrics, verdict_metrics = _metric_views(db, [int(order.id) for order in orders])
|
||||||
items = [
|
items = [
|
||||||
serialize_order(
|
serialize_order(
|
||||||
order,
|
order,
|
||||||
@@ -1769,6 +1788,7 @@ def list_orders(
|
|||||||
accounts=accounts,
|
accounts=accounts,
|
||||||
cluster=clusters.get(int(order.account_id)) if order.account_id else None,
|
cluster=clusters.get(int(order.account_id)) if order.account_id else None,
|
||||||
metric=metrics.get(int(order.id)),
|
metric=metrics.get(int(order.id)),
|
||||||
|
verdict_metric=verdict_metrics.get(int(order.id)),
|
||||||
)
|
)
|
||||||
for order in orders
|
for order in orders
|
||||||
]
|
]
|
||||||
@@ -1802,13 +1822,14 @@ def serialize_one(db: Session, order: DouyuExternalOrder) -> dict[str, Any]:
|
|||||||
db, {int(order.account_id)} if order.account_id else set()
|
db, {int(order.account_id)} if order.account_id else set()
|
||||||
)
|
)
|
||||||
clusters = account_clusters(db, [order])
|
clusters = account_clusters(db, [order])
|
||||||
metrics = _metric_views(db, [int(order.id)])
|
metrics, verdict_metrics = _metric_views(db, [int(order.id)])
|
||||||
return serialize_order(
|
return serialize_order(
|
||||||
order,
|
order,
|
||||||
users=users,
|
users=users,
|
||||||
accounts=accounts,
|
accounts=accounts,
|
||||||
cluster=clusters.get(int(order.account_id)) if order.account_id else None,
|
cluster=clusters.get(int(order.account_id)) if order.account_id else None,
|
||||||
metric=metrics.get(int(order.id)),
|
metric=metrics.get(int(order.id)),
|
||||||
|
verdict_metric=verdict_metrics.get(int(order.id)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ export interface DouyuCollabTimeline {
|
|||||||
export interface DouyuCollabQueryResult {
|
export interface DouyuCollabQueryResult {
|
||||||
order: DouyuCollabOrder;
|
order: DouyuCollabOrder;
|
||||||
metric: DouyuCollabMetric;
|
metric: DouyuCollabMetric;
|
||||||
|
scope: 'activity' | 'balances';
|
||||||
verdict: string;
|
verdict: string;
|
||||||
message: string;
|
message: string;
|
||||||
points_reason: string;
|
points_reason: string;
|
||||||
@@ -347,9 +348,13 @@ export const douyuCollabApi = {
|
|||||||
{ results: Array<{ id: number; success: boolean; ck_status?: string; role?: string; message?: string }>; success: boolean },
|
{ results: Array<{ id: number; success: boolean; ck_status?: string; role?: string; message?: string }>; success: boolean },
|
||||||
{ results: Array<{ id: number; success: boolean; ck_status?: string; role?: string; message?: string }>; success: boolean }
|
{ results: Array<{ id: number; success: boolean; ck_status?: string; role?: string; message?: string }>; success: boolean }
|
||||||
>('/douyu/external-orders/verify/batch', ids),
|
>('/douyu/external-orders/verify/batch', ids),
|
||||||
queryActivity: (id: number) =>
|
queryActivity: (id: number) => api.post<DouyuCollabQueryResult, DouyuCollabQueryResult>(
|
||||||
|
`/douyu/external-orders/${id}/activity/query`,
|
||||||
|
),
|
||||||
|
/** 只刷新鱼翅与手册积分,不查兑换记录、不判定、不改状态 */
|
||||||
|
queryBalances: (id: number) =>
|
||||||
api.post<DouyuCollabQueryResult, DouyuCollabQueryResult>(
|
api.post<DouyuCollabQueryResult, DouyuCollabQueryResult>(
|
||||||
`/douyu/external-orders/${id}/activity/query`,
|
`/douyu/external-orders/${id}/balances/query`,
|
||||||
),
|
),
|
||||||
setStatus: (id: number, data: {
|
setStatus: (id: number, data: {
|
||||||
status: DouyuCollabStatus;
|
status: DouyuCollabStatus;
|
||||||
@@ -371,6 +376,12 @@ export const douyuCollabApi = {
|
|||||||
'/douyu/external-orders/activity/query',
|
'/douyu/external-orders/activity/query',
|
||||||
ids,
|
ids,
|
||||||
),
|
),
|
||||||
|
/** 批量只刷新鱼翅/积分,不查兑换记录、不判定、不改状态 */
|
||||||
|
batchBalances: (ids: number[]) =>
|
||||||
|
api.post<{ results: Array<Record<string, unknown>>; success: boolean }, { results: Array<Record<string, unknown>>; success: boolean }>(
|
||||||
|
'/douyu/external-orders/balances/query',
|
||||||
|
ids,
|
||||||
|
),
|
||||||
updateImages: (id: number, home_images: string[], expected_version?: number) =>
|
updateImages: (id: number, home_images: string[], expected_version?: number) =>
|
||||||
api.post<DouyuCollabOrder, DouyuCollabOrder>(`/douyu/external-orders/${id}/images`, {
|
api.post<DouyuCollabOrder, DouyuCollabOrder>(`/douyu/external-orders/${id}/images`, {
|
||||||
home_images,
|
home_images,
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import {
|
|||||||
PlusOutlined,
|
PlusOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
SafetyCertificateOutlined,
|
SafetyCertificateOutlined,
|
||||||
|
SearchOutlined,
|
||||||
ShopOutlined,
|
ShopOutlined,
|
||||||
SyncOutlined,
|
SyncOutlined,
|
||||||
TagOutlined,
|
TagOutlined,
|
||||||
@@ -214,6 +215,8 @@ export default function DouyuCollabPage() {
|
|||||||
const [gifts, setGifts] = useState<string[]>([]);
|
const [gifts, setGifts] = useState<string[]>([]);
|
||||||
const [goods, setGoods] = useState<DouyuCollabGoods[]>([]);
|
const [goods, setGoods] = useState<DouyuCollabGoods[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [balanceQuerying, setBalanceQuerying] = useState(false);
|
||||||
|
const [recordQuerying, setRecordQuerying] = useState(false);
|
||||||
const [goodsLoading, setGoodsLoading] = useState(false);
|
const [goodsLoading, setGoodsLoading] = useState(false);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [pageSize, setPageSize] = useState(20);
|
const [pageSize, setPageSize] = useState(20);
|
||||||
@@ -631,18 +634,35 @@ export default function DouyuCollabPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryOrder = async (order: DouyuCollabOrder) => {
|
const queryOneBalances = async (order: DouyuCollabOrder) => {
|
||||||
try {
|
try {
|
||||||
message.loading({ content: '正在查询鱼翅、积分与兑换记录…', key: 'dy-collab-query' });
|
message.loading({ content: '正在查询鱼翅、积分…', key: 'dy-collab-balance' });
|
||||||
const result = await douyuCollabApi.queryActivity(order.id);
|
const result = await douyuCollabApi.queryBalances(order.id);
|
||||||
message.success({
|
message.success({
|
||||||
content: `${result.message}${result.points_reason ? `;${pointsReasonMeta(result.points_reason).label}` : ''}`,
|
content: `鱼翅 ${result.fish_fin_balance ?? '-'} / 积分 ${result.points_balance ?? '-'}${
|
||||||
key: 'dy-collab-query',
|
result.points_reason ? `;${pointsReasonMeta(result.points_reason).label}` : ''
|
||||||
|
}`,
|
||||||
|
key: 'dy-collab-balance',
|
||||||
});
|
});
|
||||||
refreshAll();
|
refreshAll();
|
||||||
if (detail?.id === order.id) void openDetail(order);
|
if (detail?.id === order.id) void openDetail(order);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error({ content: getErrorMessage(error), key: 'dy-collab-query' });
|
message.error({ content: getErrorMessage(error), key: 'dy-collab-balance' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const queryOneRecords = async (order: DouyuCollabOrder) => {
|
||||||
|
try {
|
||||||
|
message.loading({ content: '正在查询兑换记录…', key: 'dy-collab-records' });
|
||||||
|
const result = await douyuCollabApi.queryActivity(order.id);
|
||||||
|
message.success({
|
||||||
|
content: `${result.message}${result.points_reason ? `;${pointsReasonMeta(result.points_reason).label}` : ''}`,
|
||||||
|
key: 'dy-collab-records',
|
||||||
|
});
|
||||||
|
refreshAll();
|
||||||
|
if (detail?.id === order.id) void openDetail(order);
|
||||||
|
} catch (error) {
|
||||||
|
message.error({ content: getErrorMessage(error), key: 'dy-collab-records' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -716,21 +736,39 @@ export default function DouyuCollabPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const batchQuery = async () => {
|
const batchQueryBalances = async () => {
|
||||||
if (selectedIds.length === 0) {
|
if (selectedIds.length === 0) {
|
||||||
message.warning('请先勾选协作单');
|
message.warning('请先勾选协作单');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoading(true);
|
setBalanceQuerying(true);
|
||||||
try {
|
try {
|
||||||
const result = await douyuCollabApi.batchActivity(selectedIds);
|
const result = await douyuCollabApi.batchBalances(selectedIds);
|
||||||
const ok = result.results.filter((item) => item.success).length;
|
const ok = result.results.filter((item) => item.success).length;
|
||||||
message.success(`查询完成:${ok}/${result.results.length}`);
|
message.success(`鱼翅/积分查询完成:${ok}/${result.results.length}`);
|
||||||
refreshAll();
|
refreshAll();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(getErrorMessage(error));
|
message.error(getErrorMessage(error));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setBalanceQuerying(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const batchQueryRecords = async () => {
|
||||||
|
if (selectedIds.length === 0) {
|
||||||
|
message.warning('请先勾选协作单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setRecordQuerying(true);
|
||||||
|
try {
|
||||||
|
const result = await douyuCollabApi.batchActivity(selectedIds);
|
||||||
|
const ok = result.results.filter((item) => item.success).length;
|
||||||
|
message.success(`兑换记录查询完成:${ok}/${result.results.length}`);
|
||||||
|
refreshAll();
|
||||||
|
} catch (error) {
|
||||||
|
message.error(getErrorMessage(error));
|
||||||
|
} finally {
|
||||||
|
setRecordQuerying(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1158,7 +1196,7 @@ export default function DouyuCollabPage() {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 400,
|
width: 500,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
@@ -1196,9 +1234,16 @@ export default function DouyuCollabPage() {
|
|||||||
校验
|
校验
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Button size="small" icon={<SyncOutlined />} onClick={() => void queryOrder(row)}>
|
<Tooltip title="只刷新鱼翅与手册积分,不查兑换记录,也不会改状态">
|
||||||
查询
|
<Button size="small" icon={<SyncOutlined />} onClick={() => void queryOneBalances(row)}>
|
||||||
</Button>
|
查鱼翅/积分
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="查询当前账号兑换记录,命中礼包名后自动推进订单状态">
|
||||||
|
<Button size="small" icon={<SearchOutlined />} onClick={() => void queryOneRecords(row)}>
|
||||||
|
查兑换记录
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
items: [
|
items: [
|
||||||
@@ -1501,13 +1546,26 @@ export default function DouyuCollabPage() {
|
|||||||
<Button icon={<CheckCircleOutlined />} disabled={selectedCount === 0} onClick={openBatchStatus}>
|
<Button icon={<CheckCircleOutlined />} disabled={selectedCount === 0} onClick={openBatchStatus}>
|
||||||
批量改状态{selectedCount > 0 ? `(${selectedCount})` : ''}
|
批量改状态{selectedCount > 0 ? `(${selectedCount})` : ''}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Tooltip title="只刷新鱼翅与手册积分,不查兑换记录,也不会改状态">
|
||||||
icon={<SyncOutlined />}
|
<Button
|
||||||
disabled={selectedCount === 0 || loading}
|
icon={<SyncOutlined />}
|
||||||
onClick={() => void batchQuery()}
|
disabled={selectedCount === 0 || loading || balanceQuerying}
|
||||||
>
|
loading={balanceQuerying}
|
||||||
批量查鱼翅/积分{selectedCount > 0 ? `(${selectedCount})` : ''}
|
onClick={() => void batchQueryBalances()}
|
||||||
</Button>
|
>
|
||||||
|
批量查鱼翅/积分{selectedCount > 0 ? `(${selectedCount})` : ''}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="查询勾选订单的兑换记录;命中礼包名后自动改状态,积分短缺/超额也会标记">
|
||||||
|
<Button
|
||||||
|
icon={<SearchOutlined />}
|
||||||
|
disabled={selectedCount === 0 || loading || recordQuerying}
|
||||||
|
loading={recordQuerying}
|
||||||
|
onClick={() => void batchQueryRecords()}
|
||||||
|
>
|
||||||
|
批量查兑换记录{selectedCount > 0 ? `(${selectedCount})` : ''}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
<Button
|
<Button
|
||||||
icon={<InboxOutlined />}
|
icon={<InboxOutlined />}
|
||||||
disabled={!canArchiveSelected || loading}
|
disabled={!canArchiveSelected || loading}
|
||||||
@@ -1552,7 +1610,7 @@ export default function DouyuCollabPage() {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
dataSource={orders}
|
dataSource={orders}
|
||||||
scroll={{ x: 2090 }}
|
scroll={{ x: 2190 }}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
selectedRowKeys,
|
selectedRowKeys,
|
||||||
onChange: (keys) => setSelectedRowKeys(keys),
|
onChange: (keys) => setSelectedRowKeys(keys),
|
||||||
@@ -1933,9 +1991,12 @@ export default function DouyuCollabPage() {
|
|||||||
<Button icon={<SafetyCertificateOutlined />} onClick={() => void verifyOrder(detail)}>
|
<Button icon={<SafetyCertificateOutlined />} onClick={() => void verifyOrder(detail)}>
|
||||||
校验
|
校验
|
||||||
</Button>
|
</Button>
|
||||||
<Button icon={<SyncOutlined />} onClick={() => void queryOrder(detail)}>
|
<Button icon={<SyncOutlined />} onClick={() => void queryOneBalances(detail)}>
|
||||||
查鱼翅/积分
|
查鱼翅/积分
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button icon={<SearchOutlined />} onClick={() => void queryOneRecords(detail)}>
|
||||||
|
查兑换记录
|
||||||
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
) : undefined
|
) : undefined
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user