package app import ( "context" "net/http" ) // The pet module's landing page. It answers the two questions an operator opens // the console for — what is waiting for me, and is the money still adding up — // so nobody has to click through four lists to find out that nothing is due. func (a *App) countOne(ctx context.Context, query string, args ...any) int64 { var value int64 _ = a.db.QueryRowContext(ctx, query, args...).Scan(&value) return value } func (a *App) adminPetOverview(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // 待办:这几项有数字就意味着有人在等回复。 pending := map[string]int64{ "pets": a.countOne(ctx, `SELECT COUNT(*) FROM pets WHERE moderation_status=0 AND deleted_at IS NULL`), "adoptions": a.countOne(ctx, `SELECT COUNT(*) FROM pet_adoptions WHERE moderation_status=0 AND deleted_at IS NULL`), "matings": a.countOne(ctx, `SELECT COUNT(*) FROM pet_mating_listings WHERE moderation_status=0 AND deleted_at IS NULL`), "sitters": a.countOne(ctx, `SELECT COUNT(*) FROM pet_sitters WHERE status=0`), "disputes": a.countOne(ctx, `SELECT COUNT(*) FROM pet_feed_disputes WHERE status=0`), "withdrawals": a.countOne(ctx, `SELECT COUNT(*) FROM withdrawals WHERE status IN ('PENDING','APPROVED')`), } tasks := map[string]int64{} rows, err := a.db.QueryContext(ctx, `SELECT status,COUNT(*) FROM pet_feed_tasks GROUP BY status`) if err == nil { defer rows.Close() for rows.Next() { var status string var count int64 if rows.Scan(&status, &count) == nil { tasks[status] = count } } } // 对账:余额与流水对不上就不该继续打款,所以这条和待办放在同一屏。 mismatches := a.countOne(ctx, `SELECT COUNT(*) FROM ( SELECT a.user_id FROM wallet_accounts a LEFT JOIN wallet_transactions t ON t.user_id=a.user_id GROUP BY a.user_id,a.available_cent HAVING a.available_cent<>COALESCE(SUM(t.amount_cent*t.direction),0)) AS drift`) reply(w, map[string]any{ "pending": pending, "tasks": tasks, "money": map[string]int64{ // 代管中:这笔钱已经从主人那里收了,但还没结算给任何人。 "escrowedCent": a.countOne(ctx, `SELECT COALESCE(SUM(gross_cent),0) FROM pet_feed_tasks WHERE status IN ('ESCROWED','ASSIGNED','SERVING','COMPLETED','DISPUTED')`), "settledCent": a.countOne(ctx, `SELECT COALESCE(SUM(payout_cent),0) FROM pet_feed_tasks WHERE status='SETTLED'`), "platformFeeCent": a.countOne(ctx, `SELECT COALESCE(SUM(platform_fee_cent),0) FROM pet_feed_tasks WHERE status='SETTLED'`), "walletBalanceCent": a.countOne(ctx, `SELECT COALESCE(SUM(available_cent),0) FROM wallet_accounts`), "pendingWithdrawCent": a.countOne(ctx, `SELECT COALESCE(SUM(amount_cent),0) FROM withdrawals WHERE status IN ('PENDING','APPROVED')`), "paidWithdrawCent": a.countOne(ctx, `SELECT COALESCE(SUM(payout_cent),0) FROM withdrawals WHERE status='PAID'`), }, "scale": map[string]int64{ "pets": a.countOne(ctx, `SELECT COUNT(*) FROM pets WHERE deleted_at IS NULL`), "owners": a.countOne(ctx, `SELECT COUNT(DISTINCT owner_user_id) FROM pets WHERE deleted_at IS NULL`), "activeSitters": a.countOne(ctx, `SELECT COUNT(*) FROM pet_sitters WHERE status=1`), "openAdoptions": a.countOne(ctx, `SELECT COUNT(*) FROM pet_adoptions WHERE status=1 AND moderation_status=1 AND deleted_at IS NULL`), "handedOver": a.countOne(ctx, `SELECT COUNT(*) FROM pet_adoptions WHERE status=2`), "dueFollowups": a.countOne(ctx, `SELECT COUNT(*) FROM pet_adoption_followups WHERE submitted_at IS NULL AND due_at<=NOW(3)`), }, "switches": map[string]any{ "feedEnabled": a.configBool(ctx, "pet.feed_enabled", true), "matingEnabled": a.configBool(ctx, "pet.mating_enabled", false), "adoptionEnabled": a.configBool(ctx, "pet.adoption_enabled", true), "withdrawEnabled": a.configBool(ctx, "pet.withdraw_enabled", false), "platformFeePct": a.configInt(ctx, "pet.feed_platform_fee_percent", 15), "sitterDepositCent": a.configInt(ctx, "pet.sitter_deposit_cent", 20000), "confirmHours": a.configInt(ctx, "pet.feed_confirm_hours", 24), }, "ledgerMismatches": mismatches, }) }