model = new AgentModel(); } /** * 查看 */ public function index() { if (!$this->request->isAjax()) { return $this->view->fetch(); } $list = $this->model->sheepFilter()->with(['user.parent_user', 'level_info', 'level_status_info', 'upgrade_level'])->paginate($this->request->param('list_rows', 10)); $this->success('分销商列表', null, $list); } /** * 详情 * * @param $id */ public function detail($id) { if (!$this->request->isAjax()) { return $this->view->fetch(); } $detail = $this->model->with(['user.parent_user', 'level_info', 'level_status_info', 'upgrade_level'])->where('user_id', $id)->find(); if (!$detail) { $this->error(__('No Results were found')); } $amounts = \addons\shopro\service\partner\PartnerConfig::push3Amounts(); $seq = intval($detail->direct_order_seq ?? 0); $next = $amounts[$seq % count($amounts)] ?? 398; $last = intval($detail->last_self_order_time ?? 0); $days = \addons\shopro\service\partner\PartnerConfig::activeDays(); $ago = $last ? intval(floor((time() - $last) / 86400)) : null; $identityInfo = \addons\shopro\service\partner\IdentityService::build($detail); $isPartner = !empty($identityInfo['is_partner']); $isAdvanced = !empty($identityInfo['is_advanced']); $detail->is_partner = $isPartner ? 1 : 0; $detail->is_advanced = $isAdvanced ? 1 : 0; $detail->next_push3_amount = number_format((float) $next, 2, '.', ''); $detail->next_push3_text = '第 ' . ($seq + 1) . ' 单将返 ' . $detail->next_push3_amount; $detail->last_self_order_text = $last ? date('Y-m-d H:i:s', $last) : '从未下单'; // 业务核心规范:进阶角色(创客V1~V6、酒道馆、区县代理、合伙人)需自购考核;基础会员及品酒师免自购考核 if (!$isAdvanced) { $detail->active_status_text = '免自购考核 (基础会员/品酒师)'; } else { $detail->active_status_text = !empty($detail->active_30d) ? '正常领取' : ('已暂停' . ($ago !== null ? '(距今 ' . $ago . ' 天)' : '')); } $cashTypes = \addons\shopro\service\partner\PartnerConfig::cashTypes(); $totalCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id) ->whereIn('reward_type', $cashTypes) ->where('status', 1) ->where('amount', '>', 0) ->sum('amount'); $push3Cash = \app\admin\model\shopro\partner\Reward::where('user_id', $id) ->where('status', 1) ->where('amount', '>', 0) ->where('reward_type', 'push3') ->sum('amount'); $partnerExclusiveCash = \app\admin\model\shopro\partner\Reward::where('user_id', $id) ->whereIn('reward_type', $cashTypes) ->where('status', 1) ->where('amount', '>', 0) ->where('reward_type', '<>', 'push3') ->sum('amount'); $alignedIncome = number_format(max(floatval($detail->total_income ?? 0), (float) $totalCash), 2, '.', ''); $detail->income_total_cash = $alignedIncome; $detail->total_income = $alignedIncome; $detail->partner_cash_income = $alignedIncome; $detail->push3_cash_income = number_format((float) $push3Cash, 2, '.', ''); $userData = $detail->user ? $detail->user->getData() : []; $userMoney = $userData['money'] ?? 0; $userCommission = $userData['commission'] ?? 0; $detail->user_money = number_format(floatval($userMoney), 2, '.', ''); $detail->user_commission = number_format(floatval($userCommission), 2, '.', ''); $detail->partner_mode = \addons\shopro\service\partner\PartnerConfig::isPartnerMode() ? 1 : 0; $rate = (new \addons\shopro\service\partner\RateService())->currentRate(); $detail->madow_value = \addons\shopro\service\partner\IdentityService::formatMoney((float) ($detail->madow ?? 0) * (float) $rate); $detail->custom_role_names = \addons\shopro\service\partner\PartnerConfig::roleNames(); $detail->role_names = $detail->custom_role_names; $detail->operational_stats = $this->getOperationalStats($detail, $id); $this->success('分销商详情', null, $detail); } /** * 构建运营官全景统计数据看板 */ private function getOperationalStats($detail, $id) { // 建立邻接表,使用广度优先遍历(BFS)安全获取全部后代节点,杜绝潜在脏数据循环引用死循环 $allUsers = \think\Db::name('user')->column('parent_user_id', 'id'); $children = []; foreach ($allUsers as $cid => $pid) { if ($pid) { $children[$pid][] = (int)$cid; } } $descIds = []; $queue = [(int)$id]; $visited = [(int)$id => true]; while (!empty($queue)) { $curr = array_shift($queue); if (!empty($children[$curr])) { foreach ($children[$curr] as $cid) { if (!isset($visited[$cid])) { $visited[$cid] = true; $descIds[] = $cid; $queue[] = $cid; } } } } $directIds = $children[(int)$id] ?? []; // 1. 团队角色拓扑 $directCount = count($directIds); $directPaidCount = empty($directIds) ? 0 : \think\Db::name('user')->whereIn('id', $directIds)->where('total_consume', '>', 0)->count(); $directMakerCount = empty($directIds) ? 0 : \think\Db::name('shopro_commission_agent')->whereIn('user_id', $directIds)->where('level', '>=', 2)->count(); $teamDescCount = count($descIds); $teamPaidCount = empty($descIds) ? 0 : \think\Db::name('user')->whereIn('id', $descIds)->where('total_consume', '>', 0)->count(); $teamMakerAgents = empty($descIds) ? [] : \think\Db::name('shopro_commission_agent')->whereIn('user_id', $descIds)->where('level', '>=', 2)->column('level'); $teamMakerCount = count($teamMakerAgents); $teamMakerDistribution = array_count_values($teamMakerAgents); ksort($teamMakerDistribution); $makerDistribution = [ 'v1' => intval($teamMakerDistribution[2] ?? 0), 'v2' => intval($teamMakerDistribution[3] ?? 0), 'v3' => intval($teamMakerDistribution[4] ?? 0), 'v4' => intval($teamMakerDistribution[5] ?? 0), 'v5' => intval($teamMakerDistribution[6] ?? 0), 'v6' => intval($teamMakerDistribution[7] ?? 0), ]; $teamVenueCount = empty($descIds) ? 0 : \think\Db::name('shopro_commission_agent')->whereIn('user_id', $descIds)->where('is_venue', 1)->count(); $teamCountyCount = empty($descIds) ? 0 : \think\Db::name('shopro_commission_agent')->whereIn('user_id', $descIds)->where('is_county', 1)->count(); $conversionRate = $teamDescCount > 0 ? round(($teamPaidCount / $teamDescCount) * 100, 1) : 0.0; // 2. 收益分成细项 $rewards = \think\Db::name('shopro_partner_reward') ->where('user_id', $id) ->where('status', 1) ->group('reward_type') ->column('sum(amount)', 'reward_type'); $push3Income = (float)($rewards['push3'] ?? 0); $makerDiffIncome = (float)($rewards['maker_diff'] ?? 0); $makerPeerIncome = (float)(($rewards['maker_peer'] ?? 0) + ($rewards['maker_overtaken'] ?? 0)); $teamSplitIncome = (float)(($rewards['team_venue'] ?? 0) + ($rewards['team_county'] ?? 0)); $sharePoolIncome = (float)(($rewards['venue_share'] ?? 0) + ($rewards['county_share'] ?? 0)); // 3. 业绩与订单分类 $selfOrderMoney = number_format((float)($detail->child_order_money_0 ?? 0), 2, '.', ''); $selfOrderCount = intval($detail->child_order_count_0 ?? 0); $directOrderMoney = number_format((float)($detail->child_order_money_1 ?? 0), 2, '.', ''); $directOrderCount = intval($detail->child_order_count_1 ?? 0); $teamOrderMoney = number_format((float)($detail->child_order_money_all ?? 0), 2, '.', ''); $teamOrderCount = intval($detail->child_order_count_all ?? 0); $indirectOrderMoney = number_format(max(0, (float)$teamOrderMoney - (float)$directOrderMoney - (float)$selfOrderMoney), 2, '.', ''); $indirectOrderCount = max(0, $teamOrderCount - $directOrderCount - $selfOrderCount); // 4. 晋升考核差距雷达 $currentLevel = intval($detail->level ?? 1); $currentLevelRow = \think\Db::name('shopro_commission_level')->where('level', $currentLevel)->find(); $currentLevelName = $currentLevelRow ? $currentLevelRow['name'] : ($currentLevel >= 2 ? '创客V' . ($currentLevel - 1) : '品酒师'); $nextLevel = $currentLevel + 1; $nextLevelRow = \think\Db::name('shopro_commission_level')->where('level', $nextLevel)->find(); if ($nextLevelRow) { $nextRules = json_decode($nextLevelRow['upgrade_rules'] ?? '[]', true) ?: []; $targetDirectUsers = intval($nextRules['child_user_count_1'] ?? 0); $targetTeamOrders = intval($nextRules['child_order_count_all'] ?? 0); // 解析直推有效品酒师/创客指标要求 $targetDirectAgents = 0; if (!empty($nextRules['child_agent_level_1']) && is_array($nextRules['child_agent_level_1'])) { foreach ($nextRules['child_agent_level_1'] as $al) { $targetDirectAgents += intval($al['count'] ?? 0); } } elseif (!empty($nextRules['child_agent_count_1'])) { $targetDirectAgents = intval($nextRules['child_agent_count_1']); } $currentDirectUsers = intval($detail->child_user_count_1 ?? $directCount); $currentDirectAgents = intval($detail->child_agent_count_1 ?? $directPaidCount); $currentTeamOrders = intval($detail->child_order_count_all ?? 0); $currentSelfMoney = (float)($detail->child_order_money_0 ?? 0); $directUserGap = max(0, $targetDirectUsers - $currentDirectUsers); $directAgentGap = max(0, $targetDirectAgents - $currentDirectAgents); $teamOrderGap = max(0, $targetTeamOrders - $currentTeamOrders); $isSelfPaid = ($selfOrderCount > 0 || $currentSelfMoney >= 1398); $directUserPercent = $targetDirectUsers > 0 ? min(100, (int)round(($currentDirectUsers / $targetDirectUsers) * 100)) : 100; $directAgentPercent = $targetDirectAgents > 0 ? min(100, (int)round(($currentDirectAgents / $targetDirectAgents) * 100)) : 100; $teamOrderPercent = $targetTeamOrders > 0 ? min(100, (int)round(($currentTeamOrders / $targetTeamOrders) * 100)) : 100; $upgradeRadar = [ 'current_level' => $currentLevel, 'current_level_name' => $currentLevelName, 'next_level' => $nextLevel, 'next_level_name' => $nextLevelRow['name'], 'target_direct_users' => $targetDirectUsers, 'current_direct_users' => $currentDirectUsers, 'direct_user_gap' => $directUserGap, 'direct_user_reached' => ($directUserGap === 0), 'direct_user_percent' => $directUserPercent, 'target_direct_agents' => $targetDirectAgents, 'current_direct_agents' => $currentDirectAgents, 'direct_agent_gap' => $directAgentGap, 'direct_agent_reached' => ($directAgentGap === 0), 'direct_agent_percent' => $directAgentPercent, 'target_team_orders' => $targetTeamOrders, 'current_team_orders' => $currentTeamOrders, 'team_order_gap' => $teamOrderGap, 'team_order_reached' => ($teamOrderGap === 0), 'team_order_percent' => $teamOrderPercent, 'self_paid_reached' => $isSelfPaid, 'all_reached' => ($directUserGap === 0 && $directAgentGap === 0 && $teamOrderGap === 0 && $isSelfPaid), 'upgrade_lock' => intval($detail->upgrade_lock ?? 0) ]; } else { $upgradeRadar = [ 'current_level' => $currentLevel, 'current_level_name' => $currentLevelName, 'next_level' => 0, 'next_level_name' => '已达最高职级', 'target_direct_users' => 0, 'current_direct_users' => intval($detail->child_user_count_1 ?? $directCount), 'direct_user_gap' => 0, 'direct_user_reached' => true, 'direct_user_percent' => 100, 'target_direct_agents' => 0, 'current_direct_agents' => intval($detail->child_agent_count_1 ?? $directPaidCount), 'direct_agent_gap' => 0, 'direct_agent_reached' => true, 'direct_agent_percent' => 100, 'target_team_orders' => 0, 'current_team_orders' => intval($detail->child_order_count_all ?? 0), 'team_order_gap' => 0, 'team_order_reached' => true, 'team_order_percent' => 100, 'self_paid_reached' => true, 'all_reached' => true, ]; } return [ 'roles' => [ 'direct_total' => $directCount, 'direct_paid' => $directPaidCount, 'direct_unpaid' => max(0, $directCount - $directPaidCount), 'direct_maker' => $directMakerCount, 'team_total' => $teamDescCount, 'team_paid' => $teamPaidCount, 'team_unpaid' => max(0, $teamDescCount - $teamPaidCount), 'team_conversion_rate' => $conversionRate, 'team_maker' => $teamMakerCount, 'team_maker_distribution' => $makerDistribution, 'team_venue' => $teamVenueCount, 'team_county' => $teamCountyCount, ], 'orders' => [ 'self_money' => $selfOrderMoney, 'self_count' => $selfOrderCount, 'direct_money' => $directOrderMoney, 'direct_count' => $directOrderCount, 'indirect_money' => $indirectOrderMoney, 'indirect_count' => $indirectOrderCount, 'team_money' => $teamOrderMoney, 'team_count' => $teamOrderCount, ], 'rewards' => [ 'push3' => number_format($push3Income, 2, '.', ''), 'maker_diff' => number_format($makerDiffIncome, 2, '.', ''), 'maker_peer' => number_format($makerPeerIncome, 2, '.', ''), 'maker_total' => number_format($makerDiffIncome + $makerPeerIncome, 2, '.', ''), 'team_split' => number_format($teamSplitIncome, 2, '.', ''), 'share_pool' => number_format($sharePoolIncome, 2, '.', ''), ], 'assets' => [ 'wallet_money' => $detail->user_money, 'total_income' => $detail->total_income, 'untransfer_commission' => $detail->user_commission, 'dmepa_madow' => number_format((float)($detail->madow ?? 0), 4, '.', ''), 'dmepa_value' => $detail->madow_value, 'voucher_total' => number_format((float)($detail->voucher_total ?? 0), 2, '.', ''), 'voucher_released' => number_format((float)($detail->voucher_released ?? 0), 2, '.', ''), 'voucher_available' => number_format(max(0, (float)($detail->voucher_released ?? 0) - (float)($detail->voucher_used ?? 0)), 2, '.', ''), ], 'upgrade_radar' => $upgradeRadar ]; } /** * 团队 * * @param $id */ public function team($id) { if (!$this->request->isAjax()) { return $this->view->fetch(); } $detail = $this->model->with(['user.parent_user', 'level_info'])->where('user_id', $id)->find(); if (!$detail) { $this->error(__('No Results were found')); } $detail->agent_team = AgentModel::hasWhere('user', function ($query) use ($detail) { return $query->where('parent_user_id', $detail->user_id); })->with(['user', 'level_info'])->select(); $this->success('分销商详情', null, $detail); } // 选择分销商 public function select() { if (!$this->request->isAjax()) { return $this->view->fetch(); } $data = $this->model->sheepFilter()->with(['user', 'level_info', 'level_status_info', 'upgrade_level']) ->paginate($this->request->param('list_rows', 10)); $this->success('选择分销商', null, $data); } /** * 编辑 * * @param $id */ public function edit($id = null) { $params = $this->request->only(['status', 'upgrade_lock', 'level_status', 'level', 'apply_info', 'is_venue', 'is_county', 'county_code']); $result = Db::transaction(function () use ($id, $params) { $row = $this->model->with(['user', 'level_info', 'level_status_info', 'upgrade_level'])->where('user_id', $id)->find(); if (!$row) { $this->error('未找到该分销商'); } foreach ($params as $field => $value) { switch ($field) { case 'status': // 修改状态 return $this->changeStatus($row, $value); break; case 'level_status': // 审核等级 return $this->changeLevelStatus($row, $value); break; case 'level': // 修改等级 return $this->changeLevel($row, $value); break; case 'is_venue': case 'is_county': case 'county_code': $result = $row->save([$field => $value]); if ($result) { LogModel::add($row->user_id, 'agent', ['type' => 'identity', 'field' => $field, 'value' => $value]); } return $result; default: return $row->save([$field => $value]); } } }); if ($result) { $this->success('更新成功', null, $result); } else { $this->error('更新失败'); } } /** * 开通 / 调整身份(弹窗一次提交) */ public function identity($id = null) { if (!$this->auth->check('shopro/commission/agent/edit')) { $this->error(__('You have no permission')); } $params = $this->request->only(['level', 'is_venue', 'is_county', 'county_code', 'upgrade_lock', 'remark']); if ($id === null || $id === '') { $this->error('请选择用户'); } $level = intval($params['level'] ?? 0); if ($level < 1 || $level > 7) { $this->error('请选择品酒师或创客等级'); } $isVenue = intval($params['is_venue'] ?? 0) ? 1 : 0; $isCounty = intval($params['is_county'] ?? 0) ? 1 : 0; $countyCode = trim((string) ($params['county_code'] ?? '')); if ($isCounty && $countyCode === '') { $this->error('开通区县代理时请选择区县'); } if (!$isCounty) { $countyCode = ''; } $upgradeLock = array_key_exists('upgrade_lock', $params) ? intval($params['upgrade_lock']) : null; $remark = trim((string) ($params['remark'] ?? '')); $result = Db::transaction(function () use ($id, $level, $isVenue, $isCounty, $countyCode, $upgradeLock, $remark) { $service = new AgentService(intval($id)); if (!$service->user) { $this->error('未找到该用户'); } if (!$service->agent) { $service->createNewAgent('user'); $service = new AgentService(intval($id)); } $row = $service->agent; if (!$row) { $this->error('未能生成分销身份'); } $before = \addons\shopro\service\partner\IdentityService::build($row); $levelModel = LevelModel::find($level); if (!$levelModel) { $this->error('未找到该等级'); } $save = [ 'level' => $level, 'is_venue' => $isVenue, 'is_county' => $isCounty, 'county_code' => $countyCode, ]; if ($upgradeLock !== null) { $save['upgrade_lock'] = $upgradeLock ? 1 : 0; } $row->save($save); $after = \addons\shopro\service\partner\IdentityService::build($row); $logRemark = $remark !== '' ? $remark : sprintf('将「%s」从 %s 调整为 %s。已入账收益不追溯。', $service->user->nickname ?: $id, $before['identity_text'] ?: '未开通', $after['identity_text'] ?: '-'); LogModel::add($row->user_id, 'agent', [ 'type' => 'identity', 'field' => 'bundle', 'value' => $after['identity_text'], 'before' => $before['identity_text'], ], null, $logRemark); LogModel::add($row->user_id, 'agent', ['type' => 'level', 'level' => $levelModel]); (new AgentService($row->user_id))->createAsyncAgentUpgrade(); return [ 'identity_text' => $after['identity_text'], 'level' => $level, 'is_venue' => $isVenue, 'is_county' => $isCounty, 'county_code' => $countyCode, ]; }); $this->success('身份已更新', null, $result); } // 修改状态 private function changeStatus($row, $value) { $result = $row->save(['status' => $value]); if ($result) { LogModel::add($row->user_id, 'agent', ['type' => 'status', 'value' => $value]); (new AgentService($row->user_id))->createAsyncAgentUpgrade(); } return $result; } // 审核等级 private function changeLevelStatus($row, $value) { if ($row->level_status == 0 && $value > 0) { $this->error('非法操作'); } if ($value == 0) { // 拒绝操作 return $row->save(['level_status' => 0]); } else { // 同意操作 if ($row->upgrade_level) { $result = $row->save(['level_status' => 0, 'level' => $row->upgrade_level->level]); if ($result) { LogModel::add($row->user_id, 'agent', ['type' => 'level', 'level' => $row->upgrade_level]); (new AgentService($row->user_id))->createAsyncAgentUpgrade(); } return $result; } } return false; } // 修改等级 private function changeLevel($row, $value) { $level = LevelModel::find($value); if ($level) { $result = $row->save(['level' => $level->level]); if ($result) { LogModel::add($row->user_id, 'agent', ['type' => 'level', 'level' => $level]); (new AgentService($row->user_id))->createAsyncAgentUpgrade(); } return $result; } else { $this->error('未找到该等级'); } } // 更换推荐人 public function changeParentUser($id) { $userAgent = new AgentService($id); if (!$userAgent->user) { $this->error('未找到该用户'); } $parentUserId = $this->request->param('parent_user_id', 0); // 更换推荐人检查 if ($parentUserId != 0) { $parentAgent = new AgentService($parentUserId); if (!$parentAgent->isAgentAvaliable()) { $this->error('选中用户暂未成为分销商,不能成为推荐人'); } if (!$this->checkChangeParentAgent($id, $parentUserId)) { $this->error('不能绑定该上级'); } LogModel::add($parentUserId, 'share', ['user' => $userAgent->user]); if ($userAgent->isAgentAvaliable()) { LogModel::add($id, 'bind', ['user' => $parentAgent->user ?? NULL]); } } $lastParentUserId = $userAgent->user->parent_user_id; $userAgent->user->parent_user_id = $parentUserId; $userAgent->user->save(); if ($lastParentUserId > 0) { $userAgent->createAsyncAgentUpgrade($lastParentUserId); } if ($parentUserId > 0) { $userAgent->createAsyncAgentUpgrade($parentUserId); } $this->success('绑定成功'); } // 递归往上找推荐人,防止出现推荐循环 private function checkChangeParentAgent($userId, $parentUserId) { if ($userId == $parentUserId) { $this->error('推荐人不能是本人'); } if ($parentUserId == 0) { return true; } $parentAgent = UserModel::find($parentUserId); if ($parentAgent) { if ($parentAgent->parent_user_id == $userId) { $this->error("已选中分销商的上级团队中已存在该用户"); } if ($parentAgent->parent_user_id == 0) { return true; } else { return $this->checkChangeParentAgent($userId, $parentAgent->parent_user_id); } } return false; } }