feat: 强化用量计费状态机,新增钱包每日消费汇总分类账

- 将 usage.billing_status 默认值从 settled 改为 pending,完善
  pending -> settled/void 的状态转换逻辑,确保终态不可逆
- 新增 WalletDailyUsageLedger 模型和聚合服务,按账单日汇总
  每个钱包的消费金额、请求数和 token 用量
- 前端钱包中心页面集成每日消费流水展示,支持与充值记录混合
  排序和分页
- 新增两个数据库迁移:修复历史数据状态一致性、创建每日汇总表
- 补充计费状态机单元测试

Closes #218

Co-authored-by: LewisPen <LewisPen@nyadoo.com>
This commit is contained in:
fawney19
2026-03-11 15:05:11 +08:00
parent 6235c772ac
commit 04ab4bd9f2
25 changed files with 1212 additions and 121 deletions

View File

@@ -48,3 +48,33 @@ async def test_poll_task_status_routes_gemini_video_to_gemini(
assert result.status == VideoStatus.PROCESSING
assert poll_gemini.await_count == 1
assert poll_openai.await_count == 0
@pytest.mark.asyncio
async def test_update_task_after_poll_skips_terminal_cancelled_task(
monkeypatch: pytest.MonkeyPatch,
) -> None:
finalize = AsyncMock()
adapter = VideoTaskPollerAdapter(finalize_video_task_fn=finalize)
cancelled_task = SimpleNamespace(
id="t1",
status=VideoStatus.CANCELLED.value,
)
session = MagicMock()
session.__enter__.return_value = session
session.__exit__.return_value = None
session.get.return_value = cancelled_task
monkeypatch.setattr("src.services.task.video.poller_adapter.create_session", lambda: session)
await adapter.update_task_after_poll(
task_id="t1",
result=InternalVideoPollResult(status=VideoStatus.COMPLETED),
ctx=None,
redis_client=None,
)
finalize.assert_not_awaited()
session.commit.assert_not_called()