fix(dashboard): moonshot voucher/cash leftover follows bucket balance (#12733)

Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

Available 0% com Voucher 100% e Cash 100% não é estado de carteira que exista — foi o sinal certo para puxar o fio. Tratar leftover como booleano só para Available e cravar 100% nos outros dois buckets é o tipo de defeito que passa despercebido enquanto a conta tem saldo.

Os dois testes cobrem os dois lados: o produtor e o caminho até `getQuotaRemainingPercentage` com `isCredits` + CNY.
This commit is contained in:
Bob.Hou
2026-09-07 07:57:02 -04:00
committed by GitHub
parent d4d2e68a1f
commit 600abe68d0
5 changed files with 183 additions and 10 deletions

View File

@@ -434,7 +434,32 @@ export function isKiloPassDisplayRow(quota: any): boolean {
return quota?.kiloPass === true || quota?.name === KILO_PASS_DISPLAY_ROW;
}
function parseMoonshotBalanceQuota(quotaKey: string, quota: any) {
if (quotaKey !== "available" && quotaKey !== "voucher" && quotaKey !== "cash") {
return normalizeQuotaEntry(quotaKey, quota);
}
const remaining = Math.max(0, Number(quota?.remaining ?? 0));
const currency = quota?.currency || "CNY";
const remainingPercentage =
safePercentage(quota?.remainingPercentage) ?? (remaining > 0 ? 100 : 0);
return buildCreditsQuota(quotaKey, remaining, remainingPercentage, {
currency,
displayName: quota?.displayName,
});
}
function parseMoonshotBalance(data: any) {
return quotaEntries(data).map(([quotaKey, quota]) => parseMoonshotBalanceQuota(quotaKey, quota));
}
function looksLikeMoonshotBalance(data: any): boolean {
const quotas = data?.quotas;
if (!quotas || typeof quotas !== "object" || Array.isArray(quotas)) return false;
return "available" in quotas && "voucher" in quotas && "cash" in quotas;
}
function parseProviderQuotas(providerId: string, data: any) {
if (looksLikeMoonshotBalance(data)) return parseMoonshotBalance(data);
if (providerId === "github") return parseGithub(data);
if (["glm", "glm-cn", "glmt", "opencode-go"].includes(providerId)) return parseGlmFamily(data);
if (providerId === "antigravity" || providerId === "agy") return parseAntigravity(data);