--- todos: - id: "group-model" content: "WalletFundLotGroupModel 扩展 SOURCE_TYPE_* 覆盖券/会员/活动/直接发奖,并加 createSingleMemberGroup 便捷封装(中文常量注释)" status: pending - id: "recharge-group" content: "RechargeFundLotService:纯 Deposit / 纯 Bonus 也建单成员组并写 group_no(required_wager=该 Lot 所需)" status: pending - id: "register-group" content: "RegisterService:fee>0 且 bonus=0 时建单成员 Deposit 组;Trial(fee=0) 保持不建" status: pending - id: "grant-group" content: "GrantBonusService:注入组模型,直接发奖 Bonus 建单成员组并写 group_no" status: pending - id: "win-unify" content: "WinService:去掉未分组结算路径,统一 finalizeGroup;补全 deposit-only/bonus-only 退化组的 Completed/PlayedOut 语义" status: pending - id: "bet-cash" content: "BetService:确认/清理 Cash→FIFO progress_group 计入逻辑,移除未分组丢弃相关死分支与注释" status: pending - id: "task-display" content: "PlayerTaskQueryService:以组为唯一任务结构,未分组分支降为历史兜底或移除" status: pending - id: "migration" content: "新增回填 command:将存量进行中未分组 Lot 迁为单成员组(按 uid 分片、幂等)" status: pending - id: "doc-update" content: "更新 docs/requirements/钱包系统V2.md §7/§10/§11:人人有组 + Cash 计入 progress_group" status: pending - id: "tests" content: "WalletGroupWageringTest 补单成员组与 Cash 计入打码进度用例" status: pending - id: "verify" content: "收尾跑 verify-slot-backend.sh 门禁 + docker php -l,输出检测报告" status: pending isProject: false --- # 钱包打码组统一(路线 A:人人有组) ## 目标与硬约束 - 真金每笔 `wallet_fund_lot` 都归入一个 `wallet_fund_lot_group`,**不再有 `group_no=''` 的真金 Lot**(单 Lot = 单成员退化组)。 - **Cash(withdraw)有效投注必须计入当前 FIFO `progress_group`**(修掉「只有未分组 Lot 时 Cash 白打」的缺陷)。 - **Trial 体验金永远独立**:不建 Lot、不建组、不进 `BetService/WinService`(已天然满足,仅需保证不破坏)。 - 复用现有三表,**不改表结构**;仅做数据回填迁移。 ## 现状关键事实(已核对) - 建组只在「Deposit+Bonus 同时存在」时发生:`RechargeFundLotService::resolveRechargeGroupNo`、`RegisterService::resolveRegisterGroupNo`(`deposit<=0 || bonus<=0` 返回空串)。 - 纯发奖 Bonus 永远未分组:`GrantBonusService::insertDirectRewardBonusLot`(`insertFundLot` 不带 `group_no`)。 - Cash 投注归属:`BetService::accumulateGroupWager` 把 `withdrawValidBet` 计入 `lockEarliestActiveGroup`;**无 Active 组时直接丢弃**(缺陷根因)。 - 结算双路径:`WinService::execute` 同时跑 `finalizeActiveGroups` + `finalizeUngroupedLots`。 - 展示双路径:`PlayerTaskQueryService::queryTaskProgress` 返回 `groups` + 未分组 `bonus_tasks/deposit_tasks`。 ## 设计决定 - 单成员组:`group_no` 复用来源键(`order_no` / 注册 `biz_id` / grant `wallet_request_id`);`deposit_amount`/`bonus_amount` 一侧为该 Lot 金额、另一侧 0;`required_wager = lot.required_wager`。 - `WalletFundLotGroupModel` 扩展 `SOURCE_TYPE_*`,覆盖券/会员/活动/直接发奖(现仅 REGISTER=1、RECHARGE=4)。 - `BetService`:成员投注计入自身 `group_no`、Cash 计入 FIFO 最早 Active 组——逻辑不变,但因「人人有组」Cash 不再丢失;`consumeForBet` 仍累加 Lot 级 `current_wager` 供明细展示。 - `WinService`:去掉未分组结算路径,统一走 `finalizeGroup`;补全**单边退化组**(deposit-only / bonus-only)的 Completed / PlayedOut 语义。 - 迁移期可保留 `finalizeUngroupedLots` 作为历史兜底,回填完成后再删。 ## 影响文件 - `slot-wallet/app/model/multi/WalletFundLotGroupModel.php`:加 `SOURCE_TYPE_*`;可加 `createSingleMemberGroup()` 便捷封装。 - `slot-wallet/app/service/wallet/RechargeFundLotService.php`:纯 Deposit、纯 Bonus 也建单成员组并写 `group_no`。 - `slot-wallet/app/service/wallet/RegisterService.php`:`fee>0 & bonus=0` 建单成员 Deposit 组。 - `slot-wallet/app/service/wallet/GrantBonusService.php`:注入组模型,直接发奖 Bonus 建单成员组并写 `group_no`。 - `slot-wallet/app/service/wallet/WinService.php`:统一结算路径,处理退化组终态。 - `slot-wallet/app/service/wallet/BetService.php`:清理「未分组 Cash 丢弃」相关分支与注释,确认 Cash→FIFO 组生效。 - `slot-wallet/app/service/wallet/PlayerTaskQueryService.php`:以组为唯一任务结构;未分组分支降为历史兜底或移除。 - 迁移:新增 command(参照 `app/command/InitDB.php`)回填存量「进行中」未分组 Lot 为单成员组。 - 文档:`docs/requirements/钱包系统V2.md` §7 / §10 / §11 改为「人人有组 + Cash 计入 progress_group」。 - 测试:`slot-wallet/tests/Feature/WalletGroupWageringTest.php` 补单成员组与 Cash 计入用例。 ## 风险 / 待确认 - 退化组 PlayedOut:deposit-only 组资金耗尽未达标时 Deposit 成员状态如何置(现 `markGroupBonusPlayedOut` 只处理 Bonus)。 - 存量回填在分表上的批量与幂等(按 `uid` 分片,`createGroupIfAbsent` 幂等)。 - 是否一并采纳 V2「整笔只计 progress_group」(成员投注也归 FIFO 组而非自身组)——本方案默认**不改**,保留现有「成员计自身组 + Cash 计 FIFO 组」,单组场景下二者等价。 - 是否同时调整扣款顺序为 V2 的「Cash 最后扣」——**默认不在本次范围**。