值班的人打开控制台只想知道两件事:谁在等回复,钱还对不对得上。概览把 待裁定的申诉、待打款的提现、各类待审排在一屏,并把代管中、已结算、 平台服务费和账户余额一起摆出来;余额与流水对不上时先弹红条,因为那 种时候不该继续打款。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
4.1 KiB
Go
81 lines
4.1 KiB
Go
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,
|
|
})
|
|
}
|