doge 2 дней назад
Родитель
Сommit
73520739bc

+ 1 - 5
app/Services/Agent/AgentApplicationService.php

@@ -43,7 +43,6 @@ class AgentApplicationService
         $data = Validator::make($input, [
             'id' => ['required', 'integer', 'min:1'], 'status' => ['required', 'in:approved,rejected'],
             'remark' => ['required_if:status,rejected', 'nullable', 'string', 'max:500'],
-            'type' => ['nullable', 'integer', Rule::in(array_keys(Agent::TYPE_NAMES))],
         ])->validate();
         return DB::transaction(function () use ($data, $agents) {
             $found = AgentApplication::findOrFail($data['id']);
@@ -60,15 +59,12 @@ class AgentApplicationService
             if ($data['status'] === 'approved') {
                 if (Agent::where('owner_user_id', $member->id)->exists()) throw new \RuntimeException('该会员已是代理');
                 if (Agent::whereRaw('LOWER(username) = ?', [strtolower($application->username)])->exists()) throw new \RuntimeException('代理账号已被使用,请驳回后重新申请');
-                // 默认按会员申请时选择的类型开通;审核时传 type 可改为另一类型。
-                $type = (int) ($data['type'] ?? ($application->type ?: Agent::TYPE_JOINT));
                 $agent = $agents->createFromApplication($application, [
-                    'type' => $type,
+                    'type' => $application->type ?: Agent::TYPE_JOINT,
                     'real_name' => $member->first_name ?: '', 'language' => in_array($member->language, ['zh', 'en', 'vi'], true) ? $member->language : 'zh',
                 ], (int) $actor['operator_id']);
                 $this->bindMember($agent, (int) $member->id);
                 $application->agent_id = $agent->id;
-                $application->type = $type;
             }
             $application->status = $data['status'];
             $application->reject_reason = $data['status'] === 'rejected' ? trim((string) $data['remark']) : '';

+ 1 - 1
docs/前端交付/README.md

@@ -5,7 +5,7 @@
 ## 会员绑定上级(2026-11-10)
 
 - [整站与游戏维护子类-总后台前端修改单](整站与游戏维护子类-总后台前端修改单.md):维护可以细到子类(三方平台、F1 彩种、F1 体育项);列表为树形(已包含《维护列表按层级返回》的内容);子类新增「支持内嵌」开关 `embeddable`;新增菜单「F1体育列表」(`GET /admin/config/sportList`,用 `setField` 的 `sport_list` 保存)。
-- [整站与游戏维护子类-会员前台修改单](整站与游戏维护子类-会员前台修改单.md):三方平台 / 游戏、F1 彩票、F1 体育列表项新增 `is_maintaining`、`maintenance`、`embeddable`;维护中照常展示、点击提示,`embeddable=false` 时用新窗口打开;新增 `GET /api/index/sportConfig`;状态接口大类新增 `children`。
+- [游戏支持内嵌-会员前台修改单](游戏支持内嵌-会员前台修改单.md):三方平台 / 游戏、F1 彩票、F1 体育列表项新增 `embeddable`(是否支持内嵌)。
 - [会员绑定上级-前端修改单](会员绑定上级-前端修改单.md):注册时代理域名和会员邀请码同时存在,**以会员邀请码为主**,会员前台不用改。总后台会员列表新增 `can_bind_superior`,无上级的会员可以通过 `POST /admin/user/bindSuperior` 绑定合营代理(代理 ID)或上级会员(会员 ID),绑定后不能修改。
 
 ## 代理审核实时推送(2026-11-10)

+ 2 - 2
docs/前端交付/代理合营与入股-前端修改清单.md

@@ -142,8 +142,8 @@
 
 ### 5. 独立代理申请审核
 
-- 列表 `GET /admin/agent/applications` 新增 `type`、`type_text` 字段和 `type` 筛选。
-- 审核弹窗展示会员选择的类型,并提供类型下拉(默认值为申请的 `type`)。通过时不传 `type` 就按会员选的类型开通。
+- 列表 `GET /admin/agent/applications` 新增「申请类型」列,显示 `type_text`(合营 / 入股),可用 `type` 筛选。
+- 审核弹窗只读显示该行的 `type_text`,移除类型下拉和必填校验。审核请求省略 `type`,通过后按会员申请的类型开通。
 
 文档:[独立代理申请-审核接入](../总后台/独立代理申请-审核接入.md)。
 

+ 0 - 77
docs/前端交付/整站与游戏维护子类-会员前台修改单.md

@@ -1,77 +0,0 @@
-# 整站与游戏维护子类-会员前台修改单(PC / H5 / APP,2026-11-10)
-
-## 改动
-
-1. **维护可以细到子类**:除了整站和 9 个大类,后台现在可以单独维护某个三方平台(如电子里的 PG)、某个 F1 彩种(如极速赛车)或某个 F1 体育项(如篮球)。大类没维护时,其他平台 / 彩种 / 体育项照常可进。
-2. **游戏列表直接带维护状态和「是否支持内嵌」**:三方平台和游戏、F1 彩票、F1 体育的列表项都新增 `is_maintaining`、`maintenance`、`embeddable`,前端按列表项处理,不需要自己拼代码或对照编号。
-3. **新增 F1 体育列表接口** `GET /api/index/sportConfig`。
-
-接口都在 `melbet_sport-api`。原有请求头(`Platform`、`Lang`、`Authorization`)和 `11001` / `11002` 的处理方式不变。
-
-## 一、列表项新增字段
-
-| 接口 | 列表项 | 新增字段 |
-|---|---|---|
-| `/api/egame/platforms`、`/api/egame/electronicGames`、`/api/egame/gameCodes` | 三方平台、平台下的游戏 | `is_maintaining`、`maintenance`、`embeddable` |
-| `/api/index/gameConfig`、`/api/index/getLastLottery` | F1 彩种 | `maintenance_scope`、`is_maintaining`、`maintenance`、`embeddable` |
-| `/api/index/sportConfig`(新增) | F1 体育项 | 同上 |
-
-| 字段 | 说明 |
-|---|---|
-| `is_maintaining` | 该项当前是否不可进入(整站、所属大类或它自己在维护,都会是 `true`) |
-| `maintenance` | `null`,或维护详情:`content`(文案)、`ends_at_utc`(预计结束,可能为 `null`)、`category`(大类)、`subcategory`(子类,大类维护时为 `null`)等,结构同 `/api/maintenance/status` |
-| `embeddable` | 布尔值,该项是否支持内嵌打开,后台设置,默认 `true`;和维护无关。三方平台下的游戏沿用所属平台的值 |
-| `maintenance_scope` | 该项的子类代码,如 `self:lottery:pc28`、`self:sport:basketball`,用于和 `/api/maintenance/status` 对应,一般不用展示 |
-
-F1 彩种示例(`gameConfig` 一项):
-
-```json
-{"type": 7, "name": "极速赛车", "game": "jisu", "params": {"type": 1}, "status": 1, "sort": 0, "is_rec": 0,
- "maintenance_scope": "self:lottery:jisu:1", "is_maintaining": true, "embeddable": true,
- "maintenance": {"scope": "game", "category": "self:lottery", "subcategory": "self:lottery:jisu:1",
-   "content": "极速赛车维护中,请稍后再试。", "ends_at_utc": "2026-11-10T20:00:00Z", "...": "其余字段同状态接口"}}
-```
-
-## 二、点击列表项的处理
-
-1. `is_maintaining` 为 `true`:**列表照常显示,不要隐藏**,加「维护中」标记;点击时弹出 `maintenance.content`(纯文本,保留换行),不进入游戏。有 `ends_at_utc` 时可显示预计恢复时间,`null` 显示「恢复时间待定」。
-2. 否则按 `embeddable` 打开:
-   - `true`:沿用现在的方式,页面内嵌(iframe / 内嵌 WebView)打开;
-   - `false`:**不要内嵌**,改为新窗口打开(PC、H5 用 `window.open` 或整页跳转,APP 用外部浏览器或新开 WebView)。
-3. 最终以进入 / 下注接口的返回为准:列表加载后后台才开始的维护,进入时会返回 `11002`,按原有方式提示 `msg`。
-
-`11002` 的 `data` 新增 `subcategory`:单独维护某个平台 / 彩种 / 体育项时为子类代码(如 `third:2:pg`),大类维护时为 `null`;`data.category` 始终是大类代码。前端提示 `msg` 即可,不需要按它区分处理。
-
-## 三、新增 F1 体育列表 `GET /api/index/sportConfig`
-
-免登录,返回后台「F1体育列表」中显示的体育项,排序规则同 `gameConfig`:
-
-```json
-{"code": 1, "data": [
-  {"type": 1, "code": "football", "name": "足球", "status": 1, "sort": 0,
-   "maintenance_scope": "self:sport:football", "is_maintaining": false, "maintenance": null, "embeddable": true},
-  {"type": 2, "code": "basketball", "name": "篮球", "status": 1, "sort": 0,
-   "maintenance_scope": "self:sport:basketball", "is_maintaining": true, "embeddable": true,
-   "maintenance": {"scope": "game", "category": "self:sport", "subcategory": "self:sport:basketball", "content": "篮球维护中", "...": "其余字段同状态接口"}}
-]}
-```
-
-- 可用于 F1 体育入口的体育分类导航,点击处理同第二节。
-- 目前只有足球有赛事数据,F1 体育现有的赛事、下注、撤单、足球游戏接口都按「足球」判断维护。其他体育项暂时只用于展示和维护标记。
-
-## 四、维护状态接口 `GET /api/maintenance/status`
-
-`data.games` 的每个大类新增 `children`:当前单独在维护的子类(只列生效的,没有则为 `[]`)。
-
-```json
-{"category": "third:2", "is_maintaining": false, "maintenance": null,
- "children": [{"category": "third:2:pg", "parent": "third:2", "is_maintaining": true, "maintenance": {"...": "维护详情"}}]}
-```
-
-- 大类入口仍看 `games[].is_maintaining`。只有 PG 维护时电子大类是 `false`,电子入口照常可进。
-- 平台 / 彩种 / 体育项的维护标记优先用第一节列表上的字段;没有列表数据的页面可以用 `children` 判断。
-
-## 不用改的
-
-- 请求头、整站维护页(`11001`)、维护页轮询和倒计时,沿用原有接入。
-- 下注接口参数不变:`/api/game/bet` 的 `pc28` 传 `0`(加拿大28)或 `1`(极速28);`/api/jisuGameOrder/submit` 的 `data[].id` 传单个正整数玩法 ID(传数组等非法值会返回「投注项编号无效」)。

+ 18 - 0
docs/前端交付/游戏支持内嵌-会员前台修改单.md

@@ -0,0 +1,18 @@
+# 游戏支持内嵌-会员前台修改单(PC / H5 / APP,2026-11-10)
+
+## 新增字段 `embeddable`
+
+| 接口 | 列表项 |
+|---|---|
+| `/api/egame/platforms`、`/api/egame/electronicGames`、`/api/egame/gameCodes` | 三方平台、平台下的游戏(游戏沿用所属平台的值) |
+| `/api/index/gameConfig`、`/api/index/getLastLottery` | F1 彩种 |
+| `/api/index/sportConfig` | F1 体育项 |
+
+| 字段 | 类型 | 说明 |
+|---|---|---|
+| `embeddable` | 布尔 | 是否支持内嵌,默认 `true` |
+
+```json
+{"type": 7, "name": "极速赛车", "game": "jisu", "params": {"type": 1}, "status": 1, "sort": 0,
+ "maintenance_scope": "self:lottery:jisu:1", "is_maintaining": false, "maintenance": null, "embeddable": false}
+```

+ 1 - 1
docs/总后台/代理合营与入股-前端接入.md

@@ -314,7 +314,7 @@
 
 ## 10. 独立代理申请审核
 
-`POST /admin/agent/applicationAudit` 删除了 `flow_commission_profile_id`、`profit_commission_profile_id` 两个参数,新增可选的 `type`。会员在 PC 前台申请时已选择类型,不传 `type` 就按会员选的类型开通。`GET /admin/agent/applications` 新增 `type`、`type_text` 字段和 `type` 筛选。详见 `docs/总后台/独立代理申请-审核接入.md`。通过后合营代理没有方案,需要代理自己去申请。
+`GET /admin/agent/applications` 返回 `type`、`type_text`,支持按 `type` 筛选。列表新增「申请类型」列,审核弹窗以纯文本显示同一个 `type_text`。`POST /admin/agent/applicationAudit` 提交 `id`、`status`,驳回时加 `remark`。通过后按会员申请时选的类型开通,合营代理自行申请分成方案。详见[独立代理申请审核接入](独立代理申请-审核接入.md)。
 
 ## 11. 会员盈亏日报
 

+ 4 - 14
docs/总后台/独立代理申请-审核接入.md

@@ -2,15 +2,15 @@
 
 沿用后台登录凭证,成功为 `code=0`。使用已有代理管理权限。
 
-> **本次变更(代理类型)**:会员在 PC 前台申请时会选择代理类型(`1` 合营 / `2` 入股)。列表新增 `type`、`type_text` 字段和 `type` 筛选;审核通过默认按会员选的类型开通,也可以传 `type` 改成另一种。
+会员在 PC 前台选择申请类型(`1` 合营 / `2` 入股)。审核列表和弹窗都显示该类型,通过后按会员提交的类型开通。
 
 ## 1. 申请列表
 
 `GET /admin/agent/applications?page=1&limit=20&status=pending`
 
-可选筛选:`status`(`pending/approved/rejected`)、`username`(代理账号)、`member_id`(会员编号)、`type`(**新增**,`1` 合营 / `2` 入股)。
+可选筛选字段有 `status`(`pending/approved/rejected`)、`username`(代理账号)、`member_id`(会员编号)、`type`(`1` 合营 / `2` 入股)。
 
-列表在 `data.data[]`,总数为 `data.total`。每项包含 `id`(申请 ID)、`user_id`(会员 ID)、`member_id`(会员编号)、`member_account`、`username`、`type`(**新增**,代理类型)、`type_text`(**新增**,`合营` / `入股`)、`status`、`agent_id`、`reject_reason`、`created_at`、`reviewed_at`、`reviewed_by`。
+列表在 `data.data[]`,总数为 `data.total`。每项包含 `id`(申请 ID)、`user_id`(会员 ID)、`member_id`(会员编号)、`member_account`、`username`、`type`(申请类型)、`type_text`(`合营` / `入股`)、`status`、`agent_id`、`reject_reason`、`created_at`、`reviewed_at`、`reviewed_by`。新增「申请类型」列,直接显示 `type_text`。
 
 ## 2. 审核
 
@@ -30,18 +30,8 @@
 
 `id` 取申请列表的申请 ID;驳回必须填写 `remark`,最多 500 字。
 
-通过时可选传 `type`(`1` 合营 / `2` 入股):不传按会员申请时选的类型开通;传了以传的为准,例如把入股申请改为合营开通:
-
-```json
-{"id":123,"status":"approved","type":1}
-```
-
-开通后申请记录的 `type` 会更新为实际开通的类型,会员前台看到的也是这个类型。审核弹窗建议展示会员选的 `type_text`,并提供类型下拉(默认值为申请的 `type`)。
+审核弹窗以纯文本显示该行的 `type_text`,移除类型下拉和必填校验。提交审核时省略 `type`。旧页面即使提交 `type`,审核也按申请记录中的类型开通。
 
 原来的 `flow_commission_profile_id`、`profit_commission_profile_id` 参数已删除:通过后合营代理没有分成方案,由代理在代理后台自己申请。
 
 审核成功后刷新列表即可。通过会开通独立一级代理;通过、驳回都会自动发送站内信。已审核的申请不能改成另一审核结果。
-
-## 3. 绑定已有代理账号(已删除)
-
-`POST /admin/agent/create`、`/admin/agent/update` 不再接收 `owner_user_id`,会员只能通过申请审核成为代理。

+ 7 - 6
tests/Integration/AgentApplicationTest.php

@@ -256,7 +256,7 @@ class AgentApplicationTest extends TestCase
         $this->assertSame(1, DB::table('site_messages')->count());
     }
 
-    public function test_approval_opens_the_agent_type_chosen_by_the_member_unless_the_admin_overrides_it(): void
+    public function test_approval_always_uses_the_member_application_type(): void
     {
         $equity = $this->application(11, 'equityagent', Agent::TYPE_EQUITY);
         $listed = $this->service->listing(['type' => Agent::TYPE_EQUITY])['data'];
@@ -267,10 +267,11 @@ class AgentApplicationTest extends TestCase
         $this->assertSame(0, $this->audit($equity->id)['code']);
         $this->assertSame(Agent::TYPE_EQUITY, (int) Agent::where('username', 'equityagent')->value('type'));
 
-        $changed = $this->application(22, 'changedagent', Agent::TYPE_EQUITY);
-        $this->assertSame(0, $this->audit($changed->id, 'approved', ['type' => Agent::TYPE_JOINT])['code']);
-        $this->assertSame(Agent::TYPE_JOINT, (int) Agent::where('username', 'changedagent')->value('type'));
-        $this->assertSame(Agent::TYPE_JOINT, $changed->fresh()->type, 'application records the type actually opened');
-        $this->assertSame('合营', $changed->fresh()->toArray()['type_text']);
+        $joint = $this->application(22, 'jointagent', Agent::TYPE_JOINT);
+        $response = $this->audit($joint->id, 'approved', ['type' => Agent::TYPE_EQUITY]);
+        $this->assertSame(0, $response['code'], $response['msg']);
+        $this->assertSame(Agent::TYPE_JOINT, (int) Agent::where('username', 'jointagent')->value('type'));
+        $this->assertSame(Agent::TYPE_JOINT, $joint->fresh()->type);
+        $this->assertSame('合营', $this->service->listing(['id' => $joint->id])['data'][0]->type_text);
     }
 }