This commit is contained in:
Your Name
2026-09-28 11:32:28 +08:00
parent 49fc974421
commit 4ef3cef6a0
2 changed files with 35 additions and 54 deletions
@@ -10,7 +10,6 @@ use app\adminapi\logic\stats\ConversionLogic;
use app\adminapi\logic\stats\YejiStatsLogic;
use app\common\model\auth\Admin;
use app\common\model\auth\AdminDept;
use app\common\model\stats\PersonalYeji;
use app\common\service\DataScope\DataScopeService;
use app\common\service\qywx\MediaChannelService;
use think\facade\Db;
@@ -18,7 +17,7 @@ use think\facade\Db;
/**
* 一诊「综合数据转化」。
*
* 自动指标复用 ConversionLogic;开口数来自个人业绩录入。所有筛选先与 DataScope
* 自动指标复用 ConversionLogic;开口数来自获客客户首次发起会话事件。所有筛选先与 DataScope
* 可见管理员集合取交集,HTTP 参数不能扩大当前账号的数据范围。
*/
class FirstVisitConversionLogic
@@ -94,7 +93,7 @@ class FirstVisitConversionLogic
$endDate,
$effectiveAdminIds,
array_fill_keys(array_keys($rowDeptIdSet), true),
self::personalYejiMediaSources($selectedMediaChannelCode, $selectedMediaChannel)
$selectedMediaChannel
);
$openDirect = $openCounts['dept'];
self::applyOpenCounts($rows, $openDirect, $openCounts['admin']);
@@ -159,8 +158,8 @@ class FirstVisitConversionLogic
'selected_media_channel_code' => $selectedMediaChannelCode,
'selected_media_channel_name' => $selectedMediaChannelName,
'open_count_source' => $selectedMediaChannelCode === ''
? '个人业绩录入'
: '个人业绩录入(按渠道名称匹配)',
? '企微获客客户首次开口事件'
: '企微获客客户首次开口事件(按当前客户标签筛选渠道)',
'can_view_finance' => $canViewFinance,
'appointment_rule' => '按预约日期统计,归属优先挂号医助、再回退诊单医助;仅含已预约、已完成和已过号',
'registration_rule' => '按支付时间统计已支付且实收金额大于 0、低于 10 元的订单,每笔计 1 个挂号并按订单创建人归属',
@@ -713,7 +712,7 @@ class FirstVisitConversionLogic
/**
* @param int[]|null $effectiveAdminIds
* @param array<int,true> $rowDeptSet
* @param string[]|null $mediaSources null=全部渠道;空数组=所选渠道没有可匹配的手工来源
* @param array<string,mixed>|null $mediaChannel null=全部渠道
* @return array{dept:array<int,int>,admin:array<int,int>}
*/
private static function loadOpenCounts(
@@ -721,22 +720,42 @@ class FirstVisitConversionLogic
string $endDate,
?array $effectiveAdminIds,
array $rowDeptSet,
?array $mediaSources = null
?array $mediaChannel = null
): array
{
if ($effectiveAdminIds === [] || $rowDeptSet === [] || $mediaSources === []) {
if ($effectiveAdminIds === [] || $rowDeptSet === []) {
return ['dept' => [], 'admin' => []];
}
$query = PersonalYeji::whereBetween('yeji_date', [$startDate, $endDate]);
$startTimestamp = strtotime($startDate . ' 00:00:00');
$endTimestamp = strtotime($endDate . ' 23:59:59');
$query = Db::name('qywx_customer_acquisition_event')
->alias('open_event')
->join(
'qywx_customer_acquisition_customer acquisition_customer',
'acquisition_customer.link_id = open_event.link_id'
. ' AND acquisition_customer.external_userid = open_event.external_userid'
. ' AND acquisition_customer.userid = open_event.userid'
)
->where('open_event.change_type', 'customer_start_chat')
->where('open_event.status', 1)
->whereBetween('open_event.event_time', [$startTimestamp, $endTimestamp])
->where('acquisition_customer.chat_status', 1)
->where('acquisition_customer.owner_admin_id', '>', 0);
if ($effectiveAdminIds !== null) {
$query->whereIn('creator_id', $effectiveAdminIds);
$query->whereIn('acquisition_customer.owner_admin_id', $effectiveAdminIds);
}
if ($mediaSources !== null) {
$query->whereIn('media_source', $mediaSources);
if ($mediaChannel !== null) {
MediaChannelService::applyExternalUserChannelFilter(
$query,
'acquisition_customer.external_userid',
$mediaChannel,
'acquisition_customer.userid'
);
}
$rows = $query
->fieldRaw('creator_id, SUM(total_open_count) AS open_count')
->group('creator_id')
// 客户可从多个获客链接进入;按员工+客户去重,与加粉组合口径一致。
->fieldRaw('acquisition_customer.owner_admin_id AS creator_id, COUNT(DISTINCT acquisition_customer.external_userid) AS open_count')
->group('acquisition_customer.owner_admin_id')
->select()
->toArray();
if ($rows === []) {
@@ -871,44 +890,6 @@ class FirstVisitConversionLogic
return $sum;
}
/**
* 手工开口按 personal_yeji.media_source 保存;渠道筛选时仅匹配该渠道自身的稳定标识和名称。
* 不使用 source_group_name,避免同组多个渠道的开口数被重复计入每个渠道。
*
* @param array<string,mixed>|null $channel
* @return string[]|null
*/
private static function personalYejiMediaSources(string $channelCode, ?array $channel): ?array
{
if ($channelCode === '') {
return null;
}
if ($channel === null) {
return [];
}
$values = [
$channelCode,
$channel['channel_name'] ?? '',
$channel['source_tag_name'] ?? '',
$channel['legacy_channel_name'] ?? '',
$channel['legacy_source_tag_name'] ?? '',
];
foreach (['channel_codes', 'channel_names', 'source_tag_names'] as $listKey) {
if (!isset($channel[$listKey]) || !is_array($channel[$listKey])) {
continue;
}
foreach ($channel[$listKey] as $item) {
$values[] = $item;
}
}
return array_values(array_unique(array_filter(array_map(
static fn ($value): string => trim((string) $value),
$values
), static fn (string $value): bool => $value !== '' && !str_starts_with($value, MediaChannelService::GROUP_CODE_PREFIX))));
}
private static function canViewFinance(int $adminId, array $adminInfo): bool
{
if ((int) ($adminInfo['root'] ?? 0) === 1) {