'desc']; protected string|array $orderGuarantee = ['id' => 'desc']; protected array $withJoinTable = ['user', 'channel', 'gameRecord']; protected function initController(WebmanRequest $request): ?Response { $this->model = new \app\common\model\PlayRecord(); return null; } public function add(WebmanRequest $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } return $this->error(__('Play record is generated by game API; manual creation is not allowed')); } public function edit(WebmanRequest $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } return $this->error(__('Play record cannot be edited')); } public function del(WebmanRequest $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } return $this->error(__('Play record cannot be deleted')); } public function sortable(WebmanRequest $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } return $this->error(__('Sorting is not supported')); } protected function _index(): Response { if ($this->request && $this->request->get('select')) { return $this->select($this->request); } list($where, $alias, $limit, $order) = $this->queryBuilder(); $table = strtolower($this->model->getTable()); $mainShort = $alias[$table] ?? ''; if ($mainShort !== '' && $this->auth && !$this->auth->isSuperAdmin()) { $where[] = ['user.admin_id', 'in', $this->scopedAdminIds()]; } // 避免 ThinkORM withJoin 对 game_record 的字段缓存导致 select 出已删除列(如 preset_number) // 这里改为手写 join + 明确 field 列表,保证数据库字段变更后不受 schema 缓存影响。 $query = Db::name($table)->alias($mainShort !== '' ? $mainShort : 'play_record') ->leftJoin('user user', 'user.id = ' . ($mainShort !== '' ? $mainShort : 'play_record') . '.user_id') ->leftJoin('channel channel', 'channel.id = ' . ($mainShort !== '' ? $mainShort : 'play_record') . '.channel_id') ->leftJoin('game_record game_record', 'game_record.id = ' . ($mainShort !== '' ? $mainShort : 'play_record') . '.period_id') ->where($where); $res = $query ->field([ ($mainShort !== '' ? $mainShort : 'play_record') . '.*', 'user.username as user__username', 'user.phone as user__phone', 'channel.name as channel__name', 'game_record.period_no as gameRecord__period_no', 'game_record.status as gameRecord__status', 'game_record.result_number as gameRecord__result_number', ]) ->order($order) ->paginate($limit); $list = $res->items(); $total = $res->total(); // 将 join 扁平字段还原为原页面所需结构:user/channel/gameRecord foreach ($list as $idx => $row) { if (!is_array($row)) { continue; } $row['user'] = [ 'username' => isset($row['user__username']) ? (string) $row['user__username'] : '', 'phone' => isset($row['user__phone']) ? (string) $row['user__phone'] : '', ]; $row['channel'] = [ 'name' => isset($row['channel__name']) ? (string) $row['channel__name'] : '', ]; $row['gameRecord'] = [ 'period_no' => isset($row['gameRecord__period_no']) ? (string) $row['gameRecord__period_no'] : '', 'status' => isset($row['gameRecord__status']) && is_numeric((string) $row['gameRecord__status']) ? (int) $row['gameRecord__status'] : null, 'result_number' => isset($row['gameRecord__result_number']) && $row['gameRecord__result_number'] !== '' && $row['gameRecord__result_number'] !== null ? (string) $row['gameRecord__result_number'] : null, ]; unset( $row['user__username'], $row['user__phone'], $row['channel__name'], $row['gameRecord__period_no'], $row['gameRecord__status'], $row['gameRecord__result_number'], ); $list[$idx] = $row; } $threshold = $this->jackpotMaxAmount(); foreach ($list as $idx => $row) { if (!is_array($row)) { continue; } $status = isset($row['status']) && is_numeric($row['status']) ? (int) $row['status'] : 0; $win = bcadd(strval($row['win_amount'] ?? '0'), '0', 2); $needReview = bccomp($threshold, '0', 2) > 0 && bccomp($win, $threshold, 2) >= 0; $canApprove = $needReview && $status === GameBetSettleService::PLAY_STATUS_PENDING_REVIEW; $row['jackpot_need_review'] = $needReview ? 1 : 0; $row['can_jackpot_approve'] = $canApprove ? 1 : 0; $list[$idx] = $row; } return $this->success('', [ 'list' => $list, 'total' => $total, 'remark' => get_route_remark(), ]); } /** * 大奖审核通过并派彩(仅 win_amount >= game_config.jackpot_max_amount 且 status=待审核 才可操作) */ public function approveJackpot(WebmanRequest $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } if ($request->method() !== 'POST') { return $this->error(__('Parameter error')); } $idRaw = $request->post('id'); if ($idRaw === null || $idRaw === '' || !is_numeric(strval($idRaw))) { return $this->error(__('Parameter error')); } $id = (int) $idRaw; if ($id <= 0) { return $this->error(__('Parameter error')); } $remarkRaw = $request->post('remark'); $remark = is_string($remarkRaw) ? trim($remarkRaw) : ''; // 权限范围校验:复用列表逻辑(非超管只能操作其下辖用户) if ($this->auth && !$this->auth->isSuperAdmin()) { $uidRaw = Db::name('game_play_record')->where('id', $id)->value('user_id'); $uid = ($uidRaw === null || $uidRaw === '' || !is_numeric(strval($uidRaw))) ? 0 : (int) $uidRaw; if ($uid <= 0) { return $this->error(__('Record not found')); } $ownerAdminId = Db::name('user')->where('id', $uid)->value('admin_id'); $aid = ($ownerAdminId === null || $ownerAdminId === '' || !is_numeric(strval($ownerAdminId))) ? 0 : (int) $ownerAdminId; if ($aid <= 0 || !in_array($aid, $this->scopedAdminIds(), true)) { return $this->error(__('You have no permission')); } } $adminId = $this->auth ? (int) ($this->auth->id ?? 0) : 0; Db::startTrans(); try { $result = GameBetSettleService::approveJackpotPlayRecord($id, $adminId, $remark); if (($result['ok'] ?? false) !== true) { Db::rollback(); $msg = is_string($result['msg'] ?? null) ? $result['msg'] : __('Parameter error'); return $this->error($msg); } Db::commit(); } catch (\Throwable $e) { Db::rollback(); return $this->error($e->getMessage()); } return $this->success(__('Approved')); } /** * 大奖审核拒绝(remark 必填) */ public function rejectJackpot(WebmanRequest $request): Response { $response = $this->initializeBackend($request); if ($response !== null) { return $response; } if ($request->method() !== 'POST') { return $this->error(__('Parameter error')); } $idRaw = $request->post('id'); if ($idRaw === null || $idRaw === '' || !is_numeric(strval($idRaw))) { return $this->error(__('Parameter error')); } $id = (int) $idRaw; if ($id <= 0) { return $this->error(__('Parameter error')); } $remarkRaw = $request->post('remark'); $remark = is_string($remarkRaw) ? trim($remarkRaw) : ''; if ($remark === '') { return $this->error(__('Please provide reject reason')); } if ($this->auth && !$this->auth->isSuperAdmin()) { $uidRaw = Db::name('game_play_record')->where('id', $id)->value('user_id'); $uid = ($uidRaw === null || $uidRaw === '' || !is_numeric(strval($uidRaw))) ? 0 : (int) $uidRaw; if ($uid <= 0) { return $this->error(__('Record not found')); } $ownerAdminId = Db::name('user')->where('id', $uid)->value('admin_id'); $aid = ($ownerAdminId === null || $ownerAdminId === '' || !is_numeric(strval($ownerAdminId))) ? 0 : (int) $ownerAdminId; if ($aid <= 0 || !in_array($aid, $this->scopedAdminIds(), true)) { return $this->error(__('You have no permission')); } } $adminId = $this->auth ? (int) ($this->auth->id ?? 0) : 0; Db::startTrans(); try { $result = GameBetSettleService::rejectJackpotPlayRecord($id, $adminId, $remark); if (($result['ok'] ?? false) !== true) { Db::rollback(); $msg = is_string($result['msg'] ?? null) ? $result['msg'] : __('Parameter error'); return $this->error($msg); } Db::commit(); } catch (\Throwable $e) { Db::rollback(); return $this->error($e->getMessage()); } return $this->success(__('Rejected')); } /** * @return int[] */ private function scopedAdminIds(): array { if (!$this->auth) { return [0]; } if ($this->auth->isSuperAdmin()) { return []; } $groupIds = $this->auth->getAdminChildGroups(); $adminIds = $groupIds ? $this->auth->getGroupAdmins($groupIds) : []; $adminIds[] = $this->auth->id; $adminIds = array_map(static fn($id) => intval(strval($id)), $adminIds); $adminIds = array_values(array_unique(array_filter($adminIds, static fn($id) => $id > 0))); return $adminIds === [] ? [0] : $adminIds; } private function jackpotMaxAmount(): string { $row = Db::name('game_config')->where('config_key', GameBetSettleService::CONFIG_KEY_JACKPOT_MAX_AMOUNT)->find(); if (!is_array($row)) { return '0.00'; } $raw = $row['config_value'] ?? null; if ($raw === null || $raw === '') { return '0.00'; } $v = is_string($raw) ? trim($raw) : (is_numeric($raw) ? strval($raw) : ''); if ($v === '' || !is_numeric($v)) { return '0.00'; } $normalized = bcadd($v, '0', 2); if (bccomp($normalized, '0', 2) <= 0) { return '0.00'; } return $normalized; } }