新增功能
This commit is contained in:
@@ -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