新增功能
This commit is contained in:
@@ -81,7 +81,7 @@ class FirstVisitConversionLogic
|
||||
'start_date' => $startDate,
|
||||
'end_date' => $endDate,
|
||||
'include_filters' => 1,
|
||||
'include_members' => 0,
|
||||
'include_members' => 1,
|
||||
'exclude_cancelled_appointments' => 1,
|
||||
'order_metric_mode' => 'performance',
|
||||
'page_no' => 1,
|
||||
@@ -109,14 +109,15 @@ class FirstVisitConversionLogic
|
||||
|
||||
$rowDeptIdSet = [];
|
||||
self::collectRowDeptIds($rows, $rowDeptIdSet);
|
||||
$openDirect = self::loadOpenCountByDept(
|
||||
$openCounts = self::loadOpenCounts(
|
||||
$startDate,
|
||||
$endDate,
|
||||
$effectiveAdminIds,
|
||||
array_fill_keys(array_keys($rowDeptIdSet), true),
|
||||
self::personalYejiMediaSources($selectedMediaChannelCode, $selectedMediaChannel)
|
||||
);
|
||||
self::applyOpenCounts($rows, $openDirect);
|
||||
$openDirect = $openCounts['dept'];
|
||||
self::applyOpenCounts($rows, $openDirect, $openCounts['admin']);
|
||||
|
||||
$summary = is_array($conversion['summary'] ?? null) ? $conversion['summary'] : [];
|
||||
$summary['total_open_count'] = array_sum($openDirect);
|
||||
@@ -170,6 +171,7 @@ class FirstVisitConversionLogic
|
||||
? '个人业绩录入'
|
||||
: '个人业绩录入(按渠道名称匹配)',
|
||||
'appointment_rule' => '按预约日期统计,归属优先挂号医助、再回退诊单医助;仅含已预约、已完成和已过号',
|
||||
'registration_rule' => '按支付时间统计已支付且实收金额大于 0、低于 10 元的订单,每笔计 1 个挂号并按订单创建人归属',
|
||||
'performance_rule' => '按业务订单创建时间和创建人统计,排除取消、拒收、退款及已发生退款的订单',
|
||||
],
|
||||
'filters' => [
|
||||
@@ -294,6 +296,10 @@ class FirstVisitConversionLogic
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
if (in_array((string) ($row['type'] ?? ''), ['member', 'unbound'], true)) {
|
||||
$out[] = $row;
|
||||
continue;
|
||||
}
|
||||
$children = self::filterDeptRows(is_array($row['children'] ?? null) ? $row['children'] : [], $allowedSet);
|
||||
$id = (int) ($row['id'] ?? 0);
|
||||
if (isset($allowedSet[$id])) {
|
||||
@@ -328,9 +334,9 @@ class FirstVisitConversionLogic
|
||||
* @param int[]|null $effectiveAdminIds
|
||||
* @param array<int,true> $rowDeptSet
|
||||
* @param string[]|null $mediaSources null=全部渠道;空数组=所选渠道没有可匹配的手工来源
|
||||
* @return array<int,int>
|
||||
* @return array{dept:array<int,int>,admin:array<int,int>}
|
||||
*/
|
||||
private static function loadOpenCountByDept(
|
||||
private static function loadOpenCounts(
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
?array $effectiveAdminIds,
|
||||
@@ -339,7 +345,7 @@ class FirstVisitConversionLogic
|
||||
): array
|
||||
{
|
||||
if ($effectiveAdminIds === [] || $rowDeptSet === [] || $mediaSources === []) {
|
||||
return [];
|
||||
return ['dept' => [], 'admin' => []];
|
||||
}
|
||||
$query = PersonalYeji::whereBetween('yeji_date', [$startDate, $endDate]);
|
||||
if ($effectiveAdminIds !== null) {
|
||||
@@ -354,7 +360,7 @@ class FirstVisitConversionLogic
|
||||
->select()
|
||||
->toArray();
|
||||
if ($rows === []) {
|
||||
return [];
|
||||
return ['dept' => [], 'admin' => []];
|
||||
}
|
||||
|
||||
$creatorIds = self::normalizeIds(array_column($rows, 'creator_id'));
|
||||
@@ -415,6 +421,7 @@ class FirstVisitConversionLogic
|
||||
unset($deptIds);
|
||||
|
||||
$direct = [];
|
||||
$adminDirect = [];
|
||||
foreach ($rows as $row) {
|
||||
$adminId = (int) ($row['creator_id'] ?? 0);
|
||||
$targetDeptId = 0;
|
||||
@@ -428,24 +435,48 @@ class FirstVisitConversionLogic
|
||||
$targetDeptId = -2;
|
||||
}
|
||||
if ($targetDeptId !== 0) {
|
||||
$direct[$targetDeptId] = ($direct[$targetDeptId] ?? 0) + (int) ($row['open_count'] ?? 0);
|
||||
$openCount = (int) ($row['open_count'] ?? 0);
|
||||
$direct[$targetDeptId] = ($direct[$targetDeptId] ?? 0) + $openCount;
|
||||
$adminDirect[$adminId] = ($adminDirect[$adminId] ?? 0) + $openCount;
|
||||
}
|
||||
}
|
||||
|
||||
return $direct;
|
||||
return ['dept' => $direct, 'admin' => $adminDirect];
|
||||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $rows @param array<int,int> $direct */
|
||||
private static function applyOpenCounts(array &$rows, array $direct): int
|
||||
/**
|
||||
* @param array<int,array<string,mixed>> $rows
|
||||
* @param array<int,int> $deptDirect
|
||||
* @param array<int,int> $adminDirect
|
||||
*/
|
||||
private static function applyOpenCounts(array &$rows, array $deptDirect, array $adminDirect): int
|
||||
{
|
||||
$sum = 0;
|
||||
foreach ($rows as &$row) {
|
||||
$rowType = (string) ($row['type'] ?? '');
|
||||
if (in_array($rowType, ['member', 'unbound'], true)) {
|
||||
$count = $rowType === 'member'
|
||||
? (int) ($adminDirect[(int) ($row['admin_id'] ?? 0)] ?? 0)
|
||||
: 0;
|
||||
$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
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$children = is_array($row['children'] ?? null) ? $row['children'] : [];
|
||||
$childTotal = self::applyOpenCounts($children, $direct);
|
||||
$childTotal = self::applyOpenCounts($children, $deptDirect, $adminDirect);
|
||||
if ($children !== []) {
|
||||
$row['children'] = $children;
|
||||
}
|
||||
$count = (int) ($direct[(int) ($row['id'] ?? 0)] ?? 0) + $childTotal;
|
||||
$directCount = (int) ($deptDirect[(int) ($row['id'] ?? 0)] ?? 0);
|
||||
$count = $directCount + $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(
|
||||
@@ -453,7 +484,7 @@ class FirstVisitConversionLogic
|
||||
$count
|
||||
);
|
||||
$row['open_receive_rate'] = self::percent((int) ($row['completed_order_count'] ?? 0), $count);
|
||||
$sum += (int) ($direct[(int) ($row['id'] ?? 0)] ?? 0) + $childTotal;
|
||||
$sum += $directCount + $childTotal;
|
||||
}
|
||||
unset($row);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use think\facade\Db;
|
||||
/**
|
||||
* 一诊「医生看板」。
|
||||
*
|
||||
* 医生是最终展示维度;部门权限通过实际经手医助下推到挂号、诊单与业绩:
|
||||
* 医生是最终展示维度;部门权限通过实际经手医助下推到预约、诊单与业绩:
|
||||
* - 医生 SELF:只看本人医生数据,不限制经手医助;
|
||||
* - 医助 SELF:只看本人经手患者关联的医生数据;
|
||||
* - 组长/经理:只看数据范围内医助经手患者关联的医生数据;
|
||||
@@ -79,7 +79,15 @@ class FirstVisitDoctorDashboardLogic
|
||||
$doctorDeptNames,
|
||||
$doctorStatus
|
||||
);
|
||||
$summary = self::buildSummary($rows);
|
||||
// 支付单没有医生字段,当前数据中的低额支付单也未关联患者;挂号只能按创建人及权限范围汇总,
|
||||
// 不能为了医生排行而将医助创建的支付单虚构分摊给某位医生。
|
||||
$registrationCreatorIds = $doctorSelf ? [$adminId] : $assistantIds;
|
||||
$registrationTotal = self::loadRegistrationTotal(
|
||||
$range['start'],
|
||||
$range['end'],
|
||||
$registrationCreatorIds
|
||||
);
|
||||
$summary = self::buildSummary($rows, $registrationTotal);
|
||||
$trend = self::buildAmountTrend($doctorIds, $assistantIds);
|
||||
|
||||
$selectedDeptName = $selectedDeptId > 0
|
||||
@@ -108,7 +116,8 @@ class FirstVisitDoctorDashboardLogic
|
||||
'selected_dept_name' => $selectedDeptName,
|
||||
'selected_doctor_name' => $selectedDoctorName,
|
||||
'doctor_count' => count($rows),
|
||||
'appointment_rule' => '总挂号包含已预约、已取消、已完成和已过号;面诊取状态为已完成的挂号',
|
||||
'registration_rule' => '总挂号按支付时间统计已支付且实收金额大于 0、低于 10 元的订单,每笔计 1 个,并按订单创建人及当前权限范围归属',
|
||||
'appointment_rule' => '总预约包含已预约、已取消、已完成和已过号;面诊取状态为已完成的预约',
|
||||
'performance_rule' => '诊单按订单创建时间统计,排除已取消、拒收、全额退款及部分退款,金额归属处方开方医生',
|
||||
],
|
||||
'filters' => [
|
||||
@@ -122,7 +131,8 @@ class FirstVisitDoctorDashboardLogic
|
||||
'conversion' => self::ranking($rows, 'receive_conversion_rate', 8),
|
||||
],
|
||||
'funnel' => [
|
||||
['key' => 'appointment', 'label' => '挂号', 'value' => (int) $summary['appointment_total']],
|
||||
['key' => 'registration', 'label' => '挂号', 'value' => (int) $summary['registration_total']],
|
||||
['key' => 'appointment', 'label' => '预约', 'value' => (int) $summary['appointment_total']],
|
||||
['key' => 'interview', 'label' => '面诊', 'value' => (int) $summary['interview_count']],
|
||||
['key' => 'receive', 'label' => '接诊', 'value' => (int) $summary['order_count']],
|
||||
['key' => 'deal', 'label' => '成交', 'value' => (int) $summary['order_count']],
|
||||
@@ -327,7 +337,7 @@ class FirstVisitDoctorDashboardLogic
|
||||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $rows @return array<string,mixed> */
|
||||
private static function buildSummary(array $rows): array
|
||||
private static function buildSummary(array $rows, int $registrationTotal): array
|
||||
{
|
||||
$appointmentTotal = 0;
|
||||
$interviewCount = 0;
|
||||
@@ -345,6 +355,7 @@ class FirstVisitDoctorDashboardLogic
|
||||
}
|
||||
|
||||
return [
|
||||
'registration_total' => $registrationTotal,
|
||||
'appointment_total' => $appointmentTotal,
|
||||
'interview_count' => $interviewCount,
|
||||
'order_count' => $orderCount,
|
||||
@@ -361,6 +372,40 @@ class FirstVisitDoctorDashboardLogic
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 新挂号口径:支付时间位于筛选区间、状态为已支付、0 < 实收金额 < 10 元。
|
||||
* null 表示全部创建人,空数组表示当前权限范围没有可统计创建人。
|
||||
*
|
||||
* @param int[]|null $creatorIds
|
||||
*/
|
||||
private static function loadRegistrationTotal(
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
?array $creatorIds
|
||||
): int {
|
||||
if ($creatorIds === []) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$query = Db::name('order')
|
||||
->whereNull('delete_time')
|
||||
->where('status', 2)
|
||||
->where('amount', '>', 0)
|
||||
->where('amount', '<', 10)
|
||||
->whereNotNull('payment_time')
|
||||
->where('payment_time', '<>', '')
|
||||
->whereBetweenTime(
|
||||
'payment_time',
|
||||
$startDate . ' 00:00:00',
|
||||
$endDate . ' 23:59:59'
|
||||
);
|
||||
if ($creatorIds !== null) {
|
||||
$query->whereIn('creator_id', $creatorIds);
|
||||
}
|
||||
|
||||
return (int) $query->count();
|
||||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $rows @return array<int,array<string,mixed>> */
|
||||
private static function ranking(array $rows, string $field, int $limit): array
|
||||
{
|
||||
|
||||
@@ -14,7 +14,9 @@ use think\facade\Db;
|
||||
* 一诊「挂号统计」。
|
||||
*
|
||||
* 统计口径:
|
||||
* - 挂号:doctor_appointment.appointment_date,状态 1/3/4,排除已取消 2;
|
||||
* - 挂号:order.payment_time,已支付且 0 < amount < 10,每笔支付订单计 1 个;
|
||||
* 按支付订单 creator_id 归属员工。
|
||||
* - 预约:doctor_appointment.appointment_date,状态 1/3/4,排除已取消 2;
|
||||
* 归属优先挂号医助 assistant_id,再回退诊单医助 assistant_id。
|
||||
* - 诊单:tcm_prescription_order.create_time,归属订单 creator_id,排除履约 4/9/10。
|
||||
* - 所有部门和员工筛选都只能收窄 DataScope,不允许 HTTP 参数扩大当前账号范围。
|
||||
@@ -62,6 +64,11 @@ class FirstVisitRegistrationStatsLogic
|
||||
$range['day_after_tomorrow'],
|
||||
$assistantIds
|
||||
);
|
||||
$registrationDaily = self::loadRegistrationDaily(
|
||||
min($range['compare_start'], $range['start']),
|
||||
$range['end'],
|
||||
$assistantIds
|
||||
);
|
||||
$orderDaily = self::loadOrderDaily(
|
||||
min($range['compare_start'], $range['start']),
|
||||
$range['end'],
|
||||
@@ -73,6 +80,7 @@ class FirstVisitRegistrationStatsLogic
|
||||
$assistantIds,
|
||||
$assignment,
|
||||
$appointmentDaily,
|
||||
$registrationDaily,
|
||||
$orderDaily,
|
||||
$range
|
||||
);
|
||||
@@ -112,6 +120,7 @@ class FirstVisitRegistrationStatsLogic
|
||||
'selected_dept_name' => $selectedDeptName,
|
||||
'selected_assistant_name' => $selectedAssistantName,
|
||||
'member_count' => count($assistantIds),
|
||||
'registration_rule' => '支付时间在统计区间,状态为已支付且实收金额低于 10 元(大于 0 元),每笔支付订单计 1 个挂号',
|
||||
'appointment_rule' => '预约日期在统计区间,状态为已预约、已完成或已过号,排除已取消',
|
||||
'performance_rule' => '按订单创建时间和创建人统计,排除已取消、拒收和退款',
|
||||
],
|
||||
@@ -123,6 +132,7 @@ class FirstVisitRegistrationStatsLogic
|
||||
'employee_rows' => $groups,
|
||||
'rankings' => [
|
||||
'performance' => self::rankMembers($members, 'order_amount', 10),
|
||||
'registrations' => self::rankMembers($members, 'registration_count', 10),
|
||||
'appointments' => self::rankMembers($members, 'appointment_count', 10),
|
||||
],
|
||||
'departments' => self::departmentSummaryRows($groups),
|
||||
@@ -305,6 +315,39 @@ class FirstVisitRegistrationStatsLogic
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** @param int[] $assistantIds @return array<int,array<string,array{count:int}>> */
|
||||
private static function loadRegistrationDaily(string $startDate, string $endDate, array $assistantIds): array
|
||||
{
|
||||
if ($assistantIds === []) {
|
||||
return [];
|
||||
}
|
||||
$rows = Db::name('order')->alias('o')
|
||||
->whereNull('o.delete_time')
|
||||
->where('o.status', 2)
|
||||
->where('o.amount', '>', 0)
|
||||
->where('o.amount', '<', 10)
|
||||
->whereBetweenTime(
|
||||
'o.payment_time',
|
||||
$startDate . ' 00:00:00',
|
||||
$endDate . ' 23:59:59'
|
||||
)
|
||||
->whereIn('o.creator_id', $assistantIds)
|
||||
->fieldRaw('o.creator_id AS assistant_id, DATE(o.payment_time) AS date_label, COUNT(*) AS item_count')
|
||||
->group(['o.creator_id', 'date_label'])
|
||||
->select()
|
||||
->toArray();
|
||||
$out = [];
|
||||
foreach ($rows as $row) {
|
||||
$aid = (int) ($row['assistant_id'] ?? 0);
|
||||
$date = (string) ($row['date_label'] ?? '');
|
||||
if ($aid > 0 && $date !== '') {
|
||||
$out[$aid][$date] = ['count' => (int) ($row['item_count'] ?? 0)];
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** @param int[] $assistantIds @return array<int,array<string,array{count:int,amount:float}>> */
|
||||
private static function loadOrderDaily(string $startDate, string $endDate, array $assistantIds): array
|
||||
{
|
||||
@@ -345,6 +388,7 @@ class FirstVisitRegistrationStatsLogic
|
||||
array $assistantIds,
|
||||
array $assignment,
|
||||
array $appointmentDaily,
|
||||
array $registrationDaily,
|
||||
array $orderDaily,
|
||||
array $range
|
||||
): array {
|
||||
@@ -356,6 +400,8 @@ class FirstVisitRegistrationStatsLogic
|
||||
foreach ($assistantIds as $aid) {
|
||||
$appointmentCount = self::sumDaily($appointmentDaily[$aid] ?? [], $range['start'], $range['end'], 'count');
|
||||
$compareAppointmentCount = self::sumDaily($appointmentDaily[$aid] ?? [], $range['compare_start'], $range['compare_end'], 'count');
|
||||
$registrationCount = self::sumDaily($registrationDaily[$aid] ?? [], $range['start'], $range['end'], 'count');
|
||||
$compareRegistrationCount = self::sumDaily($registrationDaily[$aid] ?? [], $range['compare_start'], $range['compare_end'], 'count');
|
||||
$orderCount = self::sumDaily($orderDaily[$aid] ?? [], $range['start'], $range['end'], 'count');
|
||||
$orderAmount = self::sumDaily($orderDaily[$aid] ?? [], $range['start'], $range['end'], 'amount');
|
||||
$rows[] = [
|
||||
@@ -364,6 +410,9 @@ class FirstVisitRegistrationStatsLogic
|
||||
'dept_id' => (int) ($assignment[$aid] ?? 0),
|
||||
'name' => (string) ($assistantIndex[$aid] ?? '未命名员工'),
|
||||
'row_type' => 'employee',
|
||||
'registration_count' => (int) $registrationCount,
|
||||
'compare_registration_count' => (int) $compareRegistrationCount,
|
||||
'registration_compare_rate' => self::relativeChange($registrationCount, $compareRegistrationCount),
|
||||
'appointment_count' => (int) $appointmentCount,
|
||||
'compare_appointment_count' => (int) $compareAppointmentCount,
|
||||
'appointment_compare_rate' => self::relativeChange($appointmentCount, $compareAppointmentCount),
|
||||
@@ -374,7 +423,7 @@ class FirstVisitRegistrationStatsLogic
|
||||
'status' => 'normal',
|
||||
];
|
||||
}
|
||||
usort($rows, static fn (array $a, array $b): int => ($b['appointment_count'] <=> $a['appointment_count']) ?: ($b['order_amount'] <=> $a['order_amount']));
|
||||
usort($rows, static fn (array $a, array $b): int => ($b['registration_count'] <=> $a['registration_count']) ?: ($b['appointment_count'] <=> $a['appointment_count']) ?: ($b['order_amount'] <=> $a['order_amount']));
|
||||
|
||||
return $rows;
|
||||
}
|
||||
@@ -393,6 +442,8 @@ class FirstVisitRegistrationStatsLogic
|
||||
'name' => $key > 0 ? (string) ($deptIndex[$key]['name'] ?? '未命名部门') : '未分配部门',
|
||||
'row_type' => 'department',
|
||||
'member_count' => 0,
|
||||
'registration_count' => 0,
|
||||
'compare_registration_count' => 0,
|
||||
'appointment_count' => 0,
|
||||
'compare_appointment_count' => 0,
|
||||
'tomorrow_count' => 0,
|
||||
@@ -405,13 +456,17 @@ class FirstVisitRegistrationStatsLogic
|
||||
}
|
||||
$groups[$key]['children'][] = $member;
|
||||
$groups[$key]['member_count']++;
|
||||
foreach (['appointment_count', 'compare_appointment_count', 'tomorrow_count', 'day_after_count', 'order_count'] as $field) {
|
||||
foreach (['registration_count', 'compare_registration_count', 'appointment_count', 'compare_appointment_count', 'tomorrow_count', 'day_after_count', 'order_count'] as $field) {
|
||||
$groups[$key][$field] += (int) ($member[$field] ?? 0);
|
||||
}
|
||||
$groups[$key]['order_amount'] += (float) ($member['order_amount'] ?? 0);
|
||||
}
|
||||
foreach ($groups as &$group) {
|
||||
$group['order_amount'] = round((float) $group['order_amount'], 2);
|
||||
$group['registration_compare_rate'] = self::relativeChange(
|
||||
(float) $group['registration_count'],
|
||||
(float) $group['compare_registration_count']
|
||||
);
|
||||
$group['appointment_compare_rate'] = self::relativeChange(
|
||||
(float) $group['appointment_count'],
|
||||
(float) $group['compare_appointment_count']
|
||||
@@ -432,11 +487,15 @@ class FirstVisitRegistrationStatsLogic
|
||||
/** @param array<int,array<string,mixed>> $members @return array<string,mixed> */
|
||||
private static function buildSummary(array $members, array $range): array
|
||||
{
|
||||
$registrationCount = 0;
|
||||
$compareRegistrationCount = 0;
|
||||
$appointmentCount = 0;
|
||||
$compareAppointmentCount = 0;
|
||||
$orderCount = 0;
|
||||
$orderAmount = 0.0;
|
||||
foreach ($members as $member) {
|
||||
$registrationCount += (int) ($member['registration_count'] ?? 0);
|
||||
$compareRegistrationCount += (int) ($member['compare_registration_count'] ?? 0);
|
||||
$appointmentCount += (int) ($member['appointment_count'] ?? 0);
|
||||
$compareAppointmentCount += (int) ($member['compare_appointment_count'] ?? 0);
|
||||
$orderCount += (int) ($member['order_count'] ?? 0);
|
||||
@@ -444,6 +503,9 @@ class FirstVisitRegistrationStatsLogic
|
||||
}
|
||||
|
||||
return [
|
||||
'registration_count' => $registrationCount,
|
||||
'registration_compare_count' => $compareRegistrationCount,
|
||||
'registration_compare_rate' => self::relativeChange($registrationCount, $compareRegistrationCount),
|
||||
'appointment_count' => $appointmentCount,
|
||||
'appointment_compare_count' => $compareAppointmentCount,
|
||||
'appointment_compare_rate' => self::relativeChange($appointmentCount, $compareAppointmentCount),
|
||||
@@ -460,13 +522,18 @@ class FirstVisitRegistrationStatsLogic
|
||||
usort($rows, static fn (array $a, array $b): int => (($b[$field] ?? 0) <=> ($a[$field] ?? 0)) ?: strcmp((string) $a['name'], (string) $b['name']));
|
||||
$out = [];
|
||||
foreach (array_slice($rows, 0, $limit) as $row) {
|
||||
$countField = match ($field) {
|
||||
'order_amount' => 'order_count',
|
||||
'registration_count' => 'registration_count',
|
||||
default => 'appointment_count',
|
||||
};
|
||||
$out[] = [
|
||||
'admin_id' => (int) ($row['admin_id'] ?? 0),
|
||||
'name' => (string) ($row['name'] ?? ''),
|
||||
'value' => $field === 'order_amount'
|
||||
? round((float) ($row[$field] ?? 0), 2)
|
||||
: (int) ($row[$field] ?? 0),
|
||||
'count' => $field === 'order_amount' ? (int) ($row['order_count'] ?? 0) : (int) ($row['appointment_count'] ?? 0),
|
||||
'count' => (int) ($row[$countField] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,8 @@ class ConversionLogic
|
||||
$endDate,
|
||||
$mediaChannel,
|
||||
$visibleAdminIds,
|
||||
$excludeCancelledAppointments
|
||||
$excludeCancelledAppointments,
|
||||
$usePerformanceOrderMetrics
|
||||
);
|
||||
self::hydrateOrderAndAmountStats(
|
||||
$entities,
|
||||
@@ -251,7 +252,9 @@ class ConversionLogic
|
||||
$adminToDeptIds,
|
||||
$validDeptIds,
|
||||
$globalAccountCost,
|
||||
$visibleAdminIds
|
||||
$visibleAdminIds,
|
||||
$excludeCancelledAppointments,
|
||||
$usePerformanceOrderMetrics
|
||||
);
|
||||
$pagedRows = self::attachDeptMembers($pagedRows, $memberRowsByDeptId);
|
||||
}
|
||||
@@ -983,7 +986,8 @@ class ConversionLogic
|
||||
string $endDate,
|
||||
?array $mediaChannel,
|
||||
?array $visibleAdminIds = null,
|
||||
bool $excludeCancelledAppointments = false
|
||||
bool $excludeCancelledAppointments = false,
|
||||
bool $useRegistrationMetric = false
|
||||
): void {
|
||||
$sourceExpr = $dimension === 'doctor'
|
||||
? 'a.doctor_id'
|
||||
@@ -1040,7 +1044,17 @@ class ConversionLogic
|
||||
}
|
||||
}
|
||||
|
||||
self::hydratePaidAppointmentStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel, $visibleAdminIds);
|
||||
self::hydratePaidAppointmentStats(
|
||||
$entities,
|
||||
$dimension,
|
||||
$entityIds,
|
||||
$adminToDeptIds,
|
||||
$startTimestamp,
|
||||
$endTimestamp,
|
||||
$mediaChannel,
|
||||
$visibleAdminIds,
|
||||
$useRegistrationMetric
|
||||
);
|
||||
|
||||
foreach ($entities as &$entity) {
|
||||
$appointmentTotalCount = (int)($entity['appointment_total_count'] ?? 0);
|
||||
@@ -1065,7 +1079,8 @@ class ConversionLogic
|
||||
int $startTimestamp,
|
||||
int $endTimestamp,
|
||||
?array $mediaChannel,
|
||||
?array $visibleAdminIds = null
|
||||
?array $visibleAdminIds = null,
|
||||
bool $useRegistrationMetric = false
|
||||
): void {
|
||||
$startDateTime = date('Y-m-d H:i:s', $startTimestamp);
|
||||
$endDateTime = date('Y-m-d H:i:s', $endTimestamp);
|
||||
@@ -1073,14 +1088,20 @@ class ConversionLogic
|
||||
->alias('o')
|
||||
->whereNull('o.delete_time')
|
||||
->where('o.status', 2)
|
||||
->where('o.order_type', 1)
|
||||
->where('o.amount', 5)
|
||||
->whereNotNull('o.payment_time')
|
||||
->where('o.payment_time', '<>', '')
|
||||
->whereBetweenTime('o.payment_time', $startDateTime, $endDateTime)
|
||||
->fieldRaw('o.creator_id AS source_admin_id, COUNT(*) AS paid_appointment_count')
|
||||
->group('o.creator_id');
|
||||
|
||||
if ($useRegistrationMetric) {
|
||||
// 一诊页面的新“挂号”:已支付且 0 < 实收金额 < 10 元,每笔支付订单计 1 个。
|
||||
$query->where('o.amount', '>', 0)->where('o.amount', '<', 10);
|
||||
} else {
|
||||
// 保留旧统计页面的历史“付费挂号”字段,避免本次一诊改造改变其它模块口径。
|
||||
$query->where('o.order_type', 1)->where('o.amount', 5);
|
||||
}
|
||||
|
||||
if ($mediaChannel !== null) {
|
||||
$query->leftJoin('qywx_external_contact q', 'q.external_userid = o.payer_external_userid');
|
||||
MediaChannelService::applyFollowUsersChannelFilter($query, 'q.follow_users', $mediaChannel);
|
||||
@@ -1683,6 +1704,8 @@ class ConversionLogic
|
||||
* @param float $globalAccountCost 由调用方提前计算好的本期总账户消耗(zyt_account_cost SUM)。
|
||||
* -1 表示让本函数自行 hydrate;>= 0 时直接复用,避免重复 SQL。
|
||||
* @param int[]|null $visibleAdminIds 可见 admin 集合(null = SCOPE_ALL);用于成员明细的隔离
|
||||
* @param bool $excludeCancelledAppointments 是否排除已取消挂号,须与部门汇总口径一致
|
||||
* @param bool $usePerformanceOrderMetrics 是否使用有效业绩订单口径,须与部门汇总口径一致
|
||||
* @return array<int, array<int, array<string, mixed>>> dept_id => [member_row, ...]
|
||||
*/
|
||||
private static function buildMemberRowsByDept(
|
||||
@@ -1697,7 +1720,9 @@ class ConversionLogic
|
||||
array $adminToDeptIds,
|
||||
array $validDeptIds = [],
|
||||
float $globalAccountCost = -1.0,
|
||||
?array $visibleAdminIds = null
|
||||
?array $visibleAdminIds = null,
|
||||
bool $excludeCancelledAppointments = false,
|
||||
bool $usePerformanceOrderMetrics = false
|
||||
): array
|
||||
{
|
||||
$assistantEntities = self::loadAdminEntities(2, 0, $visibleAdminIds);
|
||||
@@ -1706,15 +1731,15 @@ class ConversionLogic
|
||||
$assistantIds = array_keys($assistantEntities);
|
||||
$doctorIds = array_keys($doctorEntities);
|
||||
|
||||
if ($assistantIds !== []) {
|
||||
if ($assistantIds !== [] && !$usePerformanceOrderMetrics) {
|
||||
self::hydrateFanStats($assistantEntities, 'assistant', $assistantIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel, $visibleAdminIds);
|
||||
self::hydrateAppointmentStats($assistantEntities, 'assistant', $assistantIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $startDate, $endDate, $mediaChannel, $visibleAdminIds);
|
||||
self::hydrateOrderAndAmountStats($assistantEntities, 'assistant', $assistantIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel, $visibleAdminIds);
|
||||
self::hydrateAppointmentStats($assistantEntities, 'assistant', $assistantIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $startDate, $endDate, $mediaChannel, $visibleAdminIds, $excludeCancelledAppointments);
|
||||
self::hydrateOrderAndAmountStats($assistantEntities, 'assistant', $assistantIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel, $visibleAdminIds, $usePerformanceOrderMetrics);
|
||||
}
|
||||
if ($doctorIds !== []) {
|
||||
if ($doctorIds !== [] && !$usePerformanceOrderMetrics) {
|
||||
self::hydrateFanStats($doctorEntities, 'doctor', $doctorIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel, $visibleAdminIds);
|
||||
self::hydrateAppointmentStats($doctorEntities, 'doctor', $doctorIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $startDate, $endDate, $mediaChannel, $visibleAdminIds);
|
||||
self::hydrateOrderAndAmountStats($doctorEntities, 'doctor', $doctorIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel, $visibleAdminIds);
|
||||
self::hydrateAppointmentStats($doctorEntities, 'doctor', $doctorIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $startDate, $endDate, $mediaChannel, $visibleAdminIds, $excludeCancelledAppointments);
|
||||
self::hydrateOrderAndAmountStats($doctorEntities, 'doctor', $doctorIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel, $visibleAdminIds, $usePerformanceOrderMetrics);
|
||||
}
|
||||
|
||||
// 复用调用方传入的 global account cost;只有兜底未传时才回查一次(保留向后兼容)。
|
||||
@@ -1749,6 +1774,15 @@ class ConversionLogic
|
||||
}
|
||||
}
|
||||
|
||||
if ($usePerformanceOrderMetrics && $combined !== []) {
|
||||
// 一诊综合转化的部门指标均按业务归属人统计。成员明细也必须沿用同一归属,
|
||||
// 不能再分别按“医助/医生”统计后相加,否则双角色员工会重复、人员合计也无法与部门汇总对齐。
|
||||
$combinedIds = array_keys($combined);
|
||||
self::hydrateFanStats($combined, 'assistant', $combinedIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel, $visibleAdminIds);
|
||||
self::hydrateAppointmentStats($combined, 'assistant', $combinedIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $startDate, $endDate, $mediaChannel, $visibleAdminIds, $excludeCancelledAppointments, true);
|
||||
self::hydrateOrderAndAmountStats($combined, 'assistant', $combinedIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel, $visibleAdminIds, true);
|
||||
}
|
||||
|
||||
if ($combined === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -18,14 +18,13 @@ use think\facade\Db;
|
||||
* 数据口径:
|
||||
* - 所有“业绩/接诊诊单”与业绩统计、业务订单列表保持一致:按业务订单创建时间,
|
||||
* 排除履约已取消/拒收/退款(4/9/10),金额取业务订单 amount,归属人取订单 creator_id。
|
||||
* - 今日加粉、挂号、面诊和转化率继续沿用 ConversionLogic;它们不是业绩指标。
|
||||
* - 今日预约、面诊沿用 ConversionLogic;挂号按已支付且实收低于 10 元的订单统计。
|
||||
* - 趋势使用同一业绩条件的轻量按日 SQL,固定补齐最近 7 个自然日。
|
||||
* - 所有查询都使用 DataScopeService 返回的可见管理员集合收窄。
|
||||
*/
|
||||
class PerformanceDashboardLogic
|
||||
{
|
||||
private const TREND_DAYS = 7;
|
||||
private const PAID_APPOINTMENT_DEPT_NAME = '一中心';
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
@@ -57,7 +56,7 @@ class PerformanceDashboardLogic
|
||||
|
||||
$orderDaily = self::loadPerformanceOrderDaily($previousMonthStart, $today, $visibleAdminIds);
|
||||
$personalOrderDaily = self::loadPerformanceOrderDaily($monthStart, $today, [$adminId]);
|
||||
$lowAmountPaymentDaily = self::loadLowAmountPaymentDaily($yesterday, $today, $visibleAdminIds);
|
||||
$registrationDaily = self::loadRegistrationDaily($trendStart, $today, $visibleAdminIds);
|
||||
|
||||
$monthAmount = self::sumDailyMetric($orderDaily, $monthStart, $today, 'amount');
|
||||
$previousMonthAmount = self::sumDailyMetric(
|
||||
@@ -98,7 +97,7 @@ class PerformanceDashboardLogic
|
||||
'start_date' => $today,
|
||||
'end_date' => $today,
|
||||
], $adminId, $adminInfo);
|
||||
$appointmentRanking = self::buildAppointmentRanking(
|
||||
$appointmentRanking = self::buildRegistrationRanking(
|
||||
$adminId,
|
||||
$adminInfo,
|
||||
$scope,
|
||||
@@ -112,7 +111,14 @@ class PerformanceDashboardLogic
|
||||
'start_date' => $trendStart,
|
||||
'end_date' => $today,
|
||||
], $adminId, $adminInfo);
|
||||
$trend = self::buildTrend($trendStart, $today, $visibleAdminIds, $orderDaily, $trendContext);
|
||||
$trend = self::buildTrend(
|
||||
$trendStart,
|
||||
$today,
|
||||
$visibleAdminIds,
|
||||
$orderDaily,
|
||||
$registrationDaily,
|
||||
$trendContext
|
||||
);
|
||||
$todayTrendIndex = max(0, count($trend['dates'] ?? []) - 1);
|
||||
$yesterdayTrendIndex = max(0, $todayTrendIndex - 1);
|
||||
|
||||
@@ -126,10 +132,10 @@ class PerformanceDashboardLogic
|
||||
$yesterdayOrderCount = (int) self::dailyMetric($orderDaily, $yesterday, 'count');
|
||||
$todayOrderAmount = self::dailyMetric($orderDaily, $today, 'amount');
|
||||
$yesterdayOrderAmount = self::dailyMetric($orderDaily, $yesterday, 'amount');
|
||||
$todayLowAmountPaymentCount = (int) self::dailyMetric($lowAmountPaymentDaily, $today, 'count');
|
||||
$yesterdayLowAmountPaymentCount = (int) self::dailyMetric($lowAmountPaymentDaily, $yesterday, 'count');
|
||||
$todayPaidAppointmentCount = self::paidAppointmentCountWithCenterRule($todayOverview);
|
||||
$yesterdayPaidAppointmentCount = self::paidAppointmentCountWithCenterRule($yesterdayOverview);
|
||||
$todayLowAmountPaymentCount = (int) self::dailyMetric($registrationDaily, $today, 'count');
|
||||
$yesterdayLowAmountPaymentCount = (int) self::dailyMetric($registrationDaily, $yesterday, 'count');
|
||||
$todayPaidAppointmentCount = $todayLowAmountPaymentCount;
|
||||
$yesterdayPaidAppointmentCount = $yesterdayLowAmountPaymentCount;
|
||||
$todayPaidAppointmentRate = self::percent($todayPaidAppointmentCount, $todayAddFansCount);
|
||||
$yesterdayPaidAppointmentRate = self::percent($yesterdayPaidAppointmentCount, $yesterdayAddFansCount);
|
||||
// 接诊卡片使用的是有效业务订单,接诊率必须使用同一订单口径,不能继续读取旧的双审完成单。
|
||||
@@ -210,60 +216,11 @@ class PerformanceDashboardLogic
|
||||
'generated_at' => date('Y-m-d H:i:s'),
|
||||
'timezone' => date_default_timezone_get(),
|
||||
'commission_note' => '本人业绩按当前账号创建的业务订单统计,排除已取消、拒收和退款订单。',
|
||||
'rate_note' => '付费挂号率:一中心员工的有效挂号全部按付费挂号,其他部门按 5 元实付挂号;接诊率:有效业务诊单数 / 已完成面诊数。近 7 天挂号排除已取消记录,诊单按订单创建时间统计并排除履约 4/9/10。',
|
||||
'rate_note' => '挂号及挂号率:按支付时间统计已支付且 0<实收金额<10 元的订单,每笔订单计 1 个挂号,并按订单创建人归属;预约按预约日期统计有效预约记录。接诊率:有效业务诊单数 / 已完成面诊数。',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 一中心员工的有效挂号全部视为付费;其它部门仍沿用 5 元实付挂号口径。
|
||||
* 部门树的父节点已汇总子节点,因此命中“一中心”后不再向下递归,避免重复累计。
|
||||
*
|
||||
* @param array<string, mixed> $overview
|
||||
*/
|
||||
private static function paidAppointmentCountWithCenterRule(array $overview): int
|
||||
{
|
||||
$summary = is_array($overview['summary'] ?? null) ? $overview['summary'] : [];
|
||||
$paidTotal = (int) ($summary['paid_appointment_count'] ?? 0);
|
||||
$rows = is_array($overview['lists'] ?? null) ? $overview['lists'] : [];
|
||||
[$centerAppointments, $centerPaidAppointments] = self::namedDepartmentAppointmentMetrics(
|
||||
$rows,
|
||||
self::PAID_APPOINTMENT_DEPT_NAME
|
||||
);
|
||||
|
||||
return max(0, $paidTotal - $centerPaidAppointments + $centerAppointments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $rows
|
||||
* @return array{0:int,1:int}
|
||||
*/
|
||||
private static function namedDepartmentAppointmentMetrics(array $rows, string $departmentName): array
|
||||
{
|
||||
$appointmentCount = 0;
|
||||
$paidAppointmentCount = 0;
|
||||
foreach ($rows as $row) {
|
||||
if (trim((string) ($row['name'] ?? '')) === $departmentName) {
|
||||
$appointmentCount += (int) ($row['appointment_total_count'] ?? 0);
|
||||
$paidAppointmentCount += (int) ($row['paid_appointment_count'] ?? 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
$children = is_array($row['children'] ?? null) ? $row['children'] : [];
|
||||
if ($children === []) {
|
||||
continue;
|
||||
}
|
||||
[$childAppointments, $childPaidAppointments] = self::namedDepartmentAppointmentMetrics(
|
||||
$children,
|
||||
$departmentName
|
||||
);
|
||||
$appointmentCount += $childAppointments;
|
||||
$paidAppointmentCount += $childPaidAppointments;
|
||||
}
|
||||
|
||||
return [$appointmentCount, $paidAppointmentCount];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
@@ -393,12 +350,12 @@ class PerformanceDashboardLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 当日 0 < 实收金额 <= 10 元的已支付订单笔数;退款订单状态为 4,不会进入统计。
|
||||
* 0 < 实收金额 < 10 元的已支付订单笔数;退款订单状态为 4,不会进入统计。
|
||||
*
|
||||
* @param array<int>|null $visibleAdminIds
|
||||
* @return array<string, array{count:int}>
|
||||
*/
|
||||
private static function loadLowAmountPaymentDaily(
|
||||
private static function loadRegistrationDaily(
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
?array $visibleAdminIds
|
||||
@@ -411,7 +368,7 @@ class PerformanceDashboardLogic
|
||||
->whereNull('delete_time')
|
||||
->where('status', 2)
|
||||
->where('amount', '>', 0)
|
||||
->where('amount', '<=', 10)
|
||||
->where('amount', '<', 10)
|
||||
->whereNotNull('payment_time')
|
||||
->where('payment_time', '<>', '')
|
||||
->whereBetweenTime(
|
||||
@@ -502,7 +459,7 @@ class PerformanceDashboardLogic
|
||||
* @param array<string, mixed> $scope
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function buildAppointmentRanking(
|
||||
private static function buildRegistrationRanking(
|
||||
int $adminId,
|
||||
array $adminInfo,
|
||||
array $scope,
|
||||
@@ -515,33 +472,6 @@ class PerformanceDashboardLogic
|
||||
&& in_array(1, $roleIds, true)
|
||||
&& !in_array(2, $roleIds, true);
|
||||
|
||||
if ($isDoctorSelf) {
|
||||
$doctorStats = DoctorDailyStatsLogic::overview([
|
||||
'start_date' => date('Y-m-d'),
|
||||
'end_date' => date('Y-m-d'),
|
||||
], $adminId, $adminInfo);
|
||||
$items = [];
|
||||
foreach (array_slice($doctorStats['rows'] ?? [], 0, 5) as $row) {
|
||||
$items[] = [
|
||||
'id' => (int) ($row['admin_id'] ?? 0),
|
||||
'name' => (string) ($row['doctor_name'] ?? ''),
|
||||
// DoctorDailyStats 的 total 含已取消;驾驶舱实时排行只统计有效挂号。
|
||||
'count' => max(
|
||||
0,
|
||||
(int) ($row['appointment_total'] ?? 0) - (int) ($row['appointment_cancelled'] ?? 0)
|
||||
),
|
||||
'amount' => round((float) ($row['deal_amount'] ?? 0), 2),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => '实时挂号排行',
|
||||
'kind' => 'doctor',
|
||||
'scope_label' => (string) ($scope['label'] ?? ''),
|
||||
'items' => $items,
|
||||
];
|
||||
}
|
||||
|
||||
$rankingVisibleAdminIds = $baseVisibleAdminIds;
|
||||
$rankingScopeLabel = (string) ($scope['label'] ?? '');
|
||||
if (
|
||||
@@ -562,42 +492,42 @@ class PerformanceDashboardLogic
|
||||
$rankingScopeLabel = (string) (Dept::where('id', $rankingDeptId)->value('name') ?? $rankingScopeLabel);
|
||||
}
|
||||
|
||||
$assistantStats = ConversionLogic::overview([
|
||||
'dimension' => 'assistant',
|
||||
'time_type' => 'today',
|
||||
'include_filters' => 0,
|
||||
'exclude_cancelled_appointments' => 1,
|
||||
'page_no' => 1,
|
||||
'page_size' => $rankingVisibleAdminIds !== null ? max(1, count($rankingVisibleAdminIds)) : 100,
|
||||
], $adminId, $adminInfo, $rankingVisibleAdminIds);
|
||||
$rows = is_array($assistantStats['lists'] ?? null) ? $assistantStats['lists'] : [];
|
||||
usort($rows, static function (array $a, array $b): int {
|
||||
$byAppointment = (int) ($b['appointment_total_count'] ?? 0) <=> (int) ($a['appointment_total_count'] ?? 0);
|
||||
if ($byAppointment !== 0) {
|
||||
return $byAppointment;
|
||||
}
|
||||
|
||||
$byAmount = (float) ($b['completed_order_amount'] ?? 0) <=> (float) ($a['completed_order_amount'] ?? 0);
|
||||
if ($byAmount !== 0) {
|
||||
return $byAmount;
|
||||
}
|
||||
|
||||
return (int) ($a['id'] ?? 0) <=> (int) ($b['id'] ?? 0);
|
||||
});
|
||||
|
||||
$items = [];
|
||||
foreach (array_slice($rows, 0, 5) as $row) {
|
||||
$items[] = [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'name' => (string) ($row['name'] ?? ''),
|
||||
'count' => (int) ($row['appointment_total_count'] ?? 0),
|
||||
'amount' => round((float) ($row['completed_order_amount'] ?? 0), 2),
|
||||
];
|
||||
if ($rankingVisibleAdminIds !== []) {
|
||||
$query = Db::name('order')
|
||||
->alias('o')
|
||||
->join('admin a', 'a.id = o.creator_id AND a.delete_time IS NULL', 'INNER')
|
||||
->whereNull('o.delete_time')
|
||||
->where('o.status', 2)
|
||||
->where('o.amount', '>', 0)
|
||||
->where('o.amount', '<', 10)
|
||||
->whereNotNull('o.payment_time')
|
||||
->where('o.payment_time', '<>', '')
|
||||
->whereBetweenTime('o.payment_time', date('Y-m-d 00:00:00'), date('Y-m-d 23:59:59'));
|
||||
if ($rankingVisibleAdminIds !== null) {
|
||||
$query->whereIn('o.creator_id', $rankingVisibleAdminIds);
|
||||
}
|
||||
|
||||
$rows = $query
|
||||
->fieldRaw('o.creator_id AS id, a.name, COUNT(*) AS item_count, SUM(o.amount) AS amount_sum')
|
||||
->group(['o.creator_id', 'a.name'])
|
||||
->orderRaw('item_count DESC, amount_sum DESC, o.creator_id ASC')
|
||||
->limit(5)
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($rows as $row) {
|
||||
$items[] = [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'name' => (string) ($row['name'] ?? ''),
|
||||
'count' => (int) ($row['item_count'] ?? 0),
|
||||
'amount' => round((float) ($row['amount_sum'] ?? 0), 2),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => '实时挂号排行',
|
||||
'kind' => 'assistant',
|
||||
'kind' => $isDoctorSelf ? 'doctor' : 'member',
|
||||
'scope_label' => $rankingScopeLabel,
|
||||
'items' => $items,
|
||||
];
|
||||
@@ -704,6 +634,7 @@ class PerformanceDashboardLogic
|
||||
/**
|
||||
* @param array<int>|null $visibleAdminIds
|
||||
* @param array<string, array{amount: float, count: int}> $orderDaily
|
||||
* @param array<string, array{count: int}> $registrationDaily
|
||||
* @param array<string, mixed> $trendContext
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
@@ -712,6 +643,7 @@ class PerformanceDashboardLogic
|
||||
string $endDate,
|
||||
?array $visibleAdminIds,
|
||||
array $orderDaily,
|
||||
array $registrationDaily,
|
||||
array $trendContext
|
||||
): array {
|
||||
$adminToPrimary = is_array($trendContext['adminToPrimary'] ?? null)
|
||||
@@ -729,6 +661,7 @@ class PerformanceDashboardLogic
|
||||
$tableRowDeptIds
|
||||
);
|
||||
$dates = [];
|
||||
$registrations = [];
|
||||
$appointments = [];
|
||||
$leads = [];
|
||||
$orders = [];
|
||||
@@ -738,6 +671,7 @@ class PerformanceDashboardLogic
|
||||
while ($cursor <= $end) {
|
||||
$date = date('Y-m-d', $cursor);
|
||||
$dates[] = date('m-d', $cursor);
|
||||
$registrations[] = (int) ($registrationDaily[$date]['count'] ?? 0);
|
||||
$appointments[] = (int) ($appointmentDaily[$date] ?? 0);
|
||||
$leads[] = (int) ($leadDaily[$date] ?? 0);
|
||||
$orders[] = (int) ($orderDaily[$date]['count'] ?? 0);
|
||||
@@ -747,6 +681,7 @@ class PerformanceDashboardLogic
|
||||
return [
|
||||
'date_range' => [$startDate, $endDate],
|
||||
'dates' => $dates,
|
||||
'registrations' => $registrations,
|
||||
'appointments' => $appointments,
|
||||
'leads' => $leads,
|
||||
'orders' => $orders,
|
||||
|
||||
Reference in New Issue
Block a user