Files
zyt/server/app/adminapi/lists/doctor/StatisticsLists.php
T
2026-04-09 18:35:25 +08:00

440 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\adminapi\lists\doctor;
use app\adminapi\lists\BaseAdminDataLists;
use app\common\model\doctor\Appointment;
use app\common\model\auth\Admin;
/**
* 医生统计列表
* Class StatisticsLists
* @package app\adminapi\lists\doctor
*/
class StatisticsLists extends BaseAdminDataLists
{
/**
* @notes 搜索条件
* @return array
*/
public function setSearch(): array
{
return [];
}
/**
* @notes 获取列表
* @return array
*/
public function lists(): array
{
$doctorId = $this->params['doctor_id'] ?? '';
$timeType = $this->params['time_type'] ?? 'today';
$startDate = $this->params['start_date'] ?? '';
$endDate = $this->params['end_date'] ?? '';
// 计算时间范围
list($startDate, $endDate, $timeRangeText) = $this->getTimeRange($timeType, $startDate, $endDate);
// 获取医生列表(role_id=1 表示医生角色)
$doctorAdminIds = \app\common\model\auth\AdminRole::where('role_id', 1)->column('admin_id');
if (empty($doctorAdminIds)) {
return [];
}
// 查询1:获取预约统计数据(快速查询,只查appointment表)
$statisticsData = \think\facade\Db::name('doctor_appointment')
->field([
'doctor_id',
'COUNT(*) as total_count',
'SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) as registered_count',
'SUM(CASE WHEN status = 3 THEN 1 ELSE 0 END) as completed_count',
'SUM(CASE WHEN status = 4 THEN 1 ELSE 0 END) as missed_count',
'SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) as cancelled_count'
])
->whereIn('doctor_id', $doctorAdminIds)
->where('appointment_date', '>=', $startDate)
->where('appointment_date', '<=', $endDate);
if ($doctorId) {
$statisticsData->where('doctor_id', $doctorId);
}
$statisticsData = $statisticsData->group('doctor_id')->select()->toArray();
// 将统计数据按 doctor_id 索引
$statsMap = [];
foreach ($statisticsData as $stat) {
$statsMap[$stat['doctor_id']] = $stat;
}
// 诊单数:统计期内该医生挂号对应的 distinct 诊单(patient_id 存的是诊单 id
$diagnosisCountQuery = \think\facade\Db::name('doctor_appointment')
->field('doctor_id, COUNT(DISTINCT patient_id) as diagnosis_count')
->whereIn('doctor_id', $doctorAdminIds)
->where('appointment_date', '>=', $startDate)
->where('appointment_date', '<=', $endDate);
if ($doctorId) {
$diagnosisCountQuery->where('doctor_id', $doctorId);
}
$diagnosisCountRows = $diagnosisCountQuery->group('doctor_id')->select()->toArray();
$diagnosisCountMap = [];
foreach ($diagnosisCountRows as $row) {
$diagnosisCountMap[(int) $row['doctor_id']] = (int) $row['diagnosis_count'];
}
// 成交单:上述诊单中存在有效处方(未删除、未作废)的数量
$dealCountQuery = \think\facade\Db::name('doctor_appointment')->alias('apt')
->join('tcm_prescription rx', 'rx.diagnosis_id = apt.patient_id')
->field('apt.doctor_id, COUNT(DISTINCT apt.patient_id) as deal_count')
->whereIn('apt.doctor_id', $doctorAdminIds)
->where('apt.appointment_date', '>=', $startDate)
->where('apt.appointment_date', '<=', $endDate)
->whereNull('rx.delete_time')
->whereRaw('IFNULL(rx.void_status, 0) <> 1');
if ($doctorId) {
$dealCountQuery->where('apt.doctor_id', $doctorId);
}
$dealCountRows = $dealCountQuery->group('apt.doctor_id')->select()->toArray();
$dealCountMap = [];
foreach ($dealCountRows as $row) {
$dealCountMap[(int) $row['doctor_id']] = (int) $row['deal_count'];
}
// 获取医生信息
$doctorQuery = Admin::field('id,name')->whereIn('id', $doctorAdminIds);
if ($doctorId) {
$doctorQuery->where('id', $doctorId);
}
$doctors = $doctorQuery->select()->toArray();
// 查询2:获取部门统计信息(通过assistant_id直接关联,按部门和状态分组统计数量)
$doctorIdsWithAppointments = array_keys($statsMap);
$deptStatsMap = [];
$deptStatusMap = [];
$channelStatsMap = [];
$channelStatusMap = [];
if (!empty($doctorIdsWithAppointments)) {
// 通过 assistant_id → admin_dept → dept 获取部门统计(按状态分组)
$deptStats = \think\facade\Db::name('doctor_appointment')
->alias('apt')
->leftJoin('admin_dept ad', 'apt.assistant_id = ad.admin_id')
->leftJoin('dept dept', 'ad.dept_id = dept.id')
->field([
'apt.doctor_id',
'dept.id as dept_id',
'dept.name as dept_name',
'apt.status',
'COUNT(*) as count'
])
->whereIn('apt.doctor_id', $doctorIdsWithAppointments)
->where('apt.appointment_date', '>=', $startDate)
->where('apt.appointment_date', '<=', $endDate)
->whereNotNull('dept.id')
->group('apt.doctor_id, dept.id, apt.status')
->select()
->toArray();
// 组装部门统计数据:医生ID => "部门1(数量1) 部门2(数量2)"
// 同时组装部门状态数据:医生ID => 部门ID => 状态 => 数量
foreach ($deptStats as $stat) {
$doctorId = $stat['doctor_id'];
$deptId = $stat['dept_id'];
$deptName = $stat['dept_name'];
$status = $stat['status'];
$count = $stat['count'];
// 组装部门统计数据
if (!isset($deptStatsMap[$doctorId])) {
$deptStatsMap[$doctorId] = [];
}
if (!isset($deptStatsMap[$doctorId][$deptId])) {
$deptStatsMap[$doctorId][$deptId] = [
'dept_name' => $deptName,
'total_count' => 0
];
}
$deptStatsMap[$doctorId][$deptId]['total_count'] += $count;
// 组装部门状态数据
if (!isset($deptStatusMap[$doctorId])) {
$deptStatusMap[$doctorId] = [];
}
if (!isset($deptStatusMap[$doctorId][$deptId])) {
$deptStatusMap[$doctorId][$deptId] = [
'dept_name' => $deptName,
'statuses' => []
];
}
$deptStatusMap[$doctorId][$deptId]['statuses'][$status] = $count;
}
// 格式化部门统计数据
foreach ($deptStatsMap as $doctorId => &$depts) {
$formattedDepts = [];
foreach ($depts as $dept) {
$formattedDepts[] = $dept['dept_name'] . '(' . $dept['total_count'] . ')';
}
$deptStatsMap[$doctorId] = $formattedDepts;
}
// 获取渠道字典(dict_data.value => name),与挂号创建时 channel_source 存字典 value 一致
$channelDict = \think\facade\Db::name('dict_data')
->where('type_value', 'channels')
->column('name', 'value');
// 渠道:业务保存的是 channel_source(字典 value 字符串,见 AppointmentLogic::create);旧库可能仅有 channels(tinyint)
$channelBuckets = [];
$channelStatusBuckets = [];
try {
$channelStats = \think\facade\Db::name('doctor_appointment')
->field('doctor_id, channel_source, status, COUNT(*) as channel_count')
->whereIn('doctor_id', $doctorIdsWithAppointments)
->where('appointment_date', '>=', $startDate)
->where('appointment_date', '<=', $endDate)
->where('channel_source', '<>', '')
->group('doctor_id, channel_source, status')
->select()
->toArray();
foreach ($channelStats as $stat) {
$did = (int) $stat['doctor_id'];
$src = trim((string) ($stat['channel_source'] ?? ''));
$status = $stat['status'];
$cnt = (int) ($stat['channel_count'] ?? 0);
if ($src === '' || $cnt < 1) {
continue;
}
// 组装渠道统计数据
if (!isset($channelBuckets[$did])) {
$channelBuckets[$did] = [];
}
if (!isset($channelBuckets[$did][$src])) {
$channelBuckets[$did][$src] = 0;
}
$channelBuckets[$did][$src] += $cnt;
// 组装渠道状态数据
if (!isset($channelStatusBuckets[$did])) {
$channelStatusBuckets[$did] = [];
}
if (!isset($channelStatusBuckets[$did][$src])) {
$channelStatusBuckets[$did][$src] = [];
}
$channelStatusBuckets[$did][$src][$status] = $cnt;
}
} catch (\Throwable) {
// 无 channel_source 字段等
}
$mergeLegacyChannels = static function (array &$buckets, array &$statusBuckets, array $rows): void {
foreach ($rows as $stat) {
$did = (int) $stat['doctor_id'];
$key = (string) (int) ($stat['channels'] ?? 0);
$status = $stat['status'];
$cnt = (int) ($stat['channel_count'] ?? 0);
if ($key === '0' || $cnt < 1) {
continue;
}
// 组装渠道统计数据
if (!isset($buckets[$did])) {
$buckets[$did] = [];
}
if (!isset($buckets[$did][$key])) {
$buckets[$did][$key] = 0;
}
$buckets[$did][$key] += $cnt;
// 组装渠道状态数据
if (!isset($statusBuckets[$did])) {
$statusBuckets[$did] = [];
}
if (!isset($statusBuckets[$did][$key])) {
$statusBuckets[$did][$key] = [];
}
$statusBuckets[$did][$key][$status] = $cnt;
}
};
try {
$legacyStats = \think\facade\Db::name('doctor_appointment')
->field('doctor_id, channels, status, COUNT(*) as channel_count')
->whereIn('doctor_id', $doctorIdsWithAppointments)
->where('appointment_date', '>=', $startDate)
->where('appointment_date', '<=', $endDate)
->where('channels', '>', 0)
->where(function ($q) {
$q->whereNull('channel_source')->whereOr('channel_source', '=', '');
})
->group('doctor_id, channels, status')
->select()
->toArray();
$mergeLegacyChannels($channelBuckets, $channelStatusBuckets, $legacyStats);
} catch (\Throwable) {
try {
$legacyStats = \think\facade\Db::name('doctor_appointment')
->field('doctor_id, channels, status, COUNT(*) as channel_count')
->whereIn('doctor_id', $doctorIdsWithAppointments)
->where('appointment_date', '>=', $startDate)
->where('appointment_date', '<=', $endDate)
->where('channels', '>', 0)
->group('doctor_id, channels, status')
->select()
->toArray();
$mergeLegacyChannels($channelBuckets, $channelStatusBuckets, $legacyStats);
} catch (\Throwable) {
// 无 channels 字段
}
}
foreach ($channelBuckets as $did => $byKey) {
$parts = [];
foreach ($byKey as $key => $cnt) {
$label = $channelDict[$key] ?? $channelDict[(string) $key] ?? $key;
$parts[] = $label . '(' . $cnt . ')';
}
$channelStatsMap[$did] = $parts;
}
// 组装渠道状态明细数据
foreach ($channelStatusBuckets as $did => $byKey) {
foreach ($byKey as $key => $statuses) {
$label = $channelDict[$key] ?? $channelDict[(string) $key] ?? $key;
if (!isset($channelStatusMap[$did])) {
$channelStatusMap[$did] = [];
}
$channelStatusMap[$did][] = [
'channel_name' => $label,
'statuses' => $statuses
];
}
}
}
// 组装结果
$result = [];
foreach ($doctors as $doctor) {
$stat = $statsMap[$doctor['id']] ?? [
'total_count' => 0,
'registered_count' => 0,
'completed_count' => 0,
'missed_count' => 0,
'cancelled_count' => 0
];
$did = (int) $doctor['id'];
$diagnosisCount = $diagnosisCountMap[$did] ?? 0;
$dealCount = $dealCountMap[$did] ?? 0;
$dealRate = $diagnosisCount > 0
? round(($dealCount / $diagnosisCount) * 100, 2)
: 0;
// 计算完成率
$completionRate = $stat['total_count'] > 0
? round(($stat['completed_count'] / $stat['total_count']) * 100, 2)
: 0;
// 格式化部门状态明细
$deptStatusDetails = [];
if (isset($deptStatusMap[$doctor['id']])) {
foreach ($deptStatusMap[$doctor['id']] as $deptId => $deptInfo) {
$deptStatusDetails[] = [
'dept_id' => $deptId,
'dept_name' => $deptInfo['dept_name'],
'statuses' => $deptInfo['statuses']
];
}
}
// 格式化渠道状态明细
$channelStatusDetails = [];
if (isset($channelStatusMap[$doctor['id']])) {
$channelStatusDetails = $channelStatusMap[$doctor['id']];
}
$result[] = [
'doctor_id' => $doctor['id'],
'doctor_name' => $doctor['name'],
'total_count' => (int)$stat['total_count'] ?? 0,
'registered_count' => (int)$stat['registered_count'] ?? 0,
'completed_count' => (int)$stat['completed_count'] ?? 0,
'missed_count' => (int)$stat['missed_count'] ?? 0,
'cancelled_count' => (int)$stat['cancelled_count'] ?? 0,
'diagnosis_count' => $diagnosisCount ?? 0,
'deal_count' => $dealCount ?? 0,
'deal_rate' => $dealRate ?? 0,
'completion_rate' => $completionRate ?? 0,
'dept_status_details' => $deptStatusDetails,
'channel_status_details' => $channelStatusDetails,
'time_range' => $timeRangeText
];
}
return $result;
}
/**
* @notes 获取时间范围
* @param string $timeType
* @param string $customStartDate
* @param string $customEndDate
* @return array
*/
private function getTimeRange($timeType, $customStartDate, $customEndDate)
{
$today = date('Y-m-d');
switch ($timeType) {
case 'today':
$startDate = $today;
$endDate = $today;
$timeRangeText = '今天 (' . $today . ')';
break;
case 'week':
$startDate = date('Y-m-d', strtotime('-6 days'));
$endDate = $today;
$timeRangeText = '最近7天 (' . $startDate . ' 至 ' . $endDate . ')';
break;
case 'month':
$startDate = date('Y-m-d', strtotime('-29 days'));
$endDate = $today;
$timeRangeText = '最近30天 (' . $startDate . ' 至 ' . $endDate . ')';
break;
case 'custom':
if ($customStartDate && $customEndDate) {
$startDate = $customStartDate;
$endDate = $customEndDate;
$timeRangeText = '自定义 (' . $startDate . ' 至 ' . $endDate . ')';
} else {
$startDate = $today;
$endDate = $today;
$timeRangeText = '今天 (' . $today . ')';
}
break;
default:
$startDate = $today;
$endDate = $today;
$timeRangeText = '今天 (' . $today . ')';
}
return [$startDate, $endDate, $timeRangeText];
}
/**
* @notes 获取数量
* @return int
*/
public function count(): int
{
return count($this->lists());
}
}