更新
This commit is contained in:
@@ -734,6 +734,11 @@ class CustomerLogic extends BaseLogic
|
||||
'delete_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
|
||||
// 客户已删 → 关系表硬删(无 delete_time 列;统计场景不需要保留)
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->where('external_userid', $externalUserId)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -782,16 +787,23 @@ class CustomerLogic extends BaseLogic
|
||||
->where('id', (int) $row['id'])
|
||||
->update([
|
||||
'follow_users' => json_encode([], JSON_UNESCAPED_UNICODE),
|
||||
'tags' => '[]',
|
||||
'delete_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
|
||||
// 整客户已无人跟进 → 关系表清空
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->where('external_userid', $externalUserId)
|
||||
->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$minCreate = self::minFollowCreatetime($kept);
|
||||
$update = [
|
||||
'follow_users' => json_encode($kept, JSON_UNESCAPED_UNICODE),
|
||||
'tags' => self::extractFollowUserTags($kept),
|
||||
'update_time' => $now,
|
||||
];
|
||||
if ($minCreate > 0) {
|
||||
@@ -802,6 +814,9 @@ class CustomerLogic extends BaseLogic
|
||||
Db::name('qywx_external_contact')
|
||||
->where('id', (int) $row['id'])
|
||||
->update($update);
|
||||
|
||||
// 关系表按剩余 kept follow_users 同步(自动清掉离开员工那行 + 保留其他员工的标签)
|
||||
self::syncContactTagsRelation($externalUserId, $kept);
|
||||
}
|
||||
|
||||
private static function upsertOneExternalContactBundle(
|
||||
@@ -843,6 +858,7 @@ class CustomerLogic extends BaseLogic
|
||||
'corp_full_name' => $externalContact['corp_full_name'] ?? '',
|
||||
'external_profile' => json_encode($externalContact['external_profile'] ?? [], JSON_UNESCAPED_UNICODE),
|
||||
'follow_users' => json_encode($followUsers, JSON_UNESCAPED_UNICODE),
|
||||
'tags' => self::extractFollowUserTags($followUsers),
|
||||
'follow_admin_ids' => self::resolveFollowAdminIds($followUsers),
|
||||
'external_first_add_time' => self::minFollowCreatetime($followUsers),
|
||||
'create_time' => $now,
|
||||
@@ -851,11 +867,168 @@ class CustomerLogic extends BaseLogic
|
||||
];
|
||||
|
||||
self::upsertExternalContactRow($row, $syncCount, $newCount, $updateCount);
|
||||
|
||||
// 同步「客户↔员工↔标签」关系表(用于检索/统计;列表筛选/聚合不必再解析 follow_users JSON)
|
||||
self::syncContactTagsRelation((string) $row['external_userid'], $followUsers);
|
||||
|
||||
if (($syncCount % 25) === 0) {
|
||||
usleep(8000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步「客户↔员工↔标签」关系表(zyt_qywx_external_contact_tag)。
|
||||
*
|
||||
* 行粒度:(external_userid, follow_user_id, tag_id) 三元组——同一标签由不同员工打则各占一行。
|
||||
* 用于检索/统计/聚合:按标签筛客户、统计某标签客户数、多标签 AND/OR、按分组漏斗等。
|
||||
*
|
||||
* 写入策略(保留 create_time,仅在改名/分组变化时刷 tag_name/group_name/type):
|
||||
* 1. 计算目标三元组集合
|
||||
* 2. 删除"该 external_userid 下"不在新集合里的旧关系(含跟进人离开 / 标签移除)
|
||||
* 3. 对每个新三元组 INSERT ... ON DUPLICATE KEY UPDATE 刷新冗余字段
|
||||
*
|
||||
* @param array<int, mixed> $followUsers /externalcontact/get 返回的 follow_user[]
|
||||
* @internal 仅供 UPSERT / 回填命令复用
|
||||
*/
|
||||
public static function syncContactTagsRelation(string $externalUserId, array $followUsers): void
|
||||
{
|
||||
$externalUserId = trim($externalUserId);
|
||||
if ($externalUserId === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 拍平为 (follow_user_id, tag_id) → 行 的字典;保持 follow_user_id 维度区分
|
||||
$rows = [];
|
||||
foreach ($followUsers as $fu) {
|
||||
if (!is_array($fu)) {
|
||||
continue;
|
||||
}
|
||||
$followUserId = trim((string) ($fu['userid'] ?? ''));
|
||||
$tags = $fu['tags'] ?? [];
|
||||
if (!is_array($tags)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($tags as $t) {
|
||||
if (!is_array($t)) {
|
||||
continue;
|
||||
}
|
||||
$tagId = trim((string) ($t['tag_id'] ?? ''));
|
||||
if ($tagId === '') {
|
||||
continue;
|
||||
}
|
||||
$key = $followUserId . '|' . $tagId;
|
||||
$rows[$key] = [
|
||||
'follow_user_id' => mb_substr($followUserId, 0, 64),
|
||||
'tag_id' => mb_substr($tagId, 0, 64),
|
||||
'tag_name' => mb_substr((string) ($t['tag_name'] ?? ''), 0, 128),
|
||||
'group_name' => mb_substr((string) ($t['group_name'] ?? ''), 0, 128),
|
||||
'type' => isset($t['type']) ? (int) $t['type'] : 1,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$now = time();
|
||||
|
||||
// 2. 删除该客户下不在目标集合里的旧关系
|
||||
if ($rows === []) {
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->where('external_userid', $externalUserId)
|
||||
->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 用 (follow_user_id || tag_id) 拼字符串过滤;记录少时 PHP 拉出来比 SQL CONCAT 更高效
|
||||
$existing = Db::name('qywx_external_contact_tag')
|
||||
->where('external_userid', $externalUserId)
|
||||
->field(['id', 'follow_user_id', 'tag_id'])
|
||||
->select()
|
||||
->toArray();
|
||||
$deleteIds = [];
|
||||
foreach ($existing as $ex) {
|
||||
$key = (string) ($ex['follow_user_id'] ?? '') . '|' . (string) ($ex['tag_id'] ?? '');
|
||||
if (!isset($rows[$key])) {
|
||||
$deleteIds[] = (int) $ex['id'];
|
||||
}
|
||||
}
|
||||
if ($deleteIds !== []) {
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->whereIn('id', $deleteIds)
|
||||
->delete();
|
||||
}
|
||||
|
||||
// 3. UPSERT 新关系(命中唯一键时只刷冗余字段 + update_time,create_time 保持原值)
|
||||
foreach ($rows as $r) {
|
||||
try {
|
||||
Db::name('qywx_external_contact_tag')
|
||||
->duplicate(['tag_name', 'group_name', 'type', 'update_time'])
|
||||
->insert([
|
||||
'external_userid' => $externalUserId,
|
||||
'follow_user_id' => $r['follow_user_id'],
|
||||
'tag_id' => $r['tag_id'],
|
||||
'tag_name' => $r['tag_name'],
|
||||
'group_name' => $r['group_name'],
|
||||
'type' => $r['type'],
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('qywx external contact tag upsert failed: ' . $e->getMessage(), [
|
||||
'ext' => $externalUserId,
|
||||
'follow_user_id' => $r['follow_user_id'],
|
||||
'tag_id' => $r['tag_id'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把所有 follow_user[].tags 合并去重(按 tag_id),返回 JSON 字符串。
|
||||
*
|
||||
* 一个客户可能同时被多个员工跟进,每个员工各自打的 tag 在各自的 follow_user.tags 里;
|
||||
* 这里按 tag_id 唯一去重,保留 tag_name / group_name / type 完整结构,便于后台直接渲染/筛选,
|
||||
* 不必再解析 follow_users 大 JSON。
|
||||
*
|
||||
* 来源结构示例:
|
||||
* "tags":[{"group_name":"自媒体1","tag_name":"自媒体1","type":1,"tag_id":"etRr2..."}]
|
||||
*
|
||||
* @internal 仅供 UPSERT / 回填命令复用,不属于业务对外 API
|
||||
* @param array<int, mixed> $followUsers
|
||||
*/
|
||||
public static function extractFollowUserTags(array $followUsers): string
|
||||
{
|
||||
$bag = [];
|
||||
foreach ($followUsers as $fu) {
|
||||
if (!is_array($fu)) {
|
||||
continue;
|
||||
}
|
||||
$tags = $fu['tags'] ?? [];
|
||||
if (!is_array($tags)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($tags as $t) {
|
||||
if (!is_array($t)) {
|
||||
continue;
|
||||
}
|
||||
$tagId = trim((string) ($t['tag_id'] ?? ''));
|
||||
if ($tagId === '') {
|
||||
continue;
|
||||
}
|
||||
if (isset($bag[$tagId])) {
|
||||
continue;
|
||||
}
|
||||
$bag[$tagId] = [
|
||||
'group_name' => (string) ($t['group_name'] ?? ''),
|
||||
'tag_name' => (string) ($t['tag_name'] ?? ''),
|
||||
'type' => isset($t['type']) ? (int) $t['type'] : 1,
|
||||
'tag_id' => $tagId,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return json_encode(array_values($bag), JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将企微 follow_user[].userid 与后台 admin.work_wechat_userid 对齐,得到 zyt_admin.id 列表 JSON。
|
||||
*
|
||||
@@ -922,6 +1095,7 @@ class CustomerLogic extends BaseLogic
|
||||
'corp_full_name',
|
||||
'external_profile',
|
||||
'follow_users',
|
||||
'tags',
|
||||
'follow_admin_ids',
|
||||
'external_first_add_time',
|
||||
'update_time',
|
||||
@@ -948,6 +1122,90 @@ class CustomerLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 标签维度统计:用于「按标签筛客户」下拉选项 + 「按标签看分布」面板
|
||||
*
|
||||
* 数据源:zyt_qywx_external_contact_tag(关系表)+ zyt_qywx_external_contact(剔除已软删客户)
|
||||
* 输出按 group_name 分组、组内按客户数倒序,每条带 tag_id / tag_name / customer_count
|
||||
*
|
||||
* @return array{
|
||||
* total_tags: int,
|
||||
* total_relations: int,
|
||||
* total_tagged_customers: int,
|
||||
* groups: array<int, array{group_name: string, customer_count: int, tags: array<int, array{tag_id: string, tag_name: string, customer_count: int}>}>
|
||||
* }
|
||||
*/
|
||||
public static function getTagStats(): array
|
||||
{
|
||||
// 关系表里 external_userid 可能指向已被软删的客户;这里 INNER JOIN 主表过滤未删除的
|
||||
$rows = Db::name('qywx_external_contact_tag')
|
||||
->alias('ect')
|
||||
->join('qywx_external_contact ec', 'ec.external_userid = ect.external_userid', 'INNER')
|
||||
->whereNull('ec.delete_time')
|
||||
->field([
|
||||
'ect.tag_id',
|
||||
'ect.tag_name',
|
||||
'ect.group_name',
|
||||
'COUNT(DISTINCT ect.external_userid) AS customer_count',
|
||||
])
|
||||
->group('ect.tag_id, ect.tag_name, ect.group_name')
|
||||
->order('customer_count', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$groupMap = [];
|
||||
$customersUnion = [];
|
||||
foreach ($rows as $r) {
|
||||
$g = (string) ($r['group_name'] ?? '');
|
||||
if (!isset($groupMap[$g])) {
|
||||
$groupMap[$g] = [
|
||||
'group_name' => $g,
|
||||
'customer_count' => 0,
|
||||
'tags' => [],
|
||||
];
|
||||
}
|
||||
$cnt = (int) ($r['customer_count'] ?? 0);
|
||||
$groupMap[$g]['tags'][] = [
|
||||
'tag_id' => (string) ($r['tag_id'] ?? ''),
|
||||
'tag_name' => (string) ($r['tag_name'] ?? ''),
|
||||
'customer_count' => $cnt,
|
||||
];
|
||||
}
|
||||
|
||||
// 组内已经按 customer_count desc 排好序了(外面 SQL 排序结果);
|
||||
// 组本身按"组内最大 customer_count"倒序排(最热门标签所在组优先)
|
||||
foreach ($groupMap as &$g) {
|
||||
$g['customer_count'] = $g['tags'][0]['customer_count'] ?? 0;
|
||||
}
|
||||
unset($g);
|
||||
|
||||
$groups = array_values($groupMap);
|
||||
usort($groups, static function ($a, $b) {
|
||||
return $b['customer_count'] <=> $a['customer_count'];
|
||||
});
|
||||
|
||||
// 全表 distinct 客户数(带 tag 的客户)
|
||||
$taggedCustomers = (int) Db::name('qywx_external_contact_tag')
|
||||
->alias('ect')
|
||||
->join('qywx_external_contact ec', 'ec.external_userid = ect.external_userid', 'INNER')
|
||||
->whereNull('ec.delete_time')
|
||||
->group('ect.external_userid')
|
||||
->count();
|
||||
|
||||
$totalRelations = (int) Db::name('qywx_external_contact_tag')
|
||||
->alias('ect')
|
||||
->join('qywx_external_contact ec', 'ec.external_userid = ect.external_userid', 'INNER')
|
||||
->whereNull('ec.delete_time')
|
||||
->count();
|
||||
|
||||
return [
|
||||
'total_tags' => count($rows),
|
||||
'total_relations' => $totalRelations,
|
||||
'total_tagged_customers' => $taggedCustomers,
|
||||
'groups' => $groups,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取统计信息
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user