更新
This commit is contained in:
@@ -144,7 +144,7 @@ class FirstVisitConversionLogic
|
||||
}
|
||||
unset($row);
|
||||
}
|
||||
return [
|
||||
return self::withDeletedFansVisibility([
|
||||
'meta' => [
|
||||
'time_type' => $timeType,
|
||||
'time_label' => $timeLabel,
|
||||
@@ -178,7 +178,7 @@ class FirstVisitConversionLogic
|
||||
],
|
||||
'rows' => $rows,
|
||||
'target' => $target,
|
||||
];
|
||||
], $adminInfo);
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
@@ -190,14 +190,14 @@ class FirstVisitConversionLogic
|
||||
);
|
||||
$pageNo = max(1, (int) ($params['page_no'] ?? 1));
|
||||
$pageSize = max(1, min(100, (int) ($params['page_size'] ?? 20)));
|
||||
$empty = [
|
||||
$empty = self::withDeletedFansVisibility([
|
||||
'lists' => [],
|
||||
'count' => 0,
|
||||
'page_no' => $pageNo,
|
||||
'page_size' => $pageSize,
|
||||
'date_range' => [$context['start_date'], $context['end_date']],
|
||||
'entity' => null,
|
||||
];
|
||||
], $adminInfo, true);
|
||||
|
||||
$entityType = strtolower(trim((string) ($params['entity_type'] ?? '')));
|
||||
if (!in_array($entityType, ['dept', 'member'], true)) {
|
||||
@@ -253,9 +253,54 @@ class FirstVisitConversionLogic
|
||||
];
|
||||
unset($result['deleted_count']);
|
||||
|
||||
return self::withDeletedFansVisibility($result, $adminInfo, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除客户统计是账号专属能力,与root、角色、财务权限和DataScope无关。
|
||||
* adminInfo由认证token缓存提供;缺失账号时拒绝,不能从HTTP参数补齐或标准化账号。
|
||||
*/
|
||||
private static function canViewDeletedFans(array $adminInfo): bool
|
||||
{
|
||||
return ($adminInfo['account'] ?? null) === 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* 只裁剪本页响应,不改变通用统计口径、加粉客户集合、排序或分页。
|
||||
* 对整个响应递归处理,避免嵌套成员、排名或未来新增位置泄露同一敏感指标。
|
||||
*/
|
||||
private static function withDeletedFansVisibility(array $result, array $adminInfo, bool $detail = false): array
|
||||
{
|
||||
$canView = self::canViewDeletedFans($adminInfo);
|
||||
if (!$canView) {
|
||||
$fields = $detail
|
||||
? ['deleted_fans_count', 'deleted_count', 'is_deleted', 'delete_time']
|
||||
: ['deleted_fans_count', 'deleted_count'];
|
||||
$result = self::removeDeletedFansFields($result, $fields);
|
||||
}
|
||||
if ($detail) {
|
||||
$result['can_view_deleted_fans'] = $canView;
|
||||
} else {
|
||||
$result['meta']['can_view_deleted_fans'] = $canView;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** @param string[] $fields */
|
||||
private static function removeDeletedFansFields(array $value, array $fields): array
|
||||
{
|
||||
foreach ($fields as $field) {
|
||||
unset($value[$field]);
|
||||
}
|
||||
foreach ($value as &$item) {
|
||||
if (is_array($item)) {
|
||||
$item = self::removeDeletedFansFields($item, $fields);
|
||||
}
|
||||
}
|
||||
unset($item);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve only the clicked entity and its authorized target range. This is
|
||||
* deliberately structural: it avoids recomputing all overview metrics,
|
||||
|
||||
@@ -9,6 +9,9 @@ use app\common\service\DataScope\DataScopeService;
|
||||
use app\common\service\qywx\QywxCustomerAcquisitionApiService;
|
||||
use app\common\service\qywx\QywxCustomerAcquisitionLinkService;
|
||||
use app\common\service\qywx\QywxPromotionMemberRange;
|
||||
use app\common\service\qywx\QywxPromotionConfig;
|
||||
use app\common\service\qywx\QywxPromotionContactApiService;
|
||||
use app\common\service\qywx\QywxPromotionMediaService;
|
||||
use app\common\service\qywx\QywxPromotionMemberSchedulerService;
|
||||
use app\common\service\qywx\QywxPromotionRangeSyncService;
|
||||
use app\common\service\qywx\QywxPromotionWidgetService;
|
||||
@@ -49,6 +52,7 @@ class WecomPromotionLogic
|
||||
|
||||
$domain = self::publicDomain($domain);
|
||||
foreach ($pools as &$pool) {
|
||||
$pool['automation_config'] = QywxPromotionConfig::forPool((int) $pool['id']);
|
||||
$pool['widget_config'] = QywxPromotionWidgetService::decode($pool['widget_config_json'] ?? null);
|
||||
unset($pool['widget_config_json']);
|
||||
$key = (string) $pool['public_key'];
|
||||
@@ -133,7 +137,11 @@ class WecomPromotionLogic
|
||||
$memberRules = $memberRulesByPool[(int) $pool['id']] ?? [];
|
||||
$sync = $syncByPool[(int) $pool['id']] ?? [];
|
||||
$remoteUserIds = array_fill_keys((array) ($officialLink['range_userids'] ?? []), true);
|
||||
$reception = QywxPromotionMemberRange::evaluate($memberRules, $today, time(), $pool['automation_config']);
|
||||
$availableUserIds = array_fill_keys($reception['userids'], true);
|
||||
foreach ($memberRules as &$memberRule) {
|
||||
$memberRule['is_backup'] = in_array((int) ($memberRule['admin_id'] ?? 0), $pool['automation_config']['backup_member_admin_ids'], true);
|
||||
$memberRule['reception_available'] = isset($availableUserIds[(string) ($memberRule['userid'] ?? '')]);
|
||||
$memberRule['is_current'] = false;
|
||||
$memberRule['is_applied'] = false;
|
||||
$memberRule['is_in_remote_range'] = isset($remoteUserIds[(string) ($memberRule['userid'] ?? '')]);
|
||||
@@ -157,6 +165,7 @@ class WecomPromotionLogic
|
||||
$pool['official_link_count'] = count($officialLinks);
|
||||
$pool['legacy_link_count'] = $legacyCount;
|
||||
$pool['member_admin_ids'] = array_values(array_unique($memberAdminIds));
|
||||
$pool['member_admin_ids'] = array_values(array_diff($pool['member_admin_ids'], $pool['automation_config']['backup_member_admin_ids']));
|
||||
$pool['member_rules'] = $memberRules;
|
||||
$pool['operators'] = $operatorsByPool[(int) $pool['id']] ?? [];
|
||||
$pool['operator_admin_ids'] = array_values(array_map(
|
||||
@@ -168,6 +177,7 @@ class WecomPromotionLogic
|
||||
$pool['can_manage_access'] = self::ownerInScope((int) ($pool['owner_admin_id'] ?? 0), $visibleIds);
|
||||
$pool['can_delete'] = $pool['can_manage_access'];
|
||||
$pool['dispatch_sync'] = $sync;
|
||||
$pool['using_backup'] = $reception['using_backup'];
|
||||
$pool['skip_verify'] = (int) ($officialLink['skip_verify'] ?? 0);
|
||||
$pool['migration_state'] = count($officialLinks) > 1
|
||||
? 'needs_resolution'
|
||||
@@ -201,6 +211,7 @@ class WecomPromotionLogic
|
||||
'member_options' => $memberOptions,
|
||||
'operator_options' => $operatorOptions,
|
||||
'department_options' => DeptLogic::getAllDataScoped($adminId, $adminInfo),
|
||||
'automation_installed' => QywxPromotionConfig::installed(),
|
||||
'customer_acquisition_link_example' => QywxCustomerAcquisitionLinkService::example(),
|
||||
];
|
||||
}
|
||||
@@ -220,9 +231,49 @@ class WecomPromotionLogic
|
||||
if (!QywxCustomerAcquisitionLinkService::isAllowed($fallback, true)) {
|
||||
throw new RuntimeException('兜底链接必须是企业微信获客助手生成的 HTTPS 链接');
|
||||
}
|
||||
$members = self::resolveMembers((array) ($params['member_admin_ids'] ?? []), $adminId, $adminInfo, $id);
|
||||
$automation = null;
|
||||
if (array_key_exists('automation_config', $params)) {
|
||||
QywxPromotionConfig::assertInstalled();
|
||||
if (!is_array($params['automation_config'])) {
|
||||
throw new RuntimeException('获客配置格式不正确');
|
||||
}
|
||||
$automation = QywxPromotionConfig::normalize($params['automation_config']);
|
||||
$automation = (new QywxPromotionMediaService())->validateConfig(
|
||||
$automation, $adminId, $id > 0 ? QywxPromotionConfig::forPool($id) : []
|
||||
);
|
||||
if ($automation['tags_enabled']) {
|
||||
$knownTags = [];
|
||||
foreach ((new QywxPromotionContactApiService())->tagOptions()['tag_groups'] as $group) {
|
||||
foreach ($group['tag'] as $tag) {
|
||||
$knownTags[] = (string) $tag['id'];
|
||||
}
|
||||
}
|
||||
if (array_diff($automation['tag_ids'], $knownTags) !== []) {
|
||||
throw new RuntimeException('所选企业微信标签已删除或不在应用可用范围,请刷新标签后重新选择');
|
||||
}
|
||||
}
|
||||
} elseif ($id > 0 && QywxPromotionConfig::installed()) {
|
||||
$automation = QywxPromotionConfig::forPool($id);
|
||||
}
|
||||
$primaryIds = self::normalizePositiveIds((array) ($params['member_admin_ids'] ?? []));
|
||||
$backupIds = $automation['backup_member_admin_ids'] ?? [];
|
||||
if ($primaryIds === [] || array_intersect($primaryIds, $backupIds) !== []) {
|
||||
throw new RuntimeException('请选择接待成员,且备用员工不能与接待成员重复');
|
||||
}
|
||||
$members = self::resolveMembers(array_merge($primaryIds, $backupIds), $adminId, $adminInfo, $id);
|
||||
if ($automation !== null) {
|
||||
$userIdByAdmin = array_column($members, 'userid', 'id');
|
||||
$automation['backup_userids'] = array_values(array_map(static fn ($aid) => $userIdByAdmin[$aid], $backupIds));
|
||||
foreach ($automation['reception_schedule'] as &$slot) {
|
||||
if (array_diff($slot['member_admin_ids'], $primaryIds) !== []) {
|
||||
throw new RuntimeException('接待时段只能选择方案内的接待成员');
|
||||
}
|
||||
$slot['member_userids'] = array_values(array_map(static fn ($aid) => $userIdByAdmin[$aid], $slot['member_admin_ids']));
|
||||
}
|
||||
unset($slot);
|
||||
}
|
||||
$userIds = array_values(array_column($members, 'userid'));
|
||||
$eligibleUserIds = self::eligibleSelectedUserIds($id, $members);
|
||||
$eligibleUserIds = self::eligibleSelectedUserIds($id, $members, $automation ?? []);
|
||||
$skipVerify = (int) ($params['skip_verify'] ?? 0) === 1 ? 1 : 0;
|
||||
$status = (int) ($params['status'] ?? 1) === 1 ? 1 : 0;
|
||||
$now = time();
|
||||
@@ -288,6 +339,7 @@ class WecomPromotionLogic
|
||||
$status,
|
||||
$existingPool,
|
||||
$members,
|
||||
$automation,
|
||||
$skipVerify,
|
||||
$createdRemote,
|
||||
$now,
|
||||
@@ -333,6 +385,9 @@ class WecomPromotionLogic
|
||||
]);
|
||||
}
|
||||
self::persistPoolMembers($id, $members, $now);
|
||||
if ($automation !== null) {
|
||||
QywxPromotionConfig::save($id, $automation);
|
||||
}
|
||||
if ($createdRemote) {
|
||||
QywxPromotionMemberSchedulerService::initialisePool($id, $linkId);
|
||||
}
|
||||
@@ -1061,7 +1116,7 @@ class WecomPromotionLogic
|
||||
}
|
||||
|
||||
/** @param list<array{id:int,userid:string}> $members @return list<string> */
|
||||
private static function eligibleSelectedUserIds(int $poolId, array $members): array
|
||||
private static function eligibleSelectedUserIds(int $poolId, array $members, array $config = []): array
|
||||
{
|
||||
if ($members === []) {
|
||||
throw new RuntimeException('请至少选择一名获客成员');
|
||||
@@ -1089,7 +1144,7 @@ class WecomPromotionLogic
|
||||
$rule['userid'] = $userId;
|
||||
$candidates[] = $rule;
|
||||
}
|
||||
$range = QywxPromotionMemberRange::evaluate($candidates, $today, $now);
|
||||
$range = QywxPromotionMemberRange::evaluate($candidates, $today, $now, $config);
|
||||
if ($range['userids'] === []) {
|
||||
throw new RuntimeException('至少需要一名已启用、已生效且未达到今日上限的获客医助');
|
||||
}
|
||||
@@ -1110,6 +1165,7 @@ class WecomPromotionLogic
|
||||
Db::name('qywx_promotion_pool_member')->where('id', (int) $existing['id'])->update([
|
||||
'admin_id' => (int) $member['id'],
|
||||
'userid' => $userId,
|
||||
'enabled' => $existing['delete_time'] !== null ? 1 : (int) $existing['enabled'],
|
||||
'delete_time' => null,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user