Files
cursor/plans/monthly cashback-215d598c.plan.md
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

120 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- 215d598c-9837-4e65-a3e9-850d78cd5f00 -->
## 月返水Monthly Cashback实现方案
与周返水完全同构,仅「周」改「月」。需求见 [月返水.md](slot_console/doc/月返水.md);结构镜像 [DailyRebateLogic.php](slot_console/app/api/logic/DailyRebateLogic.php) 与周返水两份计划(核心 + 统计)。
### 需要改动的服务
- slot_lib常量。
- slot_console表 / 模型 / 服务 / Logic / Controller / Validator / 命令 / 路由 / 活动配置入口 / 部署文档。
- backend/slot_admin+ vue后台「每月返水统计」页。
- slot_wallet仅确认流水类型 69 打码归【活动赠送 Y3】。
- slot_pwa无需改动复用 `rebate_bet`)。
### 周期与口径(与周返水差异)
- 统计月:每月 1 日 00:00:00 ~ 月末 23:59:59服务器时区。
- `month_start = date('Y-m-01')`;上月 = `first day of last month`;上上月 = `first day of 2 months ago`;过期时间 = 下月 1 日。
- 月有效投注 = 当月每日 `rebate_bet` 之和(仅充值用户,沿用 pwa 现有口径)。
- VIP 取月末最后一刻(结算下月 1 日读当前 VIP`floor` 取整到厘。
### 数据流
```mermaid
flowchart TD
pwa["slot_pwa 已累加 rebate_bet"] --> redis["redis user:profit:Ymd TTL7天"]
accrue["monthlyRebateAccrue 每天00:05 处理昨日"] --> redis
accrue --> rec["monthly_rebate_record 当月Pending bet累加 + vip/rate快照"]
settle["monthlyRebateSettle 每月1日00:15"] --> rec
settle --> vip["读当前VIP rate=vip_rates level"]
settle --> expirePrev["上上月 Claimable -> Expired"]
claim["C端 claim 上月"] --> wallet["WalletService gift 类型69 打码Y3"]
admin["后台 每月返水统计"] --> rec
```
### 落库方案 B幂等
- `monthly_rebate_record``last_accrued_date`date
- `monthlyRebateAccrue`(每天)处理**昨日**key 仅 1 天,在 TTL 内):遍历昨日 profit 哈希,充值用户且 `rebate_bet>0` → 定位昨日所属月记录(无则建 Pending→ 若 `last_accrued_date < 昨日``bet_amount += rebate_bet` 且更新 `last_accrued_date`;同时写入当前 `vip_level`/`rate_snapshot`(供后台对待定行展示费率)。
- C 端 This Month = `record.bet_amount` + 今日 redis 实时 `rebate_bet`
### 结算/过期(每月 1 日)
- `monthlyRebateSettle`:上月 Pending → 读当前 VIP → `rebate=floor(bet*rate/100)``rebate>0` 置 Claimable`claimable_at=本月1日``expire_at=下月1日`;同次将上上月 `Claimable 且 expire_at<=now` 置 Expired。
- `monthlyRebateExpire`:兜底,`Claimable 且 expire_at<=now -> Expired`
- 顺序accrue 先于 settle。
### 复用 vs 新建(计费 = VIP 费率单乘,不复用 tiers
- 复用骨架:领取短事务 CAS + 钱包入账失败回滚 + 结算结构化日志 + `RechargedUidSetService`/`UserProfitEntity`/四态 + `statusLabel`/Controller/Validator 形态。
- 新建:`MonthlyRebateCalcService::calcRebateLi(betLi, ratePercent)=floor(betLi*ratePercent/100)``MonthlyRebateConfigService`(解析 `vip_rates`/`maxRate`/`rateForVip`/`canonicalExtConfigForStorage`)。
### 文件清单
slot_lib
- [Consts.php](slot_lib/src/common/const/Consts.php)`ACTIVITY_TYPE_MONTHLY_REBATE=14``TRANSACTION_TYPE_MONTHLY_REBATE=69`(若周返水未先上,取下一空位即可)。
slot_console
- 模型 `app/model/common/MonthlyRebateRecordModel.php`(四态 + `findByUidAndMonth`)。
- [RechargeGiftConfigModel.php](slot_console/app/model/common/RechargeGiftConfigModel.php)`TYPE_MONTHLY_REBATE=14` + `getMonthlyRebateActiveInfo/...ByExactSource`(镜像每日返水方法)。
- 服务 `app/service/MonthlyRebateCalcService.php``app/service/MonthlyRebateConfigService.php`
- Logic `app/api/logic/MonthlyRebateLogic.php`info/claim/accrue/settle/expire
- Controller `app/api/controller/MonthlyRebateController.php`、Validator `app/api/validator/MonthlyRebateValidator.php`claim 场景,可选 month_start
- 命令 `app/command/MonthlyRebateAccrue.php``MonthlyRebateSettle.php``MonthlyRebateExpire.php`
- 路由:注册 monthly-rebate info/claim。
- SQL `db/monthly_rebate.sql`(含 `month_start date``last_accrued_date`、唯一键 `uniq_uid_month``vip_level``rate_snapshot`)。
- 活动配置入口 [ActivityConfigEntity.php](slot_console/app/entity/activity/ActivityConfigEntity.php) 对 `TYPE_MONTHLY_REBATE``MonthlyRebateConfigService::canonicalExtConfigForStorage`
- 部署文档 `doc/monthly_rebate_deploy.md`
backend/slot_admin后台统计镜像 [FreeCreditsStatsLogic.php](backend/slot_admin/app/game/logic/FreeCreditsStatsLogic.php)
- 模型 `app/game/model/common/MonthlyRebateRecordModel.php``connection='s_common'`、四态 + `statusLabel`)。
- DTO `app/game/dto/MonthlyRebateStatsQueryDTO.php`、校验 `app/game/validate/MonthlyRebateStatsValidate.php``uid/source/status/stat_range[]/orderBy(in:bet_amount,rebate_amount)/orderType/page/limit`)。
- 控制器 `app/game/controller/MonthlyRebateStatsController.php``index` 返回 list + otherData
- Logic `app/game/logic/MonthlyRebateStatsLogic.php`
- `buildQuery``uid` 精确、`source` 精确、`status` whereIn、时间范围 snap 整月(`month_start >= 月初(start)``month_start <= 月初(end)`)、排序白名单默认 `month_start desc`
- `list`:行补 `rate(=rate_snapshot)``month_start/month_end``is_current_month``status_label`,金额 `getNumberFormat`
- `statistics`:总领取/总待领取/总过期/领取率(分母 0→02 位小数)/待定(仅当筛选含本月)。
- 菜单 `db/menu-monthly-rebate-stats.sql`(挂「综合统计」父菜单,镜像 [menu-free-credits-stats.sql](backend/slot_admin/db/menu-free-credits-stats.sql)`component=game/monthlyRebateStats/index` + `sm_system_group_menu` 授权)。
backend/slot_admin_vue镜像 [freeCreditsStats/index.vue](backend/slot_admin_vue/src/views/game/freeCreditsStats/index.vue)
- `src/api/game/monthlyRebateStats.js``src/views/game/monthlyRebateStats/index.vue`:筛选(ID/渠道/状态/按日范围)、`#tableAfterButtons` 顶部总计、可排序列(有效下注额/返水金额)、统计周期列显示「本月」或 `month_start - month_end`
slot_wallet
- 确认流水类型 `69` 打码归类为 活动赠送 Y3`SOURCE_TYPE_ACTIVITY_REWARD=3`),如有类型白名单补 69。
### ext_config 标准格式type=14
```json
{
"vip_rates": [
{"vip_level": 0, "rate_percent": 0},
{"vip_level": 1, "rate_percent": 0.5},
{"vip_level": 15, "rate_percent": 2.0}
],
"banner_image": ""
}
```
### cron
- `monthlyRebateAccrue` 每天 00:05`monthlyRebateSettle` 每月 1 日 00:15`monthlyRebateExpire` 每月 1 日 00:20。
### 与周返水共用注意
- 月返水与周返水各自独立表/配置/命令/流水类型,互不影响;同一用户同期可同时拥有日/周/月三套返水记录。
- 若日/周/月三套都上,建议抽取「曾充值判断 + profit 哈希解码 + 月/周边界」公共片段,避免三处复制(可选优化,不阻塞)。
### 收尾门禁
-`app/**/*.php` 后按 `slot-backend-completion-report` Skill 跑 `verify-slot-backend.sh` + docker `php -l`,回复附「检测结果」章节(含 PHPDoc:checked
</plan>
<todos>
[
{"id": "consts", "content": "slot_lib Consts 新增 ACTIVITY_TYPE_MONTHLY_REBATE=14、TRANSACTION_TYPE_MONTHLY_REBATE=69"},
{"id": "sql-model", "content": "monthly_rebate.sql + MonthlyRebateRecordModel(month_start/last_accrued_date/vip_level/rate_snapshot/四态/findByUidAndMonth)"},
{"id": "config-model-methods", "content": "RechargeGiftConfigModel 加 TYPE_MONTHLY_REBATE 与 getMonthlyRebateActiveInfo 系列方法"},
{"id": "services", "content": "MonthlyRebateCalcService(VIP费率单乘) 与 MonthlyRebateConfigService(vip_rates/maxRate/rateForVip/canonical)"},
{"id": "logic", "content": "MonthlyRebateLogicinfo/claim(CAS+钱包回滚)/accrue(写vip/rate快照)/settle(读VIP+过期上上月)/expire"},
{"id": "controller-route", "content": "MonthlyRebateController + MonthlyRebateValidator + 路由注册"},
{"id": "commands", "content": "命令 MonthlyRebateAccrue/Settle/Expire + cron"},
{"id": "activity-entry", "content": "ActivityConfigEntity 读写 type=14 走 canonicalExtConfigForStorage"},
{"id": "admin-stats", "content": "slot_admin 每月返水统计Model(s_common)/DTO/Validate/Controller/Logic(buildQuery月snap+list+statistics)"},
{"id": "admin-vue", "content": "slot_admin_vue monthlyRebateStats api.js + index.vue(筛选/总计/排序/统计周期)"},
{"id": "admin-menu-sql", "content": "menu-monthly-rebate-stats.sql 挂综合统计父菜单 + 分组授权"},
{"id": "wallet-verify", "content": "slot_wallet 确认流水类型69 打码归 活动赠送Y3"},
{"id": "deploy-doc", "content": "编写 monthly_rebate_deploy.md"},
{"id": "verify-gate", "content": "跑 verify-slot-backend.sh + php -l 输出检测结果章节"}
]
</todos>