From 54f460a242968eb269d9f870d71d60637d6ed7b9 Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Thu, 16 Apr 2026 13:39:08 +0800 Subject: [PATCH] =?UTF-8?q?[=E6=B8=A0=E9=81=93=E7=AE=A1=E7=90=86]-?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A0=B7=E5=BC=8F,=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=89=8B=E5=8A=A8=E7=BB=93=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/Channel.php | 500 +++++++++++++++++- app/admin/controller/auth/Admin.php | 2 +- app/admin/controller/order/BetOrder.php | 5 +- app/admin/controller/order/DepositOrder.php | 5 +- app/admin/controller/order/WithdrawOrder.php | 5 +- .../controller/record/UserWalletRecord.php | 5 +- app/common/model/Channel.php | 4 + .../lang/backend/en/agent/commissionRecord.ts | 6 +- web/src/lang/backend/en/channel.ts | 43 +- .../backend/zh-cn/agent/commissionRecord.ts | 6 +- web/src/lang/backend/zh-cn/channel.ts | 43 +- .../agent/commissionRecord/popupForm.vue | 45 +- web/src/views/backend/channel/index.vue | 194 ++++++- web/src/views/backend/channel/popupForm.vue | 405 +++++++++++++- 14 files changed, 1184 insertions(+), 84 deletions(-) diff --git a/app/admin/controller/Channel.php b/app/admin/controller/Channel.php index d4027f8..83a7249 100644 --- a/app/admin/controller/Channel.php +++ b/app/admin/controller/Channel.php @@ -13,6 +13,11 @@ use Webman\Http\Request as WebmanRequest; */ class Channel extends Backend { + /** + * 预览接口与手动结算共用「手动结算」按钮权限(避免额外菜单节点) + */ + protected array $noNeedPermission = ['manualSettlePreview']; + /** * Channel模型对象 * @var object|null @@ -137,6 +142,10 @@ class Channel extends Backend $data = $this->applyInputFilter($data); $data = $this->excludeFields($data); $data = $this->normalizeAgentModeFields($data); + $bizErr = $this->validateAndNormalizeBusinessFields($data); + if ($bizErr !== null) { + return $this->error($bizErr); + } unset($data['invite_code']); $adminId = $data['admin_id'] ?? null; @@ -160,7 +169,6 @@ class Channel extends Backend } $data['admin_group_id'] = $topGroupId; if (!$this->auth->isSuperAdmin()) { - $data['top_admin_id'] = $this->auth->id; $data['admin_id'] = $this->auth->id; } @@ -226,6 +234,10 @@ class Channel extends Backend $data = $this->applyInputFilter($data); $data = $this->excludeFields($data); $data = $this->normalizeAgentModeFields($data); + $bizErr = $this->validateAndNormalizeBusinessFields($data); + if ($bizErr !== null) { + return $this->error($bizErr); + } unset($data['invite_code']); if (array_key_exists('admin_group_id', $data)) { @@ -247,7 +259,6 @@ class Channel extends Backend $data['admin_group_id'] = $topGroupId; } if (!$this->auth->isSuperAdmin()) { - $data['top_admin_id'] = $this->auth->id; $data['admin_id'] = $this->auth->id; } @@ -312,6 +323,353 @@ class Channel extends Backend ]); } + /** + * 手动结算预览:区间=上次结算周期结束~当前时间;金额来自已结算注单汇总(服务端计算) + */ + public function manualSettlePreview(WebmanRequest $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + if (!$this->auth->check('channel/manualSettle')) { + return $this->error(__('You have no permission')); + } + + $id = (int) ($request->get('id', 0)); + if ($id <= 0) { + return $this->error(__('Parameter error')); + } + $row = $this->model->find($id); + if (!$row) { + return $this->error(__('Record not found')); + } + if (!$this->auth->isSuperAdmin() && !in_array((int) $row['id'], $this->currentChannelIds, true)) { + return $this->error(__('You have no permission')); + } + + $payload = $this->buildManualSettlePayload($row->toArray()); + if (is_string($payload)) { + return $this->error($payload); + } + + return $this->success('', $payload); + } + + /** + * 手动结算(渠道维度):仅接收备注;周期与金额全部由服务端按注单汇总计算,并写入结算周期与佣金记录 + */ + public function manualSettle(WebmanRequest $request): Response + { + $response = $this->initializeBackend($request); + if ($response !== null) { + return $response; + } + + $id = (int) ($request->post('id', $request->get('id', 0))); + if ($id <= 0) { + return $this->error(__('Parameter error')); + } + $row = $this->model->find($id); + if (!$row) { + return $this->error(__('Record not found')); + } + if (!$this->auth->isSuperAdmin() && !in_array((int) $row['id'], $this->currentChannelIds, true)) { + return $this->error(__('You have no permission')); + } + + $remark = (string) $request->post('remark', ''); + + $payload = $this->buildManualSettlePayload($row->toArray()); + if (is_string($payload)) { + return $this->error($payload); + } + + $settlementNo = $payload['settlement_no']; + if (Db::name('agent_settlement_period')->where('settlement_no', $settlementNo)->value('id')) { + return $this->error('结算单号已存在,请稍后重试'); + } + + $adminId = $row['admin_id'] ?? null; + if ($adminId === null || $adminId === '' || (int) $adminId <= 0) { + return $this->error('渠道未绑定代理管理员,无法生成佣金记录'); + } + + $now = time(); + Db::startTrans(); + try { + $periodId = (int) Db::name('agent_settlement_period')->insertGetId([ + 'settlement_no' => $settlementNo, + 'period_start_at' => $payload['period_start_ts'], + 'period_end_at' => $payload['period_end_ts'], + 'total_bet_amount' => $payload['total_bet_amount'], + 'total_payout_amount' => $payload['total_payout_amount'], + 'platform_profit_amount' => $payload['platform_profit_amount'], + 'status' => 2, + 'remark' => trim($remark) !== '' ? $remark : ('手动结算-渠道#' . $row['id'] . '-' . $row['name']), + 'create_time' => $now, + 'update_time' => $now, + ]); + + Db::name('agent_commission_record')->insert([ + 'settlement_period_id' => $periodId, + 'channel_id' => (int) $row['id'], + 'admin_id' => (int) $adminId, + 'commission_rate' => $payload['commission_rate'], + 'calc_base_amount' => $payload['calc_base_amount'], + 'commission_amount' => $payload['commission_amount'], + 'status' => 0, + 'settled_at' => null, + 'remark' => trim($remark) !== '' ? $remark : ('手动结算佣金-CH' . $row['id']), + 'create_time' => $now, + 'update_time' => $now, + ]); + + Db::name('channel')->where('id', $row['id'])->update([ + 'update_time' => $now, + ]); + Db::commit(); + } catch (Throwable $e) { + Db::rollback(); + return $this->error($e->getMessage()); + } + + return $this->success('手动结算已完成,已生成结算周期与佣金记录'); + } + + /** + * @return array|string 成功返回预览数据数组,失败返回错误文案 + */ + private function buildManualSettlePayload(array $row): array|string + { + $channelId = (int) ($row['id'] ?? 0); + if ($channelId <= 0) { + return '渠道数据异常'; + } + + $endTs = time(); + $lastEnd = $this->getLastSettlementEndForChannel($channelId); + $channelCreateTs = (int) ($row['create_time'] ?? 0); + if ($lastEnd === null) { + $periodStartTs = $channelCreateTs > 0 ? $channelCreateTs : 0; + } else { + $periodStartTs = (int) $lastEnd; + } + + if ($periodStartTs >= $endTs) { + return '结算区间无效(开始时间不早于当前)'; + } + + $stats = $this->aggregateBetOrderForChannel($channelId, $periodStartTs, $lastEnd !== null, $endTs); + $totalBet = $stats['total_bet']; + $totalPayout = $stats['total_payout']; + $profit = bcsub($totalBet, $totalPayout, 4); + + $mode = (string) ($row['agent_mode'] ?? 'turnover'); + $commission = $this->computeCommissionAmounts($row, $totalBet, $profit, $mode); + if (is_string($commission)) { + return $commission; + } + + $settlementNo = $this->generateAgentSettlementNo('M', $channelId, $endTs); + + return [ + 'settlement_no' => $settlementNo, + 'period_start_ts' => $periodStartTs, + 'period_end_ts' => $endTs, + 'period_start_at' => date('Y-m-d H:i:s', $periodStartTs), + 'period_end_at' => date('Y-m-d H:i:s', $endTs), + 'total_bet_amount' => $totalBet, + 'total_payout_amount' => $totalPayout, + 'platform_profit_amount' => $profit, + 'commission_rate' => $commission['commission_rate'], + 'calc_base_amount' => $commission['calc_base_amount'], + 'commission_amount' => $commission['commission_amount'], + 'agent_mode' => $mode, + ]; + } + + /** + * 生成代理结算周期单号:仅大写字母与数字、无分隔符;首字符 M=手动结算,A=自动结算(定时任务等复用) + */ + private function generateAgentSettlementNo(string $sourceFlag, int $channelId, int $endTs): string + { + $flag = strtoupper(trim($sourceFlag)); + if ($flag !== 'M' && $flag !== 'A') { + $flag = 'M'; + } + $channelPart = str_pad((string) max(0, $channelId), 6, '0', STR_PAD_LEFT); + $timePart = str_pad((string) max(0, $endTs), 10, '0', STR_PAD_LEFT); + + $base = $flag . $channelPart . $timePart; + for ($i = 0; $i < 8; $i++) { + $randPart = strtoupper(substr(bin2hex(random_bytes(4)), 0, 8)); + $no = $base . $randPart; + if (!Db::name('agent_settlement_period')->where('settlement_no', $no)->value('id')) { + return $no; + } + } + + return $base . strtoupper(substr(bin2hex(random_bytes(8)), 0, 16)); + } + + private function getLastSettlementEndForChannel(int $channelId): ?int + { + $row = Db::name('agent_commission_record') + ->alias('acr') + ->join('agent_settlement_period asp', 'acr.settlement_period_id = asp.id') + ->where('acr.channel_id', $channelId) + ->field('MAX(asp.period_end_at) AS m') + ->find(); + if (!$row) { + return null; + } + $m = $row['m'] ?? null; + if ($m === null || $m === '') { + return null; + } + return (int) $m; + } + + /** + * @return array{total_bet:string,total_payout:string} + */ + private function aggregateBetOrderForChannel(int $channelId, int $periodStartTs, bool $hasPriorSettlement, int $endTs): array + { + $query = Db::name('bet_order') + ->where('channel_id', $channelId) + ->where('status', 2) + ->where('create_time', '<=', $endTs); + if ($hasPriorSettlement) { + $query->where('create_time', '>', $periodStartTs); + } else { + $query->where('create_time', '>=', $periodStartTs); + } + + $row = $query->field('SUM(total_amount) AS tb, SUM(win_amount) AS tw, SUM(jackpot_extra_amount) AS tj')->find(); + $tb = $row && $row['tb'] !== null && $row['tb'] !== '' ? (string) $row['tb'] : '0.0000'; + $tw = $row && $row['tw'] !== null && $row['tw'] !== '' ? (string) $row['tw'] : '0.0000'; + $tj = $row && $row['tj'] !== null && $row['tj'] !== '' ? (string) $row['tj'] : '0.0000'; + $totalPayout = bcadd($tw, $tj, 4); + + return [ + 'total_bet' => number_format((float) $tb, 4, '.', ''), + 'total_payout' => number_format((float) $totalPayout, 4, '.', ''), + ]; + } + + /** + * @return array{commission_rate:string,calc_base_amount:string,commission_amount:string}|string + */ + private function computeCommissionAmounts(array $row, string $totalBet, string $platformProfit, string $mode): array|string + { + if ($mode === 'turnover') { + $ratePercent = $row['turnover_share_rate'] ?? null; + if ($ratePercent === null || $ratePercent === '') { + return '普通返水代理未配置返水分红比例'; + } + $rateDec = bcdiv((string) $ratePercent, '100', 6); + $amount = bcmul($totalBet, $rateDec, 4); + return [ + 'commission_rate' => $rateDec, + 'calc_base_amount' => $totalBet, + 'commission_amount' => $amount, + ]; + } + + if ($mode === 'affiliate') { + $fee = $row['affiliate_fee_rate'] ?? null; + $rulesRaw = $row['affiliate_ladder_rules'] ?? null; + if ($fee === null || $fee === '') { + return '联营代理未配置成本扣除比例'; + } + $rules = $this->normalizeLadderRulesForSettlement($rulesRaw); + if ($rules === []) { + return '联营阶梯规则无效或为空'; + } + + if (bccomp($platformProfit, '0', 4) <= 0) { + return [ + 'commission_rate' => '0.000000', + 'calc_base_amount' => '0.0000', + 'commission_amount' => '0.0000', + ]; + } + + $afterFee = bcmul($platformProfit, bcsub('1', (string) $fee, 8), 4); + if (bccomp($afterFee, '0', 4) <= 0) { + return [ + 'commission_rate' => '0.000000', + 'calc_base_amount' => '0.0000', + 'commission_amount' => '0.0000', + ]; + } + + $playerLoss = $platformProfit; + $share = $this->pickAffiliateShareRateFromLadder($rules, $playerLoss); + $rateDec = number_format($share, 6, '.', ''); + $amount = bcmul($afterFee, $rateDec, 4); + + return [ + 'commission_rate' => $rateDec, + 'calc_base_amount' => $afterFee, + 'commission_amount' => $amount, + ]; + } + + return '未知的代理模式'; + } + + /** + * @return array + */ + private function normalizeLadderRulesForSettlement(mixed $rulesRaw): array + { + if ($rulesRaw === null || $rulesRaw === '') { + return []; + } + if (is_string($rulesRaw)) { + $decoded = json_decode($rulesRaw, true); + $rulesRaw = is_array($decoded) ? $decoded : []; + } + if (!is_array($rulesRaw)) { + return []; + } + $out = []; + foreach ($rulesRaw as $rule) { + if (!is_array($rule)) { + continue; + } + $minLoss = $rule['minLoss'] ?? ($rule['min_loss'] ?? null); + $shareRate = $rule['shareRate'] ?? ($rule['share_rate'] ?? null); + if ($minLoss === null || $shareRate === null || !is_numeric((string) $minLoss) || !is_numeric((string) $shareRate)) { + continue; + } + $out[] = [ + 'minLoss' => number_format((float) $minLoss, 4, '.', ''), + 'shareRate' => number_format((float) $shareRate, 6, '.', ''), + ]; + } + usort($out, function ($a, $b) { + return bccomp($a['minLoss'], $b['minLoss'], 4); + }); + return $out; + } + + /** + * @param array $rules + */ + private function pickAffiliateShareRateFromLadder(array $rules, string $playerLoss): float + { + $chosen = (float) $rules[0]['shareRate']; + foreach ($rules as $rule) { + if (bccomp($playerLoss, $rule['minLoss'], 4) >= 0) { + $chosen = (float) $rule['shareRate']; + } + } + return $chosen; + } + private function getCurrentChannelIds(): array { if ($this->auth->isSuperAdmin()) { @@ -325,18 +683,31 @@ class Channel extends Backend if ($admin && !empty($admin['channel_id'])) { $ids[] = $admin['channel_id']; } - $owned = Db::name('channel')->where('top_admin_id', $this->auth->id)->column('id'); - $created = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); - return array_values(array_unique(array_merge($ids, $owned, $created))); + $byAdmin = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); + return array_values(array_unique(array_merge($ids, $byAdmin))); } private function normalizeAgentModeFields(array $data): array { $mode = $data['agent_mode'] ?? null; + if (empty($data['settle_cycle'])) { + $data['settle_cycle'] = 'weekly'; + } + if (empty($data['settle_weekday'])) { + $data['settle_weekday'] = 1; + } + if (empty($data['settle_time'])) { + $data['settle_time'] = '02:00:00'; + } if ($mode === 'turnover') { $data['affiliate_share_rate'] = null; $data['affiliate_fee_rate'] = null; $data['carryover_balance'] = 0; + $data['affiliate_contract_no'] = null; + $data['affiliate_contract_name'] = null; + $data['affiliate_ladder_rules'] = null; + $data['affiliate_effective_start_at'] = null; + $data['affiliate_effective_end_at'] = null; return $data; } if ($mode === 'affiliate') { @@ -344,4 +715,123 @@ class Channel extends Backend } return $data; } + + private function validateAndNormalizeBusinessFields(array &$data): ?string + { + $cycle = isset($data['settle_cycle']) ? trim((string) $data['settle_cycle']) : 'weekly'; + if (!in_array($cycle, ['daily', 'weekly', 'monthly'], true)) { + return '结算周期不合法'; + } + $data['settle_cycle'] = $cycle; + + $settleTime = isset($data['settle_time']) ? trim((string) $data['settle_time']) : '02:00:00'; + if (!preg_match('/^\d{2}:\d{2}:\d{2}$/', $settleTime)) { + return '结算时间格式不正确(HH:mm:ss)'; + } + $data['settle_time'] = $settleTime; + + if ($cycle === 'weekly') { + $weekday = isset($data['settle_weekday']) ? (int) $data['settle_weekday'] : 1; + if ($weekday < 1 || $weekday > 7) { + return '周结必须选择周一到周日'; + } + $data['settle_weekday'] = $weekday; + } else { + $data['settle_weekday'] = 1; + } + + if ($cycle === 'monthly') { + $monthday = isset($data['settle_monthday']) ? (int) $data['settle_monthday'] : 1; + if ($monthday < 1 || $monthday > 31) { + return '月结日期必须在1到31之间'; + } + $data['settle_monthday'] = $monthday; + } else { + $data['settle_monthday'] = 1; + } + + $mode = isset($data['agent_mode']) ? (string) $data['agent_mode'] : ''; + if ($mode === 'turnover') { + if (isset($data['turnover_share_rate']) && $data['turnover_share_rate'] !== '' && $data['turnover_share_rate'] !== null) { + $num = (float) $data['turnover_share_rate']; + if ($num < 0 || $num > 100) { + return '返水分红比例必须在0到100之间'; + } + } + return null; + } + + if ($mode === 'affiliate') { + foreach (['affiliate_share_rate' => '联营占成比例', 'affiliate_fee_rate' => '联营成本扣除比例'] as $field => $label) { + if (!isset($data[$field]) || $data[$field] === '' || $data[$field] === null) { + return $label . '不能为空'; + } + $num = (float) $data[$field]; + if ($num < 0 || $num > 1) { + return $label . '必须在0到1之间'; + } + } + $ladderErr = $this->validateLadderRulesField($data); + if ($ladderErr !== null) { + return $ladderErr; + } + } + + return null; + } + + private function validateLadderRulesField(array &$data): ?string + { + $rulesRaw = $data['affiliate_ladder_rules'] ?? null; + if ($rulesRaw === null || $rulesRaw === '') { + return '联营阶梯规则不能为空'; + } + + if (is_string($rulesRaw)) { + $decoded = json_decode($rulesRaw, true); + if (!is_array($decoded)) { + return '联营阶梯规则必须是有效JSON数组'; + } + $rulesRaw = $decoded; + } + + if (!is_array($rulesRaw) || $rulesRaw === []) { + return '联营阶梯规则至少需要一条'; + } + + $normalized = []; + $prevMinLoss = null; + foreach ($rulesRaw as $idx => $rule) { + if (!is_array($rule)) { + return '联营阶梯规则第' . ($idx + 1) . '行格式错误'; + } + $minLoss = $rule['minLoss'] ?? ($rule['min_loss'] ?? null); + $shareRate = $rule['shareRate'] ?? ($rule['share_rate'] ?? null); + if ($minLoss === null || $minLoss === '' || !is_numeric((string) $minLoss)) { + return '联营阶梯规则第' . ($idx + 1) . '行起始客损格式错误'; + } + if ($shareRate === null || $shareRate === '' || !is_numeric((string) $shareRate)) { + return '联营阶梯规则第' . ($idx + 1) . '行占成比例格式错误'; + } + $minLossNum = (float) $minLoss; + $shareRateNum = (float) $shareRate; + if ($minLossNum < 0) { + return '联营阶梯规则第' . ($idx + 1) . '行起始客损不能为负'; + } + if ($shareRateNum < 0 || $shareRateNum > 1) { + return '联营阶梯规则第' . ($idx + 1) . '行占成比例必须在0到1之间'; + } + if ($prevMinLoss !== null && $minLossNum <= $prevMinLoss) { + return '联营阶梯规则需按起始客损递增'; + } + $prevMinLoss = $minLossNum; + $normalized[] = [ + 'minLoss' => number_format($minLossNum, 4, '.', ''), + 'shareRate' => number_format($shareRateNum, 6, '.', ''), + ]; + } + + $data['affiliate_ladder_rules'] = $normalized; + return null; + } } diff --git a/app/admin/controller/auth/Admin.php b/app/admin/controller/auth/Admin.php index d6a494c..294d2da 100644 --- a/app/admin/controller/auth/Admin.php +++ b/app/admin/controller/auth/Admin.php @@ -464,7 +464,7 @@ class Admin extends Backend return $currentAdmin['channel_id']; } $channelId = Db::name('channel') - ->where('top_admin_id', $this->auth->id) + ->where('admin_id', $this->auth->id) ->value('id'); return $channelId ?: null; } diff --git a/app/admin/controller/order/BetOrder.php b/app/admin/controller/order/BetOrder.php index d5187bd..f583b4e 100644 --- a/app/admin/controller/order/BetOrder.php +++ b/app/admin/controller/order/BetOrder.php @@ -122,8 +122,7 @@ class BetOrder extends Backend if ($admin && !empty($admin['channel_id'])) { $ids[] = $admin['channel_id']; } - $owned = Db::name('channel')->where('top_admin_id', $this->auth->id)->column('id'); - $created = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); - return array_values(array_unique(array_merge($ids, $owned, $created))); + $byAdmin = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); + return array_values(array_unique(array_merge($ids, $byAdmin))); } } diff --git a/app/admin/controller/order/DepositOrder.php b/app/admin/controller/order/DepositOrder.php index 38496d0..4b6d6a5 100644 --- a/app/admin/controller/order/DepositOrder.php +++ b/app/admin/controller/order/DepositOrder.php @@ -79,8 +79,7 @@ class DepositOrder extends Backend if ($admin && !empty($admin['channel_id'])) { $ids[] = $admin['channel_id']; } - $owned = Db::name('channel')->where('top_admin_id', $this->auth->id)->column('id'); - $created = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); - return array_values(array_unique(array_merge($ids, $owned, $created))); + $byAdmin = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); + return array_values(array_unique(array_merge($ids, $byAdmin))); } } diff --git a/app/admin/controller/order/WithdrawOrder.php b/app/admin/controller/order/WithdrawOrder.php index 8d2709e..dae7b2c 100644 --- a/app/admin/controller/order/WithdrawOrder.php +++ b/app/admin/controller/order/WithdrawOrder.php @@ -80,8 +80,7 @@ class WithdrawOrder extends Backend if ($admin && !empty($admin['channel_id'])) { $ids[] = $admin['channel_id']; } - $owned = Db::name('channel')->where('top_admin_id', $this->auth->id)->column('id'); - $created = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); - return array_values(array_unique(array_merge($ids, $owned, $created))); + $byAdmin = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); + return array_values(array_unique(array_merge($ids, $byAdmin))); } } diff --git a/app/admin/controller/record/UserWalletRecord.php b/app/admin/controller/record/UserWalletRecord.php index fa2d638..eb6537d 100644 --- a/app/admin/controller/record/UserWalletRecord.php +++ b/app/admin/controller/record/UserWalletRecord.php @@ -124,8 +124,7 @@ class UserWalletRecord extends Backend if ($admin && !empty($admin['channel_id'])) { $ids[] = $admin['channel_id']; } - $owned = Db::name('channel')->where('top_admin_id', $this->auth->id)->column('id'); - $created = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); - return array_values(array_unique(array_merge($ids, $owned, $created))); + $byAdmin = Db::name('channel')->where('admin_id', $this->auth->id)->column('id'); + return array_values(array_unique(array_merge($ids, $byAdmin))); } } diff --git a/app/common/model/Channel.php b/app/common/model/Channel.php index e656ce8..bb4836f 100644 --- a/app/common/model/Channel.php +++ b/app/common/model/Channel.php @@ -19,6 +19,10 @@ class Channel extends Model protected $type = [ 'create_time' => 'integer', 'update_time' => 'integer', + 'affiliate_effective_start_at' => 'integer', + 'affiliate_effective_end_at' => 'integer', + 'settle_weekday' => 'integer', + 'settle_monthday' => 'integer', ]; public function getprofitAmountAttr($value): ?float diff --git a/web/src/lang/backend/en/agent/commissionRecord.ts b/web/src/lang/backend/en/agent/commissionRecord.ts index 071140b..558b06d 100644 --- a/web/src/lang/backend/en/agent/commissionRecord.ts +++ b/web/src/lang/backend/en/agent/commissionRecord.ts @@ -1,11 +1,11 @@ export default { 'quick Search Fields': 'ID/Settlement period ID/Remark', id: 'ID', - settlement_period_id: 'Settlement period ID', + settlement_period_id: 'Settlement period', settlement_period_no: 'Settlement no.', - channel_id: 'Channel ID', + channel_id: 'Channel', channel_name: 'Channel', - admin_id: 'Agent admin ID', + admin_id: 'Agent admin', admin_username: 'Agent username', commission_rate: 'Commission rate', calc_base_amount: 'Calculation base amount', diff --git a/web/src/lang/backend/en/channel.ts b/web/src/lang/backend/en/channel.ts index 720d0d8..16cae8a 100644 --- a/web/src/lang/backend/en/channel.ts +++ b/web/src/lang/backend/en/channel.ts @@ -3,7 +3,6 @@ export default { code: 'code', invite_code: 'invite_code', name: 'name', - top_admin_id: 'top_admin_id', agent_mode: 'agent_mode', 'agent_mode turnover': 'turnover', 'agent_mode affiliate': 'affiliate', @@ -18,6 +17,34 @@ export default { turnover_share_rate: 'turnover_share_rate', affiliate_share_rate: 'affiliate_share_rate', affiliate_fee_rate: 'affiliate_fee_rate', + affiliate_contract_no: 'affiliate_contract_no', + affiliate_contract_name: 'affiliate_contract_name', + settle_cycle: 'settlement_cycle', + settle_cycle_placeholder: 'Select settlement cycle (daily/weekly day/monthly date)', + 'settle_cycle daily': 'daily', + 'settle_cycle weekly': 'weekly', + 'settle_cycle monthly': 'monthly', + settle_weekday: 'settlement_weekday', + settle_time: 'settlement_time', + 'weekday 1': 'Mon', + 'weekday 2': 'Tue', + 'weekday 3': 'Wed', + 'weekday 4': 'Thu', + 'weekday 5': 'Fri', + 'weekday 6': 'Sat', + 'weekday 7': 'Sun', + affiliate_effective_start_at: 'affiliate_effective_start_at', + affiliate_effective_end_at: 'affiliate_effective_end_at', + affiliate_ladder_rules: 'affiliate_ladder_rules', + affiliate_ladder_rules_placeholder: 'Input JSON, e.g. [{\"minLoss\":\"0.0000\",\"shareRate\":\"0.200000\"}]', + ladder_min_loss: 'min loss', + ladder_share_rate: 'share rate', + ladder_rule_required: 'At least one ladder rule is required', + ladder_min_loss_invalid: 'Ladder min loss must be a number >= 0', + ladder_share_rate_invalid: 'Ladder share rate must be between 0 and 1', + ladder_min_loss_order_invalid: 'Ladder rules must be sorted by min loss ascending', + settle_day_daily: 'Every day', + day_suffix: 'd', carryover_balance: 'carryover_balance', user_count: 'user_count', profit_amount: 'profit_amount', @@ -30,6 +57,20 @@ export default { admin_group_id: 'admin_group_id', admingroup__name: 'name', admin_id: 'admin_id', + admin_tree_tip: 'Admins are grouped by channel. Pick a leaf under a channel name. One channel maps to one admin.', + manual_settle: 'Manual settle', + manual_settle_confirm: 'Confirm trigger manual settlement for this channel?', + manual_settle_settlement_no: 'Settlement No.', + manual_settle_period_start: 'Period start', + manual_settle_period_end: 'Period end (now)', + manual_settle_total_bet: 'Total bet (settled bets)', + manual_settle_total_payout: 'Total payout', + manual_settle_platform_profit: 'Platform PnL', + manual_settle_commission_rate: 'Commission rate (decimal)', + manual_settle_calc_base: 'Settlement base', + manual_settle_commission_amount: 'Commission amount', + manual_settle_remark: 'Remark', + admin_id_placeholder: 'Select a channel admin account', admin__username: 'username', create_time: 'create_time', update_time: 'update_time', diff --git a/web/src/lang/backend/zh-cn/agent/commissionRecord.ts b/web/src/lang/backend/zh-cn/agent/commissionRecord.ts index 9690a86..86fd125 100644 --- a/web/src/lang/backend/zh-cn/agent/commissionRecord.ts +++ b/web/src/lang/backend/zh-cn/agent/commissionRecord.ts @@ -1,11 +1,11 @@ export default { 'quick Search Fields': 'ID/结算周期ID/备注', id: 'ID', - settlement_period_id: '结算周期ID', + settlement_period_id: '结算周期', settlement_period_no: '结算周期号', - channel_id: '渠道ID', + channel_id: '渠道', channel_name: '渠道名称', - admin_id: '代理管理员ID', + admin_id: '代理管理员', admin_username: '代理账号', commission_rate: '佣金比例', calc_base_amount: '结算基数', diff --git a/web/src/lang/backend/zh-cn/channel.ts b/web/src/lang/backend/zh-cn/channel.ts index ec53d48..764a86d 100644 --- a/web/src/lang/backend/zh-cn/channel.ts +++ b/web/src/lang/backend/zh-cn/channel.ts @@ -3,7 +3,6 @@ export default { code: '渠道标识', invite_code: '渠道邀请码', name: '渠道名', - top_admin_id: '顶级代理', agent_mode: '代理模式', 'agent_mode turnover': '普通返水代理', 'agent_mode affiliate': '联营代理', @@ -18,6 +17,34 @@ export default { turnover_share_rate: '返水分红比例', affiliate_share_rate: '联营占成比例', affiliate_fee_rate: '联营成本扣除比例', + affiliate_contract_no: '联营契约编号', + affiliate_contract_name: '联营契约名称', + settle_cycle: '结算周期', + settle_cycle_placeholder: '请选择结算周期(可细化到周几/几号)', + 'settle_cycle daily': '日结', + 'settle_cycle weekly': '周结', + 'settle_cycle monthly': '月结', + settle_weekday: '结算周几', + settle_time: '结算时间', + 'weekday 1': '周一', + 'weekday 2': '周二', + 'weekday 3': '周三', + 'weekday 4': '周四', + 'weekday 5': '周五', + 'weekday 6': '周六', + 'weekday 7': '周日', + affiliate_effective_start_at: '联营生效开始', + affiliate_effective_end_at: '联营生效结束', + affiliate_ladder_rules: '联营阶梯规则', + affiliate_ladder_rules_placeholder: '请输入 JSON,例如 [{\"minLoss\":\"0.0000\",\"shareRate\":\"0.200000\"}]', + ladder_min_loss: '起始客损', + ladder_share_rate: '占成比例', + ladder_rule_required: '联营阶梯规则至少需要一条', + ladder_min_loss_invalid: '联营阶梯规则起始客损必须为大于等于0的数字', + ladder_share_rate_invalid: '联营阶梯规则占成比例必须在0到1之间', + ladder_min_loss_order_invalid: '联营阶梯规则需按起始客损递增', + settle_day_daily: '每天', + day_suffix: '号', carryover_balance: '联营负结转余额', user_count: '用户数', profit_amount: '当期利润', @@ -30,6 +57,20 @@ export default { admin_group_id: '管理角色组', admingroup__name: '组名', admin_id: '管理员', + admin_tree_tip: '按渠道分组展示可关联的管理员账号,请在「渠道名称」下选择具体管理员(叶子节点)。渠道负责人仅对应一名管理员。', + manual_settle: '手动结算', + manual_settle_confirm: '确认触发当前渠道手动结算?', + manual_settle_settlement_no: '结算单号', + manual_settle_period_start: '周期开始', + manual_settle_period_end: '周期结束(当前时间)', + manual_settle_total_bet: '总投注额(已结算注单)', + manual_settle_total_payout: '总派彩额', + manual_settle_platform_profit: '平台盈亏', + manual_settle_commission_rate: '佣金比例(小数)', + manual_settle_calc_base: '结算基数', + manual_settle_commission_amount: '佣金金额', + manual_settle_remark: '备注', + admin_id_placeholder: '请选择渠道下的管理员账号', admin__username: '用户名', create_time: '创建时间', update_time: '修改时间', diff --git a/web/src/views/backend/agent/commissionRecord/popupForm.vue b/web/src/views/backend/agent/commissionRecord/popupForm.vue index c13ef0c..9b03456 100644 --- a/web/src/views/backend/agent/commissionRecord/popupForm.vue +++ b/web/src/views/backend/agent/commissionRecord/popupForm.vue @@ -6,9 +6,45 @@
- - - + + + @@ -42,8 +78,9 @@ const { t } = useI18n() const rules: Partial> = reactive({ settlement_period_id: [{ required: true, message: t('Please input field', { field: t('agent.commissionRecord.settlement_period_id') }) }], + channel_id: [{ required: true, message: t('Please input field', { field: t('agent.commissionRecord.channel_id') }) }], + admin_id: [{ required: true, message: t('Please input field', { field: t('agent.commissionRecord.admin_id') }) }], }) - diff --git a/web/src/views/backend/channel/index.vue b/web/src/views/backend/channel/index.vue index 3116f6e..3542987 100644 --- a/web/src/views/backend/channel/index.vue +++ b/web/src/views/backend/channel/index.vue @@ -10,14 +10,61 @@
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
@@ -226,4 +571,8 @@ const rules: Partial> = reactive({ padding-left: 18px; line-height: 1.6; } + +.channel-admin-tree-tip { + margin-bottom: 12px; +}