Compare commits

...

1 Commits

Author SHA1 Message Date
zzhyyyyy 04154b38bd 调现金接口支持目标账户(金币现金 / 邀请奖励金)+ overview 带邀请余额
- GrantCashRequest 加 account(coin_cash / invite_cash,默认 coin_cash 兼容旧调用)
- 调现金接口按 account 分支:邀请账户走 grant_invite_cash,余额读取/扣负保护/审计
  detail 都按对应账户走;两本账物理隔离不可串
- 用户 360 overview 返回 invite_cash_balance_cents(admin 调现金弹窗展示邀请余额)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:44:23 +08:00
3 changed files with 26 additions and 11 deletions
+1
View File
@@ -788,6 +788,7 @@ def get_user_overview(db: Session, user_id: int) -> dict | None:
"user": user, "user": user,
"coin_balance": acc.coin_balance if acc else 0, "coin_balance": acc.coin_balance if acc else 0,
"cash_balance_cents": acc.cash_balance_cents if acc else 0, "cash_balance_cents": acc.cash_balance_cents if acc else 0,
"invite_cash_balance_cents": acc.invite_cash_balance_cents if acc else 0,
"total_coin_earned": acc.total_coin_earned if acc else 0, "total_coin_earned": acc.total_coin_earned if acc else 0,
"comparison_total": _count(ComparisonRecord, ComparisonRecord.user_id == user_id), "comparison_total": _count(ComparisonRecord, ComparisonRecord.user_id == user_id),
"comparison_success": _count( "comparison_success": _count(
+19 -11
View File
@@ -210,22 +210,28 @@ def grant_user_cash(
db: AdminDb, db: AdminDb,
) -> OkResponse: ) -> OkResponse:
"""给指定用户增/减或设值现金(分)。delta:正=发放、负=扣减;set:直接设为目标值。 """给指定用户增/减或设值现金(分)。delta:正=发放、负=扣减;set:直接设为目标值。
account=coin_cash(金币兑现金)/ invite_cash(邀请奖励金):两本账物理隔离各调各的
主要用于让无现金用户直接测试提现""" 主要用于让无现金用户直接测试提现"""
user = user_repo.get_user_by_id(db, user_id) user = user_repo.get_user_by_id(db, user_id)
if user is None: if user is None:
raise HTTPException(status_code=404, detail="用户不存在") raise HTTPException(status_code=404, detail="用户不存在")
# set=设为目标值:读当前余额算差值,仍复用 grant_cash 写一笔流水(沿用原子/审计/扣负保护) # 按目标账户选「余额字段 + 变动入口」(grant_invite_cash 与 grant_cash 同构)
is_invite = body.account == "invite_cash"
balance_attr = "invite_cash_balance_cents" if is_invite else "cash_balance_cents"
grant_fn = wallet_repo.grant_invite_cash if is_invite else wallet_repo.grant_cash
acct_label = "邀请奖励金" if is_invite else "现金"
before: int | None = None
# set=设为目标值:读当前余额算差值,仍复用 grant 写一笔流水(沿用原子/审计/扣负保护)
if body.mode == "set": if body.mode == "set":
if body.amount_cents < 0: if body.amount_cents < 0:
raise HTTPException(status_code=400, detail="目标现金值不能为负") raise HTTPException(status_code=400, detail=f"目标{acct_label}值不能为负")
# lock=True:锁账户行,防连点/并发各读同一 before 算同一 delta 双写,余额错位 # lock=True:锁账户行,防连点/并发各读同一 before 算同一 delta 双写,余额错位
before = wallet_repo.get_or_create_account( acc_locked = wallet_repo.get_or_create_account(db, user_id, commit=False, lock=True)
db, user_id, commit=False, lock=True before = getattr(acc_locked, balance_attr)
).cash_balance_cents
delta = body.amount_cents - before delta = body.amount_cents - before
if delta == 0: if delta == 0:
raise HTTPException( raise HTTPException(
status_code=400, detail=f"当前现金已为 {body.amount_cents} 分,无需调整" status_code=400, detail=f"当前{acct_label}已为 {body.amount_cents} 分,无需调整"
) )
else: else:
if body.amount_cents == 0: if body.amount_cents == 0:
@@ -234,18 +240,20 @@ def grant_user_cash(
# 负数扣减时不允许扣成负余额(运营误操作保护);lock=True 防并发扣穿 # 负数扣减时不允许扣成负余额(运营误操作保护);lock=True 防并发扣穿
if delta < 0: if delta < 0:
acc_now = wallet_repo.get_or_create_account(db, user_id, commit=False, lock=True) acc_now = wallet_repo.get_or_create_account(db, user_id, commit=False, lock=True)
if acc_now.cash_balance_cents + delta < 0: if getattr(acc_now, balance_attr) + delta < 0:
raise HTTPException( raise HTTPException(
status_code=400, detail=f"扣减后现金为负(当前余额 {acc_now.cash_balance_cents} 分)" status_code=400,
detail=f"扣减后{acct_label}为负(当前余额 {getattr(acc_now, balance_attr)} 分)",
) )
biz_type = "admin_grant" if delta > 0 else "admin_deduct" biz_type = "admin_grant" if delta > 0 else "admin_deduct"
# grant_cash 只 flush 不 commit;审计同 commit=False;最后一起 commit → 原子(改钱+留痕) # grant 只 flush 不 commit;审计同 commit=False;最后一起 commit → 原子(改钱+留痕)
acc, _ = wallet_repo.grant_cash( acc, _ = grant_fn(
db, user_id, delta, biz_type=biz_type, remark=f"admin:{body.reason}"[:128], db, user_id, delta, biz_type=biz_type, remark=f"admin:{body.reason}"[:128],
) )
detail = { detail = {
"account": body.account,
"amount_cents": delta, "amount_cents": delta,
"balance_after_cents": acc.cash_balance_cents, "balance_after_cents": getattr(acc, balance_attr),
"reason": body.reason, "reason": body.reason,
} }
if body.mode == "set": if body.mode == "set":
+6
View File
@@ -29,6 +29,7 @@ class AdminUserOverview(BaseModel):
user: AdminUserListItem user: AdminUserListItem
coin_balance: int coin_balance: int
cash_balance_cents: int cash_balance_cents: int
invite_cash_balance_cents: int # 邀请奖励金余额(与 cash_balance_cents 物理隔离)
total_coin_earned: int total_coin_earned: int
comparison_total: int comparison_total: int
comparison_success: int comparison_success: int
@@ -88,6 +89,11 @@ class GrantCoinsRequest(BaseModel):
class GrantCashRequest(BaseModel): class GrantCashRequest(BaseModel):
# 目标账户:金币兑换的现金(cash_balance_cents)与邀请奖励金(invite_cash_balance_cents)物理隔离,
# 各调各的、不可串。默认 coin_cash 兼容旧调用。
account: Literal["coin_cash", "invite_cash"] = Field(
"coin_cash", description="目标账户:coin_cash=金币兑现金账户 / invite_cash=邀请奖励金账户"
)
mode: Literal["delta", "set"] = Field( mode: Literal["delta", "set"] = Field(
"delta", description="delta=增减(amount_cents 为变动量) / set=设为(amount_cents 为目标值,须≥0)" "delta", description="delta=增减(amount_cents 为变动量) / set=设为(amount_cents 为目标值,须≥0)"
) )