--- name: 首充第一档金额下限 overview: 首充定格时,第一档金额应至少为活动配置 `ext_config.first_cash_amount`(千分位),不能仅按 `balanceBeforeQf` 用 `min` 压到低于配置;同步调整 `handleFreeCreditInit` 的定格基数与 `calcFirstCashAmount` 公式,并更新单测。 todos: - id: fix-calc-first-cash content: 修改 FreeCreditsLogic::calcFirstCashAmount 为至少 ext_config.first_cash_amount status: completed - id: update-unit-tests content: 更新 FreeCreditsLogicAmountTest 与 handleFreeCreditInit 相关单测 status: completed - id: verify-integration content: 按需跑 FreeCreditsFirstRechargeFreezeTest 验证落库第一档金额 status: completed isProject: false --- # 首充定格:第一档金额不低于配置 ## 问题 [`handleFreeCreditInit`](slot_console/app/api/logic/FreeCreditsLogic.php) 当前: ```php $frozenAmount = max($balanceBeforeQf, 0); ``` 定格总额仍用入账前余额(正确),但第一档金额在 [`freezeFirstRecharge`](slot_console/app/api/logic/FreeCreditsLogic.php) 里走: ```php $firstCashAmount = self::calcFirstCashAmount($frozenAmount, $this->configAmount($config, 'first_cash_amount', ...)); // calcFirstCashAmount = min(frozen, configured) ``` 当 **定格额 < 配置免打码额** 时,第一档会变成定格额本身(例如池内 $6、配置 $20 → 第一档 $6),与「第一档最少给 `ext_config.first_cash_amount`」不符。 单测 [`FreeCreditsLogicAmountTest`](slot_console/tests/Unit/FreeCreditsLogicAmountTest.php) 里 `'定格小于配置' => [30, 20, 20]` 已按「至少配置值」写期望,与实现 **不一致**(`min(30,20)=20` 碰巧相等,但 `frozen=15, config=20` 会得到 15)。 ## 目标行为 | 字段 | 规则 | |------|------| | **定格总额** `frozen_amount_qf` | 仍为入账前免费余额 `balance_before_qf`(需求 §5.3),**不**强行抬到配置 | | **第一档金额** `first_cash_amount_qf` / `package_no=1.amount_qf` | `max(min(定格额, 配置), 配置)` → 等价于 **至少为配置值**:定格 ≥ 配置时取配置上限;定格 < 配置时仍记 **配置值**(运营承诺的档位面额) | ```mermaid flowchart LR event[free_credit_init] --> init[handleFreeCreditInit] init --> freeze[freezeFirstRecharge] freeze --> frozen["frozen = balanceBefore"] freeze --> first["firstCash = max(min(frozen, cfg), cfg)"] ``` **边界说明**:若定格额实际小于配置,玩家可提现金额仍受池内 `package.amount_qf` 与钱包冻结额约束;本次按你的要求把 **落库第一档金额** 抬到配置下限,与需求文档 §5.6「定格 < $20 → 第一档 = 定格额」存在冲突——以你本次口径为准;若需「可提现 ≤ 定格」再单独加提现校验。 ## 实现(仅 slot_console) ### 1. 修改 `calcFirstCashAmount` 文件:[`FreeCreditsLogic.php`](slot_console/app/api/logic/FreeCreditsLogic.php) ```php public static function calcFirstCashAmount(int $frozenAmount, int $configuredFirstCash): int { return max(min($frozenAmount, $configuredFirstCash), $configuredFirstCash); } ``` - 注释改为:第一档面额不低于配置,且不超过定格总额与配置上限。 - 逻辑上等价于 `return $configuredFirstCash`(当 `configuredFirstCash > 0`);保留 `min(frozen, …)` 形式便于日后若配置为 0 时回退。 ### 2. `handleFreeCreditInit` 显式读配置(可选但建议) 在同文件 `handleFreeCreditInit` 内,在调用 `freezeFirstRecharge` 前读取一次配置(便于日志/后续扩展;**定格额仍用 `balanceBeforeQf`**): ```php $configuredFirstCash = $this->configAmount($config, 'first_cash_amount', self::DEFAULT_FIRST_CASH); $frozenAmount = max($balanceBeforeQf, 0); // 不把 frozenAmount 改成 max(balanceBefore, configuredFirstCash),避免改变定格总额语义 ``` 若你希望 **尝试多冻** 到配置下限,可另议;当前计划 **只改第一档金额公式**,不改定格总额。 ### 3. 单测 | 文件 | 改动 | |------|------| | [`FreeCreditsLogicAmountTest.php`](slot_console/tests/Unit/FreeCreditsLogicAmountTest.php) | 修正/补充:`[15, 20, 20]`(定格小于配置 → 第一档仍为 20);保留 `[100, 20, 20]` | | [`FreeCreditsHandleFreeCreditInitTest.php`](slot_console/tests/Unit/FreeCreditsHandleFreeCreditInitTest.php) | 增加用例:`balanceBefore=15000`、`first_cash_amount_qf=20000` 时,Harness 落库后 `first_cash_amount_qf` / 第一档 `amount_qf` 为 20000(若 Harness 走完整 `freezeFirstRecharge` 需开 DB 或扩展 Harness 断言) | | 集成测 [`FreeCreditsFirstRechargeFreezeTest`](slot_console/tests/Integration/FreeCreditsFirstRechargeFreezeTest.php) | 当 DB 活动配置 `first_cash_amount_qf=20000` 且 `balanceBefore < 20000` 时断言 `player.first_cash_amount_qf === 20000` | 运行(Docker): ```bash docker exec -w /app/www/slot/slot_console php82 ./vendor/bin/phpunit \ --filter 'FreeCreditsLogicAmountTest|FreeCreditsHandleFreeCreditInitTest' ``` ## 不改动的部分 - **wallet** `maybeSendFreeCreditInit`:仍 `balance_before_qf > 0` 即发事件。 - **定格扣款金额**:仍 `freeCreditsFreeze(frozenAmount)`,`frozenAmount` 以 `balanceBefore` 为准(经 `freezeFirstRecharge` 内 `min(…, wallet.balance)` 封顶)。 - **解锁第一档**:仍用 `recharge_unlock_amount` 配置,与本次无关。 ## 风险 - 定格 < 配置时,`first_cash_amount_qf` > 实际可冻余额:C 端可能展示 $20 但池内不足;提现/保留时需依赖现有 `package.amount_qf` 与钱包校验,必要时后续在 `applyFirstCashoutWithdraw` 增加 `amount_qf <= frozen` 断言。