diff --git a/monkeyai/backend/internal/billing/query.sql b/monkeyai/backend/internal/billing/query.sql index 1efe0f2dd..e9620ad04 100644 --- a/monkeyai/backend/internal/billing/query.sql +++ b/monkeyai/backend/internal/billing/query.sql @@ -444,21 +444,24 @@ SELECT COALESCE(t.reserved, 0)::text) FROM credit_accounts a - LEFT JOIN LATERAL ( + LEFT JOIN ( SELECT + account_id, sum(credit_delta) balance FROM credit_ledger_entries - WHERE - account_id = a.id) l ON TRUE - LEFT JOIN LATERAL ( + GROUP BY + account_id) l ON l.account_id = a.id + LEFT JOIN ( SELECT + account_id, sum(reserve) reserved FROM billing_transactions WHERE - account_id = a.id - AND status NOT IN ('settled', 'released', 'rejected')) t ON TRUE + status NOT IN ('settled', 'released', 'rejected') + GROUP BY + account_id) t ON t.account_id = a.id WHERE a.balance <> COALESCE(l.balance, 0) OR a.frozen <> COALESCE(t.reserved, 0) diff --git a/monkeyai/backend/internal/billing/query_test.go b/monkeyai/backend/internal/billing/query_test.go new file mode 100644 index 000000000..d15fd65a3 --- /dev/null +++ b/monkeyai/backend/internal/billing/query_test.go @@ -0,0 +1,51 @@ +package billing + +import ( + "testing" + + "github.com/chaitin/MonkeyCode/monkeyai/backend/internal/billing/sqlc" + "github.com/chaitin/MonkeyCode/monkeyai/backend/internal/resource" +) + +func TestAccountDifferences(t *testing.T) { + s, user, model := fixture(t) + ctx := t.Context() + query := sqlc.New(s.pool) + account, err := s.Account(ctx, user) + if err != nil { + t.Fatal(err) + } + check := func(want int) []resource.Object { + t.Helper() + differences, err := resource.DecodeObjects(query.AccountDifferences(ctx)) + if err != nil { + t.Fatal(err) + } + if len(differences) != want { + t.Fatalf("账户差异数量错误: got=%d want=%d differences=%v", len(differences), want, differences) + } + return differences + } + + check(0) + if _, err = s.Begin(ctx, Request{UserID: user, ResourceID: model, Category: "model"}); err != nil { + t.Fatal(err) + } + check(0) + + if _, err = s.pool.Exec(ctx, `UPDATE credit_accounts SET frozen = frozen + 1 WHERE id = $1`, account.ID); err != nil { + t.Fatal(err) + } + differences := check(1) + if differences[0].String("account_id") != account.ID || differences[0].String("frozen") == differences[0].String("reserved") { + t.Fatalf("未识别冻结额差异: %v", differences[0]) + } + + if _, err = s.pool.Exec(ctx, `UPDATE credit_accounts SET frozen = frozen - 1, balance = balance - 1 WHERE id = $1`, account.ID); err != nil { + t.Fatal(err) + } + differences = check(1) + if differences[0].String("balance") == differences[0].String("ledger_balance") { + t.Fatalf("未识别账本余额差异: %v", differences[0]) + } +} diff --git a/monkeyai/backend/internal/billing/sqlc/query.sql.go b/monkeyai/backend/internal/billing/sqlc/query.sql.go index 53e7c77a4..3762f71fb 100644 --- a/monkeyai/backend/internal/billing/sqlc/query.sql.go +++ b/monkeyai/backend/internal/billing/sqlc/query.sql.go @@ -19,21 +19,24 @@ SELECT COALESCE(t.reserved, 0)::text) FROM credit_accounts a - LEFT JOIN LATERAL ( + LEFT JOIN ( SELECT + account_id, sum(credit_delta) balance FROM credit_ledger_entries - WHERE - account_id = a.id) l ON TRUE - LEFT JOIN LATERAL ( + GROUP BY + account_id) l ON l.account_id = a.id + LEFT JOIN ( SELECT + account_id, sum(reserve) reserved FROM billing_transactions WHERE - account_id = a.id - AND status NOT IN ('settled', 'released', 'rejected')) t ON TRUE + status NOT IN ('settled', 'released', 'rejected') + GROUP BY + account_id) t ON t.account_id = a.id WHERE a.balance <> COALESCE(l.balance, 0) OR a.frozen <> COALESCE(t.reserved, 0) diff --git a/monkeyai/backend/migrations/000007_billing_pending_time_index.down.sql b/monkeyai/backend/migrations/000007_billing_pending_time_index.down.sql new file mode 100644 index 000000000..4adaea34a --- /dev/null +++ b/monkeyai/backend/migrations/000007_billing_pending_time_index.down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS billing_transactions_pending_time_idx; diff --git a/monkeyai/backend/migrations/000007_billing_pending_time_index.up.sql b/monkeyai/backend/migrations/000007_billing_pending_time_index.up.sql new file mode 100644 index 000000000..280babe8a --- /dev/null +++ b/monkeyai/backend/migrations/000007_billing_pending_time_index.up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY billing_transactions_pending_time_idx + ON billing_transactions(started_at, id) + WHERE status NOT IN ('settled', 'released', 'rejected'); diff --git a/monkeyai/backend/migrations/000008_billing_pending_account_index.down.sql b/monkeyai/backend/migrations/000008_billing_pending_account_index.down.sql new file mode 100644 index 000000000..858bad2d8 --- /dev/null +++ b/monkeyai/backend/migrations/000008_billing_pending_account_index.down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS billing_transactions_pending_account_idx; diff --git a/monkeyai/backend/migrations/000008_billing_pending_account_index.up.sql b/monkeyai/backend/migrations/000008_billing_pending_account_index.up.sql new file mode 100644 index 000000000..5161796ac --- /dev/null +++ b/monkeyai/backend/migrations/000008_billing_pending_account_index.up.sql @@ -0,0 +1,4 @@ +CREATE INDEX CONCURRENTLY billing_transactions_pending_account_idx + ON billing_transactions(account_id) + INCLUDE (reserve) + WHERE status NOT IN ('settled', 'released', 'rejected');