更新
This commit is contained in:
@@ -11,6 +11,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -32,6 +33,12 @@ class FirstVisitConversionLogic
|
||||
$scopeValue = DataScopeService::getEffectiveScope($adminInfo);
|
||||
$selectedDeptId = max(0, (int) ($params['dept_id'] ?? 0));
|
||||
$selectedAssistantId = max(0, (int) ($params['assistant_id'] ?? 0));
|
||||
$selectedMediaChannelCode = MediaChannelService::normalizeStatsCode(
|
||||
trim((string) ($params['media_channel_code'] ?? ''))
|
||||
);
|
||||
$selectedMediaChannel = $selectedMediaChannelCode !== ''
|
||||
? MediaChannelService::getChannelByCode($selectedMediaChannelCode)
|
||||
: null;
|
||||
|
||||
$deptSelectionValid = $selectedDeptId <= 0
|
||||
|| $allowedDeptSet === null
|
||||
@@ -73,7 +80,7 @@ class FirstVisitConversionLogic
|
||||
'time_type' => 'custom',
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'include_filters' => 0,
|
||||
'include_filters' => 1,
|
||||
'include_members' => 0,
|
||||
'exclude_cancelled_appointments' => 1,
|
||||
'order_metric_mode' => 'performance',
|
||||
@@ -83,6 +90,9 @@ class FirstVisitConversionLogic
|
||||
if ($selectedDeptId > 0 && $deptSelectionValid) {
|
||||
$conversionParams['dept_id'] = $selectedDeptId;
|
||||
}
|
||||
if ($selectedMediaChannelCode !== '') {
|
||||
$conversionParams['media_channel_code'] = $selectedMediaChannelCode;
|
||||
}
|
||||
|
||||
$conversion = ConversionLogic::overview(
|
||||
$conversionParams,
|
||||
@@ -103,12 +113,21 @@ class FirstVisitConversionLogic
|
||||
$startDate,
|
||||
$endDate,
|
||||
$effectiveAdminIds,
|
||||
array_fill_keys(array_keys($rowDeptIdSet), true)
|
||||
array_fill_keys(array_keys($rowDeptIdSet), true),
|
||||
self::personalYejiMediaSources($selectedMediaChannelCode, $selectedMediaChannel)
|
||||
);
|
||||
self::applyOpenCounts($rows, $openDirect);
|
||||
|
||||
$summary = is_array($conversion['summary'] ?? null) ? $conversion['summary'] : [];
|
||||
$summary['total_open_count'] = array_sum($openDirect);
|
||||
$summary['total_open_rate'] = self::percent(
|
||||
(int) $summary['total_open_count'],
|
||||
(int) ($summary['add_fans_count'] ?? 0)
|
||||
);
|
||||
$summary['open_appointment_rate'] = self::percent(
|
||||
(int) ($summary['paid_appointment_count'] ?? 0),
|
||||
(int) $summary['total_open_count']
|
||||
);
|
||||
$summary['open_receive_rate'] = self::percent(
|
||||
(int) ($summary['completed_order_count'] ?? 0),
|
||||
(int) $summary['total_open_count']
|
||||
@@ -127,6 +146,12 @@ class FirstVisitConversionLogic
|
||||
$selectedAssistantName = $selectedAssistantId > 0
|
||||
? (string) (Admin::where('id', $selectedAssistantId)->whereNull('delete_time')->value('name') ?? '')
|
||||
: '';
|
||||
$selectedMediaChannelName = $selectedMediaChannelCode !== ''
|
||||
? (string) ($selectedMediaChannel['channel_name'] ?? $selectedMediaChannelCode)
|
||||
: '';
|
||||
$conversionFilters = is_array($conversion['extend']['filters'] ?? null)
|
||||
? $conversion['extend']['filters']
|
||||
: [];
|
||||
|
||||
return [
|
||||
'meta' => [
|
||||
@@ -139,13 +164,20 @@ class FirstVisitConversionLogic
|
||||
'scope_label' => DataScopeService::scopeLabel($scopeValue),
|
||||
'selected_dept_name' => $selectedDeptName,
|
||||
'selected_assistant_name' => $selectedAssistantName,
|
||||
'open_count_source' => '个人业绩录入',
|
||||
'selected_media_channel_code' => $selectedMediaChannelCode,
|
||||
'selected_media_channel_name' => $selectedMediaChannelName,
|
||||
'open_count_source' => $selectedMediaChannelCode === ''
|
||||
? '个人业绩录入'
|
||||
: '个人业绩录入(按渠道名称匹配)',
|
||||
'appointment_rule' => '按预约日期统计,归属优先挂号医助、再回退诊单医助;仅含已预约、已完成和已过号',
|
||||
'performance_rule' => '按业务订单创建时间和创建人统计,排除取消、拒收、退款及已发生退款的订单',
|
||||
],
|
||||
'filters' => [
|
||||
'departments' => DeptLogic::getAllDataScoped($adminId, $adminInfo),
|
||||
'assistants' => self::assistantOptions($baseVisibleAdminIds, $selectedDeptIds, $selectedDeptId),
|
||||
'media_channels' => is_array($conversionFilters['media_channels'] ?? null)
|
||||
? $conversionFilters['media_channels']
|
||||
: [],
|
||||
],
|
||||
'summary' => $summary,
|
||||
'rankings' => [
|
||||
@@ -292,16 +324,30 @@ class FirstVisitConversionLogic
|
||||
}
|
||||
}
|
||||
|
||||
/** @param int[]|null $effectiveAdminIds @param array<int,true> $rowDeptSet @return array<int,int> */
|
||||
private static function loadOpenCountByDept(string $startDate, string $endDate, ?array $effectiveAdminIds, array $rowDeptSet): array
|
||||
/**
|
||||
* @param int[]|null $effectiveAdminIds
|
||||
* @param array<int,true> $rowDeptSet
|
||||
* @param string[]|null $mediaSources null=全部渠道;空数组=所选渠道没有可匹配的手工来源
|
||||
* @return array<int,int>
|
||||
*/
|
||||
private static function loadOpenCountByDept(
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
?array $effectiveAdminIds,
|
||||
array $rowDeptSet,
|
||||
?array $mediaSources = null
|
||||
): array
|
||||
{
|
||||
if ($effectiveAdminIds === [] || $rowDeptSet === []) {
|
||||
if ($effectiveAdminIds === [] || $rowDeptSet === [] || $mediaSources === []) {
|
||||
return [];
|
||||
}
|
||||
$query = PersonalYeji::whereBetween('yeji_date', [$startDate, $endDate]);
|
||||
if ($effectiveAdminIds !== null) {
|
||||
$query->whereIn('creator_id', $effectiveAdminIds);
|
||||
}
|
||||
if ($mediaSources !== null) {
|
||||
$query->whereIn('media_source', $mediaSources);
|
||||
}
|
||||
$rows = $query
|
||||
->fieldRaw('creator_id, SUM(total_open_count) AS open_count')
|
||||
->group('creator_id')
|
||||
@@ -401,6 +447,11 @@ class FirstVisitConversionLogic
|
||||
}
|
||||
$count = (int) ($direct[(int) ($row['id'] ?? 0)] ?? 0) + $childTotal;
|
||||
$row['total_open_count'] = $count;
|
||||
$row['total_open_rate'] = self::percent($count, (int) ($row['add_fans_count'] ?? 0));
|
||||
$row['open_appointment_rate'] = self::percent(
|
||||
(int) ($row['paid_appointment_count'] ?? 0),
|
||||
$count
|
||||
);
|
||||
$row['open_receive_rate'] = self::percent((int) ($row['completed_order_count'] ?? 0), $count);
|
||||
$sum += (int) ($direct[(int) ($row['id'] ?? 0)] ?? 0) + $childTotal;
|
||||
}
|
||||
@@ -409,6 +460,32 @@ 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 [];
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter(array_map(
|
||||
static fn ($value): string => trim((string) $value),
|
||||
[
|
||||
$channelCode,
|
||||
$channel['channel_name'] ?? '',
|
||||
$channel['source_tag_name'] ?? '',
|
||||
]
|
||||
), static fn (string $value): bool => $value !== '')));
|
||||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $rows @return array<int,array<string,mixed>> */
|
||||
private static function rankingRows(array $rows): array
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user