This commit is contained in:
mzl
2025-12-12 09:47:34 +00:00
parent 909f169aae
commit f99b2a84ca
6 changed files with 404 additions and 120 deletions
+68 -20
View File
@@ -12,11 +12,6 @@ class FbFileImportService
{ {
/** /**
* 导入指定目录下所有 .xlsx 文件 * 导入指定目录下所有 .xlsx 文件
*
* @param string $dir 绝对目录路径
* @param int $channelId 渠道ID(写入 fb_leads.channel)
* @return int 实际导入的文件数量(按文件数计,不是行数)
* @throws BusinessException
*/ */
public function importDir(string $dir, int $channelId): int public function importDir(string $dir, int $channelId): int
{ {
@@ -37,10 +32,8 @@ class FbFileImportService
continue; continue;
} }
// 这里可以再做一道保险:如果已经有同名的 .done 文件,可以认为它已经导过,直接跳过
$doneFlag = $filePath . '.done'; $doneFlag = $filePath . '.done';
if (is_file($doneFlag)) { if (is_file($doneFlag)) {
// 理论上不会出现(因为 .xlsx 已经被改名了),但防御性写法无害
continue; continue;
} }
@@ -61,11 +54,6 @@ class FbFileImportService
/** /**
* 导入单个 Excel 到 fb_leads * 导入单个 Excel 到 fb_leads
*
* @param string $filePath
* @param int $channelId 渠道ID
* @return int 导入的记录条数
* @throws BusinessException
*/ */
public function importOneFile(string $filePath, int $channelId): int public function importOneFile(string $filePath, int $channelId): int
{ {
@@ -85,8 +73,9 @@ class FbFileImportService
$headerRow = array_shift($rows); $headerRow = array_shift($rows);
$mapper = new FbLeadHeaderMapper(); $mapper = new FbLeadHeaderMapper();
// 如有未知字段,会在这里抛异常 $res = $mapper->buildColumnFieldMap($headerRow);
$colToField = $mapper->buildColumnFieldMap($headerRow); $colToField = $res['colToField'];
$unknownCols = $res['unknownCols'];
$insertData = []; $insertData = [];
$now = time(); $now = time();
@@ -103,20 +92,34 @@ class FbFileImportService
$item['is_organic'] = $this->normalizeIsOrganic($raw); $item['is_organic'] = $this->normalizeIsOrganic($raw);
break; break;
case 'fb_created_time': case 'fb_created_time':
// ✅ 方案B:写入 DATETIME 字符串
$item['fb_created_time'] = $this->normalizeCreatedTime($raw); $item['fb_created_time'] = $this->normalizeCreatedTime($raw);
break; break;
default: default:
// 小增强:字符串统一 trim(避免 " xxx ")
if (is_string($raw)) {
$raw = trim($raw);
}
$item[$field] = $raw; $item[$field] = $raw;
break; break;
} }
} }
// 未知列收集到 extra_data(宽松模式)
$extra = [];
foreach ($unknownCols as $colIndex => $rawHeader) {
$val = $row[$colIndex] ?? null;
if ($val === null || $val === '') continue;
$extra[$rawHeader] = is_string($val) ? trim($val) : $val;
}
$item['extra_data'] = $extra ? json_encode($extra, JSON_UNESCAPED_UNICODE) : null;
$item['source_file'] = $sourceFile; $item['source_file'] = $sourceFile;
$item['channel'] = $channelId; // 👈 渠道ID记录到 fb_leads $item['channel'] = $channelId;
$item['created_at'] = $now; $item['created_at'] = $now;
$item['updated_at'] = $now; $item['updated_at'] = $now;
$item['sync_status'] = 0; // 未同步到 inquiry_log $item['sync_status'] = 0;
$insertData[] = $item; $insertData[] = $item;
} }
@@ -136,7 +139,7 @@ class FbFileImportService
if (is_bool($raw)) { if (is_bool($raw)) {
return $raw ? 1 : 0; return $raw ? 1 : 0;
} }
$str = strtolower((string)$raw); $str = strtolower(trim((string)$raw));
if ($str === '') { if ($str === '') {
return 0; return 0;
} }
@@ -144,10 +147,55 @@ class FbFileImportService
} }
/** /**
* FB 创建时间目前按原始字符串存储 * ✅ 方案B:把 FB 创建时间统一转成 MySQL DATETIME(Y-m-d H:i:s)
*
* 支持:
* - "10/13/25" / "12/8/25"
* - "2025-12-08 13:22:11"
* - Excel 日期序列(数字)
*
* 解析失败:返回 null(让 DB 用默认值/允许为空)
* 注意:如果你的 fb_created_time 列是 NOT NULL 且无默认值,请把 null 改成 '1970-01-01 00:00:00'
*/ */
protected function normalizeCreatedTime($raw) protected function normalizeCreatedTime($raw): ?string
{ {
return $raw; if ($raw === null || $raw === '') {
return null;
}
// Excel 日期序列:数字
if (is_numeric($raw)) {
$n = (float)$raw;
// Excel serial date -> unix timestamp
$ts = (int)round(($n - 25569) * 86400);
if ($ts > 0) {
return date('Y-m-d H:i:s', $ts);
}
return null;
}
$s = trim((string)$raw);
if ($s === '') {
return null;
}
// 1) strtotime 能解析很多格式
$ts = strtotime($s);
if ($ts !== false) {
return date('Y-m-d H:i:s', $ts);
}
// 2) 兼容 m/d/yy(如 10/13/25)
if (preg_match('#^(\d{1,2})/(\d{1,2})/(\d{2})$#', $s, $m)) {
$mm = (int)$m[1];
$dd = (int)$m[2];
$yy = (int)$m[3];
$year = 2000 + $yy; // FB 导出通常是 20xx
$ts2 = mktime(0, 0, 0, $mm, $dd, $year);
return date('Y-m-d H:i:s', $ts2);
}
// 3) 兜底:无法解析
return null;
} }
} }
+106 -14
View File
@@ -27,33 +27,111 @@ class FbLeadSyncService
} }
$now = time(); $now = time();
$count = 0;
Db::transaction(function () use ($leads, $enterUserId, $channelId, $now, &$count) {
$insert = [];
$ids = [];
// 转数组,后面循环更快
$leadArrList = [];
foreach ($leads as $lead) { foreach ($leads as $lead) {
$leadArr = (array)$lead; $leadArrList[] = (array)$lead;
$insert[] = $this->mapLeadToInquiry($leadArr, $now, $enterUserId, $channelId);
$ids[] = $leadArr['id'];
} }
if ($insert) { // ① 收集本批手机号(去重)
Db::table('inquiry_log')->insert($insert); $phoneList = $this->collectPhonesFromLeads($leadArrList);
// ② 批量查出 inquiry_log 已存在手机号集合(索引加持)
$existingTelSet = $this->getExistingTelSet($phoneList);
$insert = [];
$idsToMarkProcessed = []; // 插入成功 or 重复跳过,统一标记已处理
$insertCount = 0;
Db::transaction(function () use (
$leadArrList, $existingTelSet, $enterUserId, $channelId, $now,
&$insert, &$idsToMarkProcessed, &$insertCount
) {
foreach ($leadArrList as $lead) {
$leadId = (int)($lead['id'] ?? 0);
$tel = trim((string)($lead['phone'] ?? ''));
// 如果手机号存在于 inquiry_log,跳过同步(但要标记 fb_leads 已处理)
if ($tel !== '' && isset($existingTelSet[$tel])) {
if ($leadId > 0) {
$idsToMarkProcessed[] = $leadId;
}
continue;
}
$insert[] = $this->mapLeadToInquiry($lead, $now, $enterUserId, $channelId);
if ($leadId > 0) {
$idsToMarkProcessed[] = $leadId;
}
}
if (!empty($insert)) {
Db::table('inquiry_log')->insert($insert);
$insertCount = count($insert);
}
if (!empty($idsToMarkProcessed)) {
Db::table('fb_leads') Db::table('fb_leads')
->whereIn('id', $ids) ->whereIn('id', $idsToMarkProcessed)
->update([ ->update([
'sync_status' => 1, 'sync_status' => 1,
'updated_at' => $now, 'updated_at' => $now,
]); ]);
$count = count($insert);
} }
}); });
return $count; return $insertCount;
}
/**
* 从本批 leads 中收集手机号(tel),去重后返回
*/
protected function collectPhonesFromLeads(array $leadArrList): array
{
$phones = [];
foreach ($leadArrList as $lead) {
$tel = trim((string)($lead['phone'] ?? ''));
if ($tel !== '') {
// 先用原值精确匹配(你后续要做归一化再改这里即可)
$phones[$tel] = true;
}
}
return array_keys($phones);
}
/**
* 批量查出 inquiry_log 已存在手机号集合(返回 set:tel => true)
* - 自动分块 whereIn,避免 SQL 过长
* - 建议给 inquiry_log.tel 加索引 idx_tel
*/
protected function getExistingTelSet(array $phoneList, int $chunkSize = 800): array
{
$phoneList = array_values(array_unique(array_filter(array_map('trim', $phoneList))));
if (empty($phoneList)) {
return [];
}
$existingTelSet = [];
foreach (array_chunk($phoneList, $chunkSize) as $chunk) {
$rows = Db::table('inquiry_log')
->select('tel')
->where('tel', '<>', '')
->whereIn('tel', $chunk)
->get();
foreach ($rows as $r) {
$t = trim((string)($r->tel ?? ''));
if ($t !== '') {
$existingTelSet[$t] = true;
}
}
}
return $existingTelSet;
} }
/** /**
@@ -124,6 +202,20 @@ class FbLeadSyncService
if (!empty($lead['seat_preference'])) { if (!empty($lead['seat_preference'])) {
$lines[] = '<p><strong>座位数偏好:</strong>' . $e($lead['seat_preference']) . '</p>'; $lines[] = '<p><strong>座位数偏好:</strong>' . $e($lead['seat_preference']) . '</p>';
} }
if (!empty($lead['drive_type'])) {
$lines[] = '<p><strong>驱动形式偏好:</strong>' . $e($lead['drive_type']) . '</p>';
}
if (!empty($lead['extra_data'])) {
$extra = json_decode((string)$lead['extra_data'], true);
if (is_array($extra) && $extra) {
$lines[] = '<hr />';
$lines[] = '<p><strong>补充问答信息:</strong></p>';
foreach ($extra as $k => $v) {
$lines[] = '<p>' . $e($k) . ':' . $e($v) . '</p>';
}
}
}
$lines[] = '<hr />'; $lines[] = '<hr />';
$lines[] = '<p><strong>广告与表单信息:</strong></p>'; $lines[] = '<p><strong>广告与表单信息:</strong></p>';
+77 -9
View File
@@ -12,17 +12,24 @@ class InquiryAssignService
/** /**
* 按渠道分配 inquiry_log 中待分配的记录,轮询业务员,并推送到 CRM 队列 * 按渠道分配 inquiry_log 中待分配的记录,轮询业务员,并推送到 CRM 队列
* *
* @param int[] $salesUserIds 参与轮询的业务员ID列表 * @param int[] $salesUserIds 默认参与轮询的业务员ID列表(该渠道默认池)
* @param string $roundRobinKey 轮询分组key(Redis记指针) * @param string $roundRobinKey 默认轮询分组key(Redis记指针)
* @param int $channelId 渠道ID(对应 inquiry_log.channel) * @param int $channelId 渠道ID(对应 inquiry_log.channel)
* @param array $assignRules 渠道内规则:按国家切换业务员池(可选)
* @param int $limit 每次最多分配多少条 * @param int $limit 每次最多分配多少条
* *
* @return int 实际分配的条数 * @return int 实际分配的条数
*/ */
public function assignBatch(array $salesUserIds, string $roundRobinKey, int $channelId, int $limit = 100): int public function assignBatch(
{ array $salesUserIds,
$userIds = array_values(array_filter(array_map('intval', $salesUserIds), fn($v) => $v > 0)); string $roundRobinKey,
if (empty($userIds)) { int $channelId,
array $assignRules = [],
int $limit = 100
): int {
// 默认池(渠道默认业务员列表)
$defaultUserIds = $this->normalizeUserIds($salesUserIds);
if (empty($defaultUserIds)) {
throw new BusinessException('分配失败:业务员列表为空'); throw new BusinessException('分配失败:业务员列表为空');
} }
@@ -43,7 +50,17 @@ class InquiryAssignService
foreach ($inquiries as $row) { foreach ($inquiries as $row) {
$inquiryId = (int)$row->id; $inquiryId = (int)$row->id;
$recipientUserId = $this->nextRecipientId($userIds, $roundRobinKey);
// ✅ 关键:按规则选业务员池(命中规则就用规则池,否则用默认池)
[$poolUserIds, $rrKey] = $this->pickSalesPoolByRules(
(int)($row->country ?? 0),
$defaultUserIds,
$roundRobinKey,
$assignRules
);
// ✅ 用“对应池子自己的 rrKey”轮询拿业务员
$recipientUserId = $this->nextRecipientId($poolUserIds, $rrKey);
Db::transaction(function () use ($inquiryId, $recipientUserId, $now, &$count) { Db::transaction(function () use ($inquiryId, $recipientUserId, $now, &$count) {
$affected = Db::table('inquiry_log') $affected = Db::table('inquiry_log')
@@ -68,7 +85,6 @@ class InquiryAssignService
'source' => 'fb_leads_inquiry', 'source' => 'fb_leads_inquiry',
]; ];
// 队列推送 —— 用你原来的写法
RedisQueueRedis::send('inquiry-to-crm-new', $res); RedisQueueRedis::send('inquiry-to-crm-new', $res);
$count++; $count++;
@@ -83,7 +99,7 @@ class InquiryAssignService
*/ */
protected function nextRecipientId(array $userIds, string $groupKey): int protected function nextRecipientId(array $userIds, string $groupKey): int
{ {
$userIds = array_values(array_filter(array_map('intval', $userIds), fn($v) => $v > 0)); $userIds = $this->normalizeUserIds($userIds);
if (empty($userIds)) { if (empty($userIds)) {
throw new BusinessException('轮询分配失败:业务员列表为空'); throw new BusinessException('轮询分配失败:业务员列表为空');
} }
@@ -96,4 +112,56 @@ class InquiryAssignService
return $userIds[$index]; return $userIds[$index];
} }
/**
* 按国家规则挑选“业务员池 + 轮询key”
* 命中 rules 则返回规则池,否则返回默认池
*
* rules 示例:
* [
* [
* 'countries' => [1,2,3],
* 'sales_user_ids' => [63,64],
* 'round_robin_key' => 'fb_xxx_ru',
* ],
* ]
*
* @return array{0:int[], 1:string} [userIds, rrKey]
*/
protected function pickSalesPoolByRules(
int $countryId,
array $defaultUserIds,
string $defaultRrKey,
array $rules
): array {
foreach ($rules as $rule) {
if (!is_array($rule)) continue;
$countries = $rule['countries'] ?? [];
if (!is_array($countries) || empty($countries)) continue;
if (!in_array($countryId, array_map('intval', $countries), true)) {
continue;
}
$userIds = $this->normalizeUserIds($rule['sales_user_ids'] ?? []);
if (!empty($userIds)) {
// 规则池必须使用自己的 rrKey,否则会和默认池“抢指针”
$rrKey = (string)($rule['round_robin_key'] ?? ($defaultRrKey . '_rule_' . md5(json_encode($countries))));
return [$userIds, $rrKey];
}
}
return [$this->normalizeUserIds($defaultUserIds), $defaultRrKey];
}
/**
* 统一清洗业务员ID数组:转 int、去重、过滤 <=0
*/
protected function normalizeUserIds(array $ids): array
{
$ids = array_values(array_filter(array_map('intval', $ids), fn($v) => $v > 0));
$ids = array_values(array_unique($ids));
return $ids;
}
} }
+91 -33
View File
@@ -8,7 +8,7 @@ class FbLeadHeaderMapper
{ {
/** /**
* Excel 原始表头 => DB 字段名 * Excel 原始表头 => DB 字段名
* 注意:key 用的是“原始表头字符串”(包括下划线) * 注意:这里的 key 会经过 normalizeHeader() 处理后再匹配
*/ */
protected array $headerMap = [ protected array $headerMap = [
// ===== 基础公共字段 ===== // ===== 基础公共字段 =====
@@ -33,37 +33,53 @@ class FbLeadHeaderMapper
'inbox_url' => 'inbox_url', 'inbox_url' => 'inbox_url',
// ===== 车型相关:全部归到 car_model ===== // ===== 车型相关:全部归到 car_model =====
'which_model_of_this_car_would_you_like_to_purchase?_' => 'car_model', 'which_model_of_this_car_would_you_like_to_purchase' => 'car_model',
'which_model_of_this_car_would_you_like_to_purchase?' => 'car_model', 'which_car_model_would_you_like_to_purchase' => 'car_model',
'which_car_model_would_you_like_to_purchase?' => 'car_model', 'which_model_do_you_want_to_purchase' => 'car_model',
'which_model_do_you_want_to_purchase?' => 'car_model', 'which_model_would_you_like_to_purchase' => 'car_model',
'which_model_would_you_like_to_purchase?' => 'car_model', 'which_version_do_you_want_to_buy' => 'car_model', // 这里也归到车型/版本字段
'which_version_do_you_want_to_buy?' => 'car_model', // 这里也归到车型/版本字段
// ===== 数量相关:全部归到 car_quantity ===== // ===== 数量相关:建议归到 car_quantity_text(避免 1-2/3-10/10+)=====
'how_many_cars_would_you_like_to_buy?_' => 'car_quantity', // 你之前 car_quantity 用 int 时会截断,这里建议后续统一改成 car_quantity_text
'how_many_cars_would_you_like_to_buy?' => 'car_quantity', 'how_many_cars_would_you_like_to_buy' => 'car_quantity_text',
'_how_many_cars_would_you_like_to_buy?' => 'car_quantity', '_how_many_cars_would_you_like_to_buy' => 'car_quantity_text',
'how_many_cars_do_you_buy?' => 'car_quantity', 'how_many_cars_do_you_buy' => 'car_quantity_text',
'how_many_cars_do_you_want_to_purchase?' => 'car_quantity', 'how_many_cars_do_you_want_to_purchase' => 'car_quantity_text',
'how_many_cars_do_you_want_to_buy?' => 'car_quantity', 'how_many_cars_do_you_want_to_buy' => 'car_quantity_text',
'what_quantity_do_you_want_to_buy?' => 'car_quantity', 'what_quantity_do_you_want_to_buy' => 'car_quantity_text',
'what_quantity_do_you_wantv_to_buy' => 'car_quantity_text', // wantv 拼写兼容
// ===== 颜色相关:归到 favorite_color ===== // ===== 颜色相关:归到 favorite_color =====
'what_color_do_you_like?' => 'favorite_color', 'what_color_do_you_like' => 'favorite_color',
'which_color_would_you_like_to_purchase?' => 'favorite_color', 'which_color_would_you_like_to_purchase' => 'favorite_color',
'what_color_car_do_you_plan_to_buy' => 'favorite_color',
// ===== 用途相关:归到 usage_type ===== // ===== 用途相关:归到 usage_type =====
'are_you_using_it_for_business_or_personal_use?' => 'usage_type', 'are_you_using_it_for_business_or_personal_use' => 'usage_type',
'business_use_or_personal_use?' => 'usage_type', 'business_use_or_personal_use' => 'usage_type',
'personal_use_or_business_use?' => 'usage_type', 'personal_use_or_business_use' => 'usage_type',
// ===== 座位数相关 ===== // ===== 座位数相关 =====
'do_you_want_to_buy_six_seats_or_seven_seats?' => 'seat_preference', 'do_you_want_to_buy_six_seats_or_seven_seats' => 'seat_preference',
// ===== AVATR 11 =====
'which_model_of_the_avatr_11_do_you_want_to_purchase' => 'car_model',
'do_you_want_to_buy_a_four-wheel_drive_or_rear-wheel_drive_car' => 'drive_type',
]; ];
/** /**
* 返回所有支持的 Excel 表头 * 宽松模式下仍然强制要求存在的“核心 DB 字段”
* 你说的:联系人、电话、邮箱、国家、渠道
* - country / channel 不是从 Excel 映射,而是你代码里根据电话识别/配置注入,所以这里不用要求
*/
protected array $requiredDbFields = [
'full_name',
'phone',
'email',
];
/**
* 返回所有支持的 Excel 表头(标准化后的 key)
*/ */
public function getSupportedHeaders(): array public function getSupportedHeaders(): array
{ {
@@ -76,12 +92,11 @@ class FbLeadHeaderMapper
public function mapHeader(string $rawHeader): ?string public function mapHeader(string $rawHeader): ?string
{ {
$key = $this->normalizeHeader($rawHeader); $key = $this->normalizeHeader($rawHeader);
return $this->headerMap[$key] ?? null; return $this->headerMap[$key] ?? null;
} }
/** /**
* 校验一整行表头: * 旧的严格校验:保留(有时你可能还想用来排查某个表单的字段)
* - 有任何不认识的表头,就抛 BusinessException * - 有任何不认识的表头,就抛 BusinessException
*/ */
public function validateHeaders(array $rawHeaders): void public function validateHeaders(array $rawHeaders): void
@@ -110,36 +125,68 @@ class FbLeadHeaderMapper
} }
/** /**
* 根据第一行表头,构建 “列索引 => DB 字段” 映射 * ✅ 宽松模式:根据第一行表头,构建映射
*
* 返回结构:
* [
* 'colToField' => [ 'A' => 'email', 'B' => 'phone', ... ],
* 'unknownCols' => [ 'C' => 'some_question?', ... ], // 未映射字段(后续可进富文本/extra_data)
* ]
*/ */
public function buildColumnFieldMap(array $rawHeaders): array public function buildColumnFieldMap(array $rawHeaders): array
{ {
// 先做一遍校验(发现问题直接抛异常)
$this->validateHeaders($rawHeaders);
$colToField = []; $colToField = [];
$unknownCols = [];
foreach ($rawHeaders as $index => $header) { foreach ($rawHeaders as $index => $header) {
$key = $this->normalizeHeader((string)$header); $raw = (string)$header;
$key = $this->normalizeHeader($raw);
if ($key === '') { if ($key === '') {
continue; continue;
} }
$field = $this->headerMap[$key] ?? null; $field = $this->headerMap[$key] ?? null;
if ($field === null) { if ($field === null) {
continue; // 理论上不会到这儿,双保险 // 宽松:记录未知列(保留原始表头用于展示)
$unknownCols[$index] = trim($raw) !== '' ? trim($raw) : $key;
continue;
} }
$colToField[$index] = $field; $colToField[$index] = $field;
} }
return $colToField; // 核心字段校验:缺少就终止(保证你说的“联系人/电话/邮箱没问题”)
$this->assertRequiredFields($colToField);
return [
'colToField' => $colToField,
'unknownCols' => $unknownCols,
];
}
/**
* 核心字段校验:要求必须能映射出 requiredDbFields
*/
protected function assertRequiredFields(array $colToField): void
{
$fields = array_values($colToField);
foreach ($this->requiredDbFields as $req) {
if (!in_array($req, $fields, true)) {
throw new BusinessException("Excel 表头缺少必要字段映射:{$req}(请补充映射或别名)");
}
}
} }
/** /**
* 表头标准化: * 表头标准化:
* 目前只去 BOM + trim,保持和 headerMap 中 key 完全一致 * - 去 BOM
* 如果后面再出现“有空格/大小写”的版本,我们可以在这里做 strtolower / 替换空格 为 _ * - trim
* - ✅ 统一处理尾部多余的 ? / _(解决 xxx?_ 与 xxx?)
*
* 注意:为了避免误伤中文表头,这里默认不做 strtolower / 空格替换。
* 若你后续想增强,可在注释处开启。
*/ */
protected function normalizeHeader(string $header): string protected function normalizeHeader(string $header): string
{ {
@@ -149,6 +196,17 @@ class FbLeadHeaderMapper
// 去掉前后空白 // 去掉前后空白
$header = trim($header); $header = trim($header);
if ($header === '') {
return '';
}
// 可选增强(默认不开启,避免影响中文):
// $header = mb_strtolower($header);
// $header = preg_replace('/\s+/', '_', $header);
// ✅ 去掉末尾连续的 "_" / "?"
$header = rtrim($header, "_?");
return $header; return $header;
} }
} }
+8 -1
View File
@@ -77,7 +77,14 @@ class InquiryAutoProcess
$synced = $syncService->syncToInquiryLog($enterUserId, $channelId); $synced = $syncService->syncToInquiryLog($enterUserId, $channelId);
// 3) 只分配该渠道的 inquiry_log(channel 过滤) // 3) 只分配该渠道的 inquiry_log(channel 过滤)
$assigned = $assignService->assignBatch($salesUserIds, $roundRobinKey, $channelId, 100); // $assigned = $assignService->assignBatch($salesUserIds, $roundRobinKey, $channelId, 100);
$assigned = $assignService->assignBatch(
$salesUserIds,
$roundRobinKey,
$channelId,
$cfg['assign_rules'] ?? [],
100
);
Log::info("InquiryAutoProcess[{$channelKey}] files={$filesImported}, synced={$synced}, assigned={$assigned}"); Log::info("InquiryAutoProcess[{$channelKey}] files={$filesImported}, synced={$synced}, assigned={$assigned}");
} catch (\Throwable $e) { } catch (\Throwable $e) {
+49 -38
View File
@@ -1,7 +1,7 @@
<?php <?php
return [ return [
// Facebook 主渠道示例 // 派卡社媒
'fb_paika_shemei' => [ 'fb_paika_shemei' => [
// 监听目录(相对 public_path) // 监听目录(相对 public_path)
'watch_dir' => 'excel/fb_paika_shemei', 'watch_dir' => 'excel/fb_paika_shemei',
@@ -12,47 +12,58 @@ return [
// 参与轮询分配的业务员 ID 列表 // 参与轮询分配的业务员 ID 列表
'sales_user_ids' => [ 'sales_user_ids' => [
// 63, 63,
// 64, 67,
// 67, 93,
// 93, 108,
// 108, 132,
// 132, 143,
// 143, 145,
// 145, 157,
// 157, 160,
// 160, 168,
// 168, 172,
// 172, 173,
// 173, 191,
// 191, 193,
// 193, 194,
// 194, 195,
// 195, 196,
// 196, 197,
// 197, 200
1, 74, 78
], ],
// 轮询用的分组 key(Redis 记指针用) // 轮询用的分组 key(Redis 记指针用)
'round_robin_key'=> 'fb_paika_shemei_inquiry', 'round_robin_key'=> 'fb_paika_shemei_inquiry',
// ✅ 渠道内分配规则(按顺序匹配,先命中先用)
'assign_rules' => [
[
'name' => '加纳池',
'countries' => [82],
'sales_user_ids' => [93],
'round_robin_key' => 'fb_paika_shemei_gh',
]
],
], ],
// 将来可以继续加其他渠道,如: // 合塑社媒
// 'tiktok' => [ 'fb_hesu_shemei' => [
// // 监听的目录(相对 public_path) 'watch_dir' => 'excel/fb_hesu_shemei',
// 'watch_dir' => 'excel/paika_shemei/11', 'enter_user_id' => 186,
'channel_id' => 6,
// // 清洗到 inquiry_log 时使用 'sales_user_ids' => [
// 'enter_user_id' => 186, 22, 23, 34, 44, 49, 71, 72, 73, 96, 104, 125, 135, 155
// 'channel' => 10, ],
'round_robin_key'=> 'fb_hesu_shemei_inquiry',
// // 参与轮询分配的业务员 ID 列表 // ✅ 渠道内分配规则(按顺序匹配,先命中先用)
// 'sales_user_ids' => [ 'assign_rules' => [
// 1, 74, 78 [
// ], 'name' => '加纳池',
'countries' => [82],
// // 轮询用的分组 key(Redis 记指针用) 'sales_user_ids' => [44],
// 'round_robin_key'=> 'fb_main_inquiry', 'round_robin_key' => 'fb_hesu_shemei_gh',
// ], ]
],
],
]; ];