id, $companyData ); // 3. 创建来源记录 TrafficPoolSource::createSource( $poolCompany->id, $identifier, $companyId, $sourceType, $sourceData ); Db::commit(); return [ 'poolId' => $pool->id, 'poolCompanyId' => $poolCompany->id ]; } catch (\Exception $e) { Db::rollback(); throw $e; } } /** * 好友通过时同步流量信息 * * @param string $identifier 微信ID * @param int $companyId 公司ID * @param int $wechatFriendId 微信好友表ID * @param array $friendData 好友数据 * @return TrafficPoolCompany|null */ public function syncFriendPass(string $identifier, int $companyId, int $wechatFriendId, array $friendData = []) { // 查找流量池记录 $poolCompany = TrafficPoolCompany::where('identifier', $identifier) ->where('companyId', $companyId) ->where('isDel', 0) ->find(); if (!$poolCompany) { // 如果不存在,先入池 $result = $this->enterPool( $identifier, $companyId, TrafficPoolSource::SOURCE_TYPE_FRIEND_ADD, $friendData, array_merge($friendData, [ 'wechatFriendId' => $wechatFriendId, 'friendStatus' => TrafficPoolCompany::FRIEND_STATUS_PASSED, 'friendPassTime' => time() ]) ); $poolCompany = TrafficPoolCompany::find($result['poolCompanyId']); } else { // 更新现有记录 $poolCompany->save([ 'wechatFriendId' => $wechatFriendId, 'friendStatus' => TrafficPoolCompany::FRIEND_STATUS_PASSED, 'friendPassTime' => time(), 'updateTime' => time() ]); } // 更新总表基础信息 if (!empty($friendData)) { $pool = TrafficPoolV2::find($poolCompany->poolId); if ($pool) { $pool->updateBasicInfo($friendData); } } return $poolCompany; } /** * 同步微信标签 * * @param int $poolCompanyId 公司流量ID * @param array $labels 标签数组 * @return int 同步数量 */ public function syncWechatTags(int $poolCompanyId, array $labels) { $poolCompany = TrafficPoolCompany::find($poolCompanyId); if (!$poolCompany) { return 0; } return TrafficPoolTag::syncWechatTags( $poolCompanyId, $poolCompany->identifier, $poolCompany->companyId, $labels ); } /** * 获取流量池列表(分页) * * @param int $companyId 公司ID * @param int $page 页码 * @param int $pageSize 每页数量 * @param array $filters 筛选条件 * @return array */ public function getPoolList(int $companyId, int $page = 1, int $pageSize = 10, array $filters = []) { $query = TrafficPoolCompany::alias('tpc') ->join('ck_traffic_pool tp', 'tp.id = tpc.poolId', 'LEFT') ->where('tpc.companyId', $companyId) ->where('tpc.isDel', 0); // 应用筛选条件 if (!empty($filters['keyword'])) { $keyword = $filters['keyword']; $query->where(function($q) use ($keyword) { $q->where('tp.nickname', 'like', "%{$keyword}%") ->whereOr('tp.wechatId', 'like', "%{$keyword}%") ->whereOr('tp.wechatAlias', 'like', "%{$keyword}%") ->whereOr('tp.mobile', 'like', "%{$keyword}%") ->whereOr('tpc.realName', 'like', "%{$keyword}%") ->whereOr('tpc.phone', 'like', "%{$keyword}%"); }); } if (isset($filters['friendStatus'])) { $query->where('tpc.friendStatus', $filters['friendStatus']); } if (isset($filters['level'])) { $query->where('tpc.level', $filters['level']); } if (isset($filters['lifecycle'])) { $query->where('tpc.lifecycle', $filters['lifecycle']); } if (isset($filters['allocateStatus'])) { $query->where('tpc.allocateStatus', $filters['allocateStatus']); } if (!empty($filters['ownerWechatId'])) { $query->where('tpc.ownerWechatId', $filters['ownerWechatId']); } // RFM 筛选 if (isset($filters['rfmMMin'])) { $query->where('tpc.rfmM', '>=', $filters['rfmMMin']); } if (isset($filters['rfmMMax'])) { $query->where('tpc.rfmM', '<=', $filters['rfmMMax']); } // 统计总数 $total = $query->count(); // 查询列表 $list = $query->field([ 'tpc.id', 'tpc.poolId', 'tpc.identifier', 'tpc.companyId', 'tpc.friendStatus', 'tpc.level', 'tpc.intentionLevel', 'tpc.lifecycle', 'tpc.lastInteractTime', 'tpc.rfmF', 'tpc.rfmM', 'tpc.totalMsgCount', 'tpc.totalOrderCount', 'tpc.totalOrderAmount', 'tpc.ownerWechatId', 'tpc.allocateStatus', 'tpc.realName', 'tpc.phone', 'tpc.createTime', 'tp.nickname', 'tp.avatar', 'tp.gender', 'tp.wechatId', 'tp.wechatAlias', 'tp.mobile', 'tp.region' ]) ->order('tpc.id DESC') ->page($page, $pageSize) ->select(); // 获取标签 $poolCompanyIds = array_column($list->toArray(), 'id'); $tags = $this->getTagsForPoolCompanies($poolCompanyIds); // 组装数据 $result = []; foreach ($list as $item) { $data = $item->toArray(); // 计算 RFM R 值 $data['rfmR'] = $item->lastInteractTime ? (int)floor((time() - $item->lastInteractTime) / 86400) : 9999; // 计算 RFM 总分 $data['rfmScore'] = $this->calculateRfmScore($data['rfmR'], $data['rfmF'], $data['rfmM']); // 添加标签 $data['tags'] = $tags[$item->id] ?? []; $result[] = $data; } return [ 'list' => $result, 'total' => $total, 'page' => $page, 'pageSize' => $pageSize ]; } /** * 获取流量详情 * * @param int $poolCompanyId 公司流量ID * @param int $companyId 公司ID * @return array|null */ public function getPoolDetail(int $poolCompanyId, int $companyId) { $poolCompany = TrafficPoolCompany::alias('tpc') ->join('ck_traffic_pool tp', 'tp.id = tpc.poolId', 'LEFT') ->where('tpc.id', $poolCompanyId) ->where('tpc.companyId', $companyId) ->where('tpc.isDel', 0) ->field('tpc.*, tp.nickname, tp.avatar, tp.gender, tp.wechatId, tp.wechatAlias, tp.mobile, tp.region, tp.country, tp.province, tp.city, tp.signature') ->find(); if (!$poolCompany) { return null; } $data = $poolCompany->toArray(); // 获取标签 $data['tags'] = TrafficPoolTag::getTagsByPoolCompany($poolCompanyId)->toArray(); // 获取来源历史 $data['sources'] = TrafficPoolSource::getSourcesByPoolCompany($poolCompanyId)->toArray(); // 获取行为轨迹(最近50条) $data['behaviors'] = TrafficPoolBehavior::getUserJourney($poolCompanyId, 50)->toArray(); // 获取分配历史 $data['allotRecords'] = TrafficPoolAllotRecord::getAllotHistory($poolCompanyId)->toArray(); // 计算 RFM $data['rfmR'] = $poolCompany->lastInteractTime ? (int)floor((time() - $poolCompany->lastInteractTime) / 86400) : 9999; $data['rfmScore'] = $this->calculateRfmScore($data['rfmR'], $data['rfmF'], $data['rfmM']); return $data; } /** * 更新流量信息 * * @param int $poolCompanyId 公司流量ID * @param int $companyId 公司ID * @param array $data 更新数据 * @return bool */ public function updatePool(int $poolCompanyId, int $companyId, array $data) { $poolCompany = TrafficPoolCompany::where('id', $poolCompanyId) ->where('companyId', $companyId) ->where('isDel', 0) ->find(); if (!$poolCompany) { return false; } // 允许更新的字段 $allowFields = [ 'realName', 'phone', 'email', 'birthday', 'address', 'company', 'position', 'remark', 'customFields', 'level', 'intentionLevel', 'lifecycle', 'status' ]; $updateData = array_intersect_key($data, array_flip($allowFields)); $updateData['updateTime'] = time(); return $poolCompany->save($updateData); } /** * 批量获取流量的标签 * * @param array $poolCompanyIds * @return array */ protected function getTagsForPoolCompanies(array $poolCompanyIds) { if (empty($poolCompanyIds)) { return []; } $tags = TrafficPoolTag::whereIn('poolCompanyId', $poolCompanyIds) ->where('isDel', 0) ->select(); $result = []; foreach ($tags as $tag) { if (!isset($result[$tag->poolCompanyId])) { $result[$tag->poolCompanyId] = []; } $result[$tag->poolCompanyId][] = [ 'id' => $tag->id, 'tagDefineId' => $tag->tagDefineId, 'tagName' => $tag->tagName, 'tagType' => $tag->tagType, 'tagValue' => $tag->tagValue ]; } return $result; } /** * 计算 RFM 评分 * * @param int $r 最后互动距今天数 * @param int $f 互动频次 * @param float $m 消费金额 * @return array */ public function calculateRfmScore($r, $f, $m) { // R 评分(天数越少分数越高) if ($r <= 7) { $rScore = 5; } elseif ($r <= 30) { $rScore = 4; } elseif ($r <= 90) { $rScore = 3; } elseif ($r <= 180) { $rScore = 2; } else { $rScore = 1; } // F 评分 if ($f >= 100) { $fScore = 5; } elseif ($f >= 50) { $fScore = 4; } elseif ($f >= 20) { $fScore = 3; } elseif ($f >= 5) { $fScore = 2; } else { $fScore = 1; } // M 评分 if ($m >= 10000) { $mScore = 5; } elseif ($m >= 5000) { $mScore = 4; } elseif ($m >= 1000) { $mScore = 3; } elseif ($m >= 100) { $mScore = 2; } else { $mScore = 1; } return [ 'R' => $rScore, 'F' => $fScore, 'M' => $mScore, 'total' => $rScore + $fScore + $mScore ]; } /** * 获取流量统计数据 * * @param int $companyId 公司ID * @return array */ public function getStatistics(int $companyId) { $today = strtotime('today'); $yesterday = strtotime('yesterday'); $thisWeek = strtotime('monday this week'); $thisMonth = strtotime('first day of this month'); // 总流量数 $totalCount = TrafficPoolCompany::where('companyId', $companyId) ->where('isDel', 0) ->count(); // 好友数 $friendCount = TrafficPoolCompany::where('companyId', $companyId) ->where('friendStatus', TrafficPoolCompany::FRIEND_STATUS_PASSED) ->where('isDel', 0) ->count(); // 今日新增 $todayNewCount = TrafficPoolCompany::where('companyId', $companyId) ->where('createTime', '>=', $today) ->where('isDel', 0) ->count(); // 本周新增 $weekNewCount = TrafficPoolCompany::where('companyId', $companyId) ->where('createTime', '>=', $thisWeek) ->where('isDel', 0) ->count(); // 本月新增 $monthNewCount = TrafficPoolCompany::where('companyId', $companyId) ->where('createTime', '>=', $thisMonth) ->where('isDel', 0) ->count(); // 客户等级分布 $levelDistribution = TrafficPoolCompany::where('companyId', $companyId) ->where('isDel', 0) ->group('level') ->field('level, COUNT(*) as count') ->select() ->toArray(); // 生命周期分布 $lifecycleDistribution = TrafficPoolCompany::where('companyId', $companyId) ->where('isDel', 0) ->group('lifecycle') ->field('lifecycle, COUNT(*) as count') ->select() ->toArray(); // 来源分布 $sourceDistribution = TrafficPoolSource::where('companyId', $companyId) ->where('isFirstSource', 1) ->group('sourceType') ->field('sourceType, COUNT(*) as count') ->select() ->toArray(); return [ 'totalCount' => $totalCount, 'friendCount' => $friendCount, 'todayNewCount' => $todayNewCount, 'weekNewCount' => $weekNewCount, 'monthNewCount' => $monthNewCount, 'levelDistribution' => $levelDistribution, 'lifecycleDistribution' => $lifecycleDistribution, 'sourceDistribution' => $sourceDistribution ]; } }