From 4c07d9fcfb6790babb094d81d81f6dbfcc1b7da2 Mon Sep 17 00:00:00 2001 From: RWDai <27391645+RWDai@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:56:27 +0800 Subject: [PATCH] fix(admin): floor batch deductions at zero --- .../state/runtime/wallet/balance_mutations.rs | 6 ++++- .../src/tests/control/admin/users_batch.rs | 6 ++--- .../adapters/postgres/src/wallet.rs | 22 +++++++++++++------ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/apps/aether-gateway/src/state/runtime/wallet/balance_mutations.rs b/apps/aether-gateway/src/state/runtime/wallet/balance_mutations.rs index 70a9ee1eb..c6b6b1b39 100644 --- a/apps/aether-gateway/src/state/runtime/wallet/balance_mutations.rs +++ b/apps/aether-gateway/src/state/runtime/wallet/balance_mutations.rs @@ -226,7 +226,11 @@ impl AppState { let before_gift = wallet.gift_balance; let before_total = before_recharge + before_gift; let amount_usd = if clamp_deduction_to_available_balance && amount_usd < 0.0 { - -(-amount_usd).min(before_total.max(0.0)) + if before_total < 0.0 { + -before_total + } else { + -(-amount_usd).min(before_total) + } } else { amount_usd }; diff --git a/apps/aether-gateway/src/tests/control/admin/users_batch.rs b/apps/aether-gateway/src/tests/control/admin/users_batch.rs index bb34d61c8..35efa942e 100644 --- a/apps/aether-gateway/src/tests/control/admin/users_batch.rs +++ b/apps/aether-gateway/src/tests/control/admin/users_batch.rs @@ -581,7 +581,7 @@ async fn gateway_reports_wallet_limit_lookup_failure_as_unprocessed() { } #[tokio::test] -async fn gateway_reports_missing_wallet_and_skips_zero_delta_for_non_positive_balance() { +async fn gateway_reports_missing_wallet_and_floors_negative_balance_on_deduction() { let state = AppState::new() .expect("gateway should build") .with_auth_users_for_tests([sample_user("user-negative"), sample_user("user-no-wallet")]) @@ -607,8 +607,8 @@ async fn gateway_reports_missing_wallet_and_skips_zero_delta_for_non_positive_ba assert_eq!(result["failures"][0]["reason"], "用户钱包不可用"); let wallet = wallet_detail(&client, &gateway_url, "user-negative").await; - assert_eq!(wallet["balance"], -1.0); - assert_eq!(wallet["total_adjusted"], 0.0); + assert_eq!(wallet["balance"], 0.0); + assert_eq!(wallet["total_adjusted"], 1.0); gateway_handle.abort(); } diff --git a/crates/aether-data/adapters/postgres/src/wallet.rs b/crates/aether-data/adapters/postgres/src/wallet.rs index 23f524d54..8e89f7a1d 100644 --- a/crates/aether-data/adapters/postgres/src/wallet.rs +++ b/crates/aether-data/adapters/postgres/src/wallet.rs @@ -812,7 +812,12 @@ impl SqlxWalletRepository { fn effective_wallet_adjustment_amount(input: &AdjustWalletBalanceInput, before_total: f64) -> f64 { if input.clamp_deduction_to_available_balance && input.amount_usd < 0.0 { - -(-input.amount_usd).min(before_total.max(0.0)) + if before_total < 0.0 { + // Clear legacy negative totals to zero and record the actual ledger delta. + -before_total + } else { + -(-input.amount_usd).min(before_total) + } } else { input.amount_usd } @@ -9598,7 +9603,7 @@ mod tests { }; assert_eq!(effective_wallet_adjustment_amount(&input, 13.0), -13.0); assert_eq!(effective_wallet_adjustment_amount(&input, 0.0), -0.0); - assert_eq!(effective_wallet_adjustment_amount(&input, -1.0), -0.0); + assert_eq!(effective_wallet_adjustment_amount(&input, -1.0), 1.0); let legacy_input = AdjustWalletBalanceInput { clamp_deduction_to_available_balance: false, @@ -9681,22 +9686,25 @@ mod tests { amount_usd: -1.0, balance_type: "recharge".to_string(), operator_id: Some("admin-user".to_string()), - description: Some("bulk deduction from negative balance".to_string()), + description: Some("bulk deduction floors a negative balance".to_string()), clamp_deduction_to_available_balance: true, batch_context: None, }) .await - .expect("legacy negative wallet should remain usable") + .expect("legacy negative wallet should be floored at zero") .expect("wallet should still exist"); - assert_eq!(wallet.balance + wallet.gift_balance, -1.0); - assert!(transaction.is_none()); + assert_eq!(wallet.balance + wallet.gift_balance, 0.0); + let transaction = transaction.expect("negative balance correction should be ledgered"); + assert_eq!(transaction.amount, 1.0); + assert_eq!(transaction.balance_before, -1.0); + assert_eq!(transaction.balance_after, 0.0); let transaction_count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM wallet_transactions WHERE wallet_id = $1") .bind(&wallet_id) .fetch_one(&pool) .await .expect("ledger row count should be readable"); - assert_eq!(transaction_count, 1); + assert_eq!(transaction_count, 2); pool.close().await; }