wip(coupon): 领券状态相关 server 改动 (本地快照, 非本次会话产出)
app/api/v1/coupon.py + repositories/coupon_state + schemas/coupon_state 进行中改动, 未经我 review, 配合 launch_confirm / 领券链路。
This commit is contained in:
+14
-1
@@ -18,7 +18,7 @@ import httpx
|
||||
from fastapi import APIRouter, HTTPException, Request, status
|
||||
from fastapi.concurrency import run_in_threadpool
|
||||
|
||||
from app.api.deps import DbSession
|
||||
from app.api.deps import CurrentUser, DbSession
|
||||
from app.core.config import settings
|
||||
from app.core.pricebot_router import pick_pricebot
|
||||
from app.db.session import SessionLocal
|
||||
@@ -28,6 +28,7 @@ from app.schemas.coupon_state import (
|
||||
CouponPromptDismissIn,
|
||||
CouponPromptShouldShowOut,
|
||||
CouponPromptShownIn,
|
||||
CouponStatsOut,
|
||||
)
|
||||
|
||||
logger = logging.getLogger("shagua.coupon")
|
||||
@@ -268,3 +269,15 @@ def coupon_completed_today_reset(
|
||||
与 /prompt/reset 配套:开发设置「重置今日领券弹窗状态」一键把今日状态全清。MVP 不鉴权。"""
|
||||
coupon_repo.reset_today_completion(db, payload.device_id)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/stats",
|
||||
response_model=CouponStatsOut,
|
||||
summary="累计领券数(「我的」页战绩卡「领取优惠券 X 张」)",
|
||||
)
|
||||
def coupon_stats(user: CurrentUser, db: DbSession) -> CouponStatsOut:
|
||||
"""该登录用户累计领到的券数(SUM(claimed_count),口径见 coupon_repo.sum_claimed_count)。
|
||||
**鉴权(CurrentUser)**——区别于同文件不鉴权的 /step 透传与 /prompt 频控(那些按 device_id):
|
||||
个人战绩按 user_id 聚合,必须有登录态。"""
|
||||
return CouponStatsOut(coupon_count=coupon_repo.sum_claimed_count(db, user.id))
|
||||
|
||||
@@ -9,7 +9,7 @@ import logging
|
||||
from datetime import date, datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from sqlalchemy import delete, select
|
||||
from sqlalchemy import delete, func, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -206,3 +206,29 @@ def record_claims(
|
||||
)
|
||||
return 0
|
||||
return written
|
||||
|
||||
|
||||
# ===== 累计领券数(「我的」页战绩卡「领取优惠券 X 张」)=====
|
||||
|
||||
def sum_claimed_count(db: Session, user_id: int) -> int:
|
||||
"""该用户累计领到的优惠券张数。口径(2026-06-15 用户定):SUM(claimed_count) ——
|
||||
各成功领券记录的 pricebot 展示张数(claimed_count 列,存的是 display_count)之和,
|
||||
与领券完成时给用户看的「本次领了 N 张」同源。
|
||||
|
||||
- 只算 status ∈ {success, already_claimed}:already_claimed=今日已领过,协议里算「已领到」;
|
||||
failed / skipped 不计。
|
||||
- claimed_count 为 0 的保持 0:那是同 count_group 合并去重项(pricebot 只让一条出数),不重复计;
|
||||
为 NULL 的兜底成 1(成功领到至少 1 张;实际 pricebot to_dict 恒下发 display_count,NULL 基本不出现)。
|
||||
- 维度 user_id:登录态领的券才归入。登录前匿名领的(user_id 为空)不算(产品可接受)。
|
||||
"""
|
||||
total = db.execute(
|
||||
select(
|
||||
func.coalesce(
|
||||
func.sum(func.coalesce(CouponClaimRecord.claimed_count, 1)), 0
|
||||
)
|
||||
).where(
|
||||
CouponClaimRecord.user_id == user_id,
|
||||
CouponClaimRecord.status.in_(("success", "already_claimed")),
|
||||
)
|
||||
).scalar_one()
|
||||
return int(total or 0)
|
||||
|
||||
@@ -39,3 +39,13 @@ class CouponCompletedTodayOut(BaseModel):
|
||||
"""这台设备今天是否已跑完整轮领券(到 done 帧)。完成 → 首页「去领取」卡置灰。"""
|
||||
|
||||
completed: bool
|
||||
|
||||
|
||||
class CouponStatsOut(BaseModel):
|
||||
"""「我的」页战绩卡「领取优惠券 X 张」数据源:该登录用户累计领到的券数。
|
||||
|
||||
口径(2026-06-15 用户定):SUM(claimed_count) —— 各成功领券记录的 pricebot 展示张数之和,
|
||||
与领券完成时给用户看的「本次领了 N 张」同源。详见 repositories.coupon_state.sum_claimed_count。
|
||||
"""
|
||||
|
||||
coupon_count: int
|
||||
|
||||
Reference in New Issue
Block a user