feat(douyu): 拆分鱼翅积分与兑换记录查询
This commit is contained in:
@@ -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))
|
||||
|
||||
|
||||
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")
|
||||
async def ws_external_orders(websocket: WebSocket):
|
||||
"""协作台账实时失效通知:前端收到后按自己的筛选静默重拉。"""
|
||||
@@ -400,18 +416,29 @@ def query_external_order_activity(
|
||||
order = collab.get_order(db, order_id)
|
||||
result = collab.query_activity(db, current, order, source="manual")
|
||||
_publish_change(current, scope="orders", action="order.activity")
|
||||
return {
|
||||
"order": _one(db, result["order"]),
|
||||
"metric": collab.serialize_metric(result["metric"]),
|
||||
"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"],
|
||||
}
|
||||
return _query_result_out(db, result)
|
||||
except collab.CollabError as exc:
|
||||
raise _http_error(exc) from exc
|
||||
|
||||
|
||||
@router.post("/external-orders/{order_id}/balances/query")
|
||||
def query_external_order_balances(
|
||||
order_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("douyu:collab:edit")),
|
||||
):
|
||||
"""只刷新鱼翅和手册积分,不查兑换记录、不判定、不改状态。"""
|
||||
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:
|
||||
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)}
|
||||
|
||||
|
||||
@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")
|
||||
def query_external_orders_activity(
|
||||
ids: list[int],
|
||||
|
||||
@@ -1042,9 +1042,10 @@ def query_activity(
|
||||
*,
|
||||
client: Any | None = None,
|
||||
source: str = "manual",
|
||||
include_records: bool = True,
|
||||
commit: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
"""查询鱼翅、积分和当前账号兑换记录,落一条快照。"""
|
||||
"""查询鱼翅、积分;``include_records`` 时同时查兑换记录并判定。"""
|
||||
account = account_by_id(db, order.account_id) or account_by_username(
|
||||
db, order.account_username
|
||||
)
|
||||
@@ -1067,24 +1068,25 @@ def query_activity(
|
||||
gold = active_client.gold_account()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
errors.append(f"鱼翅: {exc}")
|
||||
try:
|
||||
records = (
|
||||
active_client.exchange_records(
|
||||
manual_id=str(config.manual_id or ""), limit=100, offset=0
|
||||
).get("records")
|
||||
or []
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
errors.append(f"兑换记录: {exc}")
|
||||
try:
|
||||
orders = (
|
||||
active_client.exchange_orders(
|
||||
manual_id=str(config.manual_id or ""), page=1, page_size=10
|
||||
).get("orders")
|
||||
or []
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
errors.append(f"锁单: {exc}")
|
||||
if include_records:
|
||||
try:
|
||||
records = (
|
||||
active_client.exchange_records(
|
||||
manual_id=str(config.manual_id or ""), limit=100, offset=0
|
||||
).get("records")
|
||||
or []
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
errors.append(f"兑换记录: {exc}")
|
||||
try:
|
||||
orders = (
|
||||
active_client.exchange_orders(
|
||||
manual_id=str(config.manual_id or ""), page=1, page_size=10
|
||||
).get("orders")
|
||||
or []
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
errors.append(f"锁单: {exc}")
|
||||
|
||||
manual_score = _to_int(elite.get("manual_score"))
|
||||
fish = _to_int(gold.get("gold"))
|
||||
@@ -1107,7 +1109,11 @@ def query_activity(
|
||||
threshold = normalize_threshold(order.points_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
|
||||
message = ";".join(errors)
|
||||
elif not order.gift_name:
|
||||
@@ -1160,7 +1166,7 @@ def query_activity(
|
||||
points_required_total=required_total,
|
||||
points_surplus=surplus,
|
||||
records_total=len(records),
|
||||
records_matched=_match_gift(records, order.gift_name),
|
||||
records_matched=matched,
|
||||
verdict=verdict,
|
||||
message=message,
|
||||
source=_text(source, max_length=16) or "manual",
|
||||
@@ -1183,6 +1189,7 @@ def query_activity(
|
||||
action="douyu:collab:activity",
|
||||
target=_status_audit_target(order),
|
||||
detail={
|
||||
"scope": "activity" if include_records else "balances",
|
||||
"verdict": verdict,
|
||||
"message": message,
|
||||
"fish_fin_balance": fish,
|
||||
@@ -1193,40 +1200,41 @@ def query_activity(
|
||||
},
|
||||
)
|
||||
|
||||
# 已上脚本的单,兑换记录命中/未命中是业务事实;其它状态只记快照。
|
||||
if order.status == STATUS_SCRIPTED and verdict == VERDICT_SUCCESS:
|
||||
set_status(
|
||||
db,
|
||||
current,
|
||||
order,
|
||||
status=STATUS_DONE,
|
||||
reason=message,
|
||||
expected_version=None,
|
||||
commit=False,
|
||||
)
|
||||
elif order.status == STATUS_SCRIPTED and verdict == VERDICT_FAILED:
|
||||
set_status(
|
||||
db,
|
||||
current,
|
||||
order,
|
||||
status=STATUS_FAILED,
|
||||
reason=message,
|
||||
expected_version=None,
|
||||
commit=False,
|
||||
)
|
||||
elif order.status in (STATUS_RECHARGED, STATUS_SCRIPTED) and reason in (
|
||||
"shortage",
|
||||
"excess",
|
||||
):
|
||||
set_status(
|
||||
db,
|
||||
current,
|
||||
order,
|
||||
status=STATUS_POINTS_ERROR,
|
||||
reason=points_message,
|
||||
expected_version=None,
|
||||
commit=False,
|
||||
)
|
||||
# 只有查兑换记录时才做业务判定;余额查询保持只读,不动状态。
|
||||
if include_records:
|
||||
if order.status == STATUS_SCRIPTED and verdict == VERDICT_SUCCESS:
|
||||
set_status(
|
||||
db,
|
||||
current,
|
||||
order,
|
||||
status=STATUS_DONE,
|
||||
reason=message,
|
||||
expected_version=None,
|
||||
commit=False,
|
||||
)
|
||||
elif order.status == STATUS_SCRIPTED and verdict == VERDICT_FAILED:
|
||||
set_status(
|
||||
db,
|
||||
current,
|
||||
order,
|
||||
status=STATUS_FAILED,
|
||||
reason=message,
|
||||
expected_version=None,
|
||||
commit=False,
|
||||
)
|
||||
elif order.status in (STATUS_RECHARGED, STATUS_SCRIPTED) and reason in (
|
||||
"shortage",
|
||||
"excess",
|
||||
):
|
||||
set_status(
|
||||
db,
|
||||
current,
|
||||
order,
|
||||
status=STATUS_POINTS_ERROR,
|
||||
reason=points_message,
|
||||
expected_version=None,
|
||||
commit=False,
|
||||
)
|
||||
|
||||
if commit:
|
||||
db.commit()
|
||||
@@ -1235,6 +1243,7 @@ def query_activity(
|
||||
return {
|
||||
"order": order,
|
||||
"metric": metric,
|
||||
"scope": "activity" if include_records else "balances",
|
||||
"verdict": verdict,
|
||||
"message": message,
|
||||
"points_reason": reason,
|
||||
@@ -1487,9 +1496,14 @@ def serialized_goods(db: Session) -> list[dict[str, Any]]:
|
||||
|
||||
def _metric_views(
|
||||
db: Session, order_ids: list[int]
|
||||
) -> dict[int, DouyuExternalOrderMetric]:
|
||||
) -> tuple[dict[int, DouyuExternalOrderMetric], dict[int, DouyuExternalOrderMetric]]:
|
||||
"""返回最近一条快照,以及最近一条带兑换判定的快照。
|
||||
|
||||
余额查询也会落快照,但它的 verdict 是 unknown;列表上的兑换结论应沿用
|
||||
最近一次「查兑换记录」的结果,不能被余额刷新覆盖。
|
||||
"""
|
||||
if not order_ids:
|
||||
return {}
|
||||
return {}, {}
|
||||
rows = (
|
||||
db.query(DouyuExternalOrderMetric)
|
||||
.filter(DouyuExternalOrderMetric.order_id.in_(order_ids))
|
||||
@@ -1499,10 +1513,14 @@ def _metric_views(
|
||||
)
|
||||
.all()
|
||||
)
|
||||
result: dict[int, DouyuExternalOrderMetric] = {}
|
||||
latest: dict[int, DouyuExternalOrderMetric] = {}
|
||||
verdict_latest: dict[int, DouyuExternalOrderMetric] = {}
|
||||
for row in rows:
|
||||
result.setdefault(int(row.order_id), row)
|
||||
return result
|
||||
order_id = int(row.order_id)
|
||||
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(
|
||||
@@ -1556,6 +1574,7 @@ def serialize_order(
|
||||
accounts: Mapping[int, Account] | None = None,
|
||||
cluster: Mapping[str, Any] | None = None,
|
||||
metric: DouyuExternalOrderMetric | None = None,
|
||||
verdict_metric: DouyuExternalOrderMetric | None = None,
|
||||
) -> dict[str, Any]:
|
||||
users = users or {}
|
||||
accounts = accounts or {}
|
||||
@@ -1630,16 +1649,16 @@ def serialize_order(
|
||||
"account_cluster": dict(cluster) if cluster else None,
|
||||
"last_metric_at": metric.created_at if metric else None,
|
||||
"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": {
|
||||
VERDICT_SUCCESS: "兑换成功",
|
||||
VERDICT_FAILED: "兑换失败",
|
||||
VERDICT_UNKNOWN: "无法判定",
|
||||
}.get(metric.verdict, "")
|
||||
if metric
|
||||
}.get(verdict_metric.verdict, "")
|
||||
if verdict_metric
|
||||
else "",
|
||||
"last_records_total": metric.records_total if metric else 0,
|
||||
"last_records_matched": metric.records_matched if metric else 0,
|
||||
"last_records_total": verdict_metric.records_total if verdict_metric else 0,
|
||||
"last_records_matched": verdict_metric.records_matched if verdict_metric else 0,
|
||||
}
|
||||
return payload
|
||||
|
||||
@@ -1761,7 +1780,7 @@ def list_orders(
|
||||
db, {int(order.account_id) for order in orders if order.account_id}
|
||||
)
|
||||
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 = [
|
||||
serialize_order(
|
||||
order,
|
||||
@@ -1769,6 +1788,7 @@ def list_orders(
|
||||
accounts=accounts,
|
||||
cluster=clusters.get(int(order.account_id)) if order.account_id else None,
|
||||
metric=metrics.get(int(order.id)),
|
||||
verdict_metric=verdict_metrics.get(int(order.id)),
|
||||
)
|
||||
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()
|
||||
)
|
||||
clusters = account_clusters(db, [order])
|
||||
metrics = _metric_views(db, [int(order.id)])
|
||||
metrics, verdict_metrics = _metric_views(db, [int(order.id)])
|
||||
return serialize_order(
|
||||
order,
|
||||
users=users,
|
||||
accounts=accounts,
|
||||
cluster=clusters.get(int(order.account_id)) if order.account_id else None,
|
||||
metric=metrics.get(int(order.id)),
|
||||
verdict_metric=verdict_metrics.get(int(order.id)),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user