Files
zyt/server/app/adminapi/lists/firstvisit/MyPatientProgressLists.php
T
2026-09-09 15:47:48 +08:00

705 lines
29 KiB
PHP

<?php
declare(strict_types=1);
namespace app\adminapi\lists\firstvisit;
use app\adminapi\lists\BaseAdminDataLists;
use app\adminapi\logic\firstvisit\MyPatientLogic;
use app\common\enum\AppointmentTypeEnum;
use app\common\lists\ListsExtendInterface;
use app\common\lists\ListsSearchInterface;
use app\common\model\DiagnosisViewRecord;
use app\common\model\auth\Admin;
use app\common\model\auth\AdminRole;
use app\common\model\doctor\Appointment;
use app\common\model\doctor\Roster;
use app\common\model\tcm\Prescription;
use app\common\service\doctor\RosterSegmentService;
use think\db\Query;
use think\facade\Db;
/**
* “我的患者”内嵌面诊进度。
*
* 一条挂号一行;只返回脱敏患者信息,并严格复用 MyPatientLogic 的患者级范围。
*/
class MyPatientProgressLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
{
private const EFFECTIVE_STATUSES = [1, 3, 4];
private const AVG_MINUTES_PER_VISIT = 15;
public function setSearch(): array
{
return [];
}
public function lists(): array
{
$rows = $this->buildQuery(true)
->field([
'a.id', 'a.patient_id AS diagnosis_id', 'a.doctor_id', 'a.appointment_date',
'a.appointment_time', 'a.appointment_type', 'a.status', 'a.create_time',
'd.patient_id AS source_patient_id', 'd.patient_name', 'd.phone', 'd.gender', 'd.age',
'd.assistant_id', 'doctor_admin.name AS doctor_name', 'assistant_admin.name AS assistant_name',
])
->order('a.appointment_date', 'asc')
->order('a.appointment_time', 'asc')
->order('a.id', 'asc')
->limit($this->limitOffset, $this->limitLength)
->select()
->toArray();
return $this->appendProgress($rows);
}
public function count(): int
{
return (int) $this->buildQuery(true)->count('a.id');
}
public function extend(): array
{
$query = $this->buildQuery(false);
[$startDate, $endDate] = $this->dateRange();
$summary = [
'total' => (int) (clone $query)->count('a.id'),
'booked' => (int) (clone $query)->where('a.status', 1)->count('a.id'),
'completed' => (int) (clone $query)->where('a.status', 3)->count('a.id'),
'missed' => (int) (clone $query)->where('a.status', 4)->count('a.id'),
];
$scheduleMode = $this->usesOwnershipSchedule() ? 'ownership' : 'roster';
$weekSchedule = $scheduleMode === 'ownership' ? $this->ownershipWeekSchedule() : $this->weekSchedule();
$todaySchedule = $weekSchedule[0] ?? $this->emptyScheduleDay(date('Y-m-d'));
$todayOverview = $scheduleMode === 'ownership'
? [
'total_visits' => (int) ($todaySchedule['total_appointments'] ?? 0),
'booked' => (int) ($todaySchedule['waiting_appointments'] ?? 0),
'completed' => (int) ($todaySchedule['completed_appointments'] ?? 0),
'missed' => (int) ($todaySchedule['missed_appointments'] ?? 0),
'empty_slots' => 0,
'passed_slots' => 0,
'remaining_slots' => 0,
'doctor_count' => (int) ($todaySchedule['doctor_count'] ?? 0),
]
: [
'total_visits' => (int) ($todaySchedule['total_slots'] ?? 0),
'booked' => (int) ($todaySchedule['booked_slots'] ?? 0),
'completed' => 0,
'missed' => 0,
// 空号口径:剩余可预约号源(未过时刻且未被有效挂号占用),过号单独给出
'empty_slots' => (int) ($todaySchedule['remaining_slots'] ?? 0),
'passed_slots' => (int) ($todaySchedule['passed_slots'] ?? 0),
'remaining_slots' => (int) ($todaySchedule['remaining_slots'] ?? 0),
'doctor_count' => (int) ($todaySchedule['doctor_count'] ?? 0),
];
return [
'summary' => $summary,
'schedule_mode' => $scheduleMode,
'today_overview' => $todayOverview,
'week_schedule' => $weekSchedule,
'scope' => MyPatientLogic::scopeMeta($this->adminId, $this->adminInfo),
'dates' => ['start' => $startDate, 'end' => $endDate],
];
}
private function buildQuery(bool $applyStatus): Query
{
$query = Appointment::alias('a')
->join('tcm_diagnosis d', 'a.patient_id = d.id')
->leftJoin('admin doctor_admin', 'a.doctor_id = doctor_admin.id')
->leftJoin('admin assistant_admin', 'CAST(d.assistant_id AS UNSIGNED) = assistant_admin.id')
->whereNull('d.delete_time')
->where('d.status', 1);
MyPatientLogic::applyScope($query, $this->adminId, $this->adminInfo);
$this->applyKeyword($query);
$this->applyDateFilter($query);
if ($applyStatus) {
$status = $this->params['status'] ?? '';
if ($status !== '' && $status !== null && in_array((int) $status, self::EFFECTIVE_STATUSES, true)) {
$query->where('a.status', (int) $status);
} else {
$query->whereIn('a.status', self::EFFECTIVE_STATUSES);
}
} else {
$query->whereIn('a.status', self::EFFECTIVE_STATUSES);
}
return $query;
}
private function applyKeyword(Query $query): void
{
$keyword = trim((string) ($this->params['keyword'] ?? ''));
if ($keyword === '') {
return;
}
$query->where(function ($q) use ($keyword) {
$like = '%' . $keyword . '%';
$q->whereLike('d.patient_name', $like)
->whereOr('d.phone', 'like', $like)
->whereOr('doctor_admin.name', 'like', $like)
->whereOr('assistant_admin.name', 'like', $like);
if (preg_match('/^\d+$/', $keyword)) {
$id = (int) $keyword;
if ($id > 0) {
$q->whereOr('a.id', $id)->whereOr('d.id', $id);
}
}
});
}
private function applyDateFilter(Query $query): void
{
[$startDate, $endDate] = $this->dateRange();
$query->whereBetween('a.appointment_date', [$startDate, $endDate]);
}
/** @return array{0:string,1:string} */
private function dateRange(): array
{
$today = date('Y-m-d');
$startDate = $this->normalizeDate($this->params['start_date'] ?? '') ?: $today;
$endDate = $this->normalizeDate($this->params['end_date'] ?? '') ?: $startDate;
if ($startDate > $endDate) {
[$startDate, $endDate] = [$endDate, $startDate];
}
$startTs = strtotime($startDate);
$endTs = strtotime($endDate);
if ($startTs !== false && $endTs !== false && $endTs - $startTs > 31 * 86400) {
$endDate = date('Y-m-d', $startTs + 31 * 86400);
}
return [$startDate, $endDate];
}
private function normalizeDate($value): string
{
$value = trim((string) $value);
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) ? $value : '';
}
/**
* @param array<int, array<string, mixed>> $rows
* @return array<int, array<string, mixed>>
*/
private function appendProgress(array $rows): array
{
if ($rows === []) {
return [];
}
$diagnosisIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'diagnosis_id')))));
$appointmentIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'id')))));
$queuePositionMap = $this->queuePositionMap($rows);
$confirmedSet = [];
if ($diagnosisIds !== []) {
$viewTable = (new DiagnosisViewRecord())->getTable();
$confirmedIds = Db::table($viewTable)
->whereIn('diagnosis_id', $diagnosisIds)
->where('is_confirmed', 1)
->whereNull('delete_time')
->column('diagnosis_id');
$confirmedSet = array_fill_keys(array_map('intval', $confirmedIds), true);
}
$prescriptionMap = [];
if ($appointmentIds !== []) {
$prescriptions = Prescription::whereIn('appointment_id', $appointmentIds)
->whereNull('delete_time')
->where('void_status', 0)
->field(['id', 'appointment_id', 'audit_status', 'is_system_auto'])
->order('id', 'desc')
->select()
->toArray();
foreach ($prescriptions as $prescription) {
$appointmentId = (int) ($prescription['appointment_id'] ?? 0);
if ($appointmentId > 0 && !isset($prescriptionMap[$appointmentId])) {
$prescriptionMap[$appointmentId] = $prescription;
}
}
}
foreach ($rows as &$row) {
$appointmentId = (int) ($row['id'] ?? 0);
$diagnosisId = (int) ($row['diagnosis_id'] ?? 0);
$status = (int) ($row['status'] ?? 0);
$prescription = $prescriptionMap[$appointmentId] ?? [];
$confirmed = isset($confirmedSet[$diagnosisId]);
$prescribed = $prescription !== [];
$aheadCount = $status === 1 ? (int) ($queuePositionMap[$appointmentId] ?? 0) : 0;
$row['phone_masked'] = $this->maskPhone((string) ($row['phone'] ?? ''));
unset($row['phone']);
$row['gender_desc'] = (int) ($row['gender'] ?? 0) === 1 ? '男' : '女';
$row['assistant_name'] = trim((string) ($row['assistant_name'] ?? '')) ?: '未分配';
$row['doctor_name'] = trim((string) ($row['doctor_name'] ?? '')) ?: '未知医生';
$row['appointment_time_text'] = $this->appointmentTimeText($row);
$row['status_text'] = $this->appointmentStatusText($status);
$row['appointment_type'] = AppointmentTypeEnum::normalizeStored($row['appointment_type'] ?? null);
$row['appointment_type_text'] = $this->appointmentTypeText($row['appointment_type']);
$row['registered'] = 1;
$row['diagnosis_confirmed'] = $confirmed ? 1 : 0;
$row['visit_completed'] = $status === 3 ? 1 : 0;
$row['has_prescription'] = $prescribed ? 1 : 0;
$row['prescription_id'] = (int) ($prescription['id'] ?? 0);
$row['prescription_audit_status'] = $prescribed ? (int) ($prescription['audit_status'] ?? 0) : -1;
$row['progress_text'] = $this->progressText($confirmed, $status === 3, $prescribed, $status);
$row['queue_no'] = $status === 1 ? $aheadCount + 1 : 0;
$row['ahead_count'] = $aheadCount;
$row['estimated_wait_minutes'] = $aheadCount * self::AVG_MINUTES_PER_VISIT;
$row['queue_status'] = $this->queueStatus($status, $confirmed, $aheadCount);
$row['queue_status_text'] = $this->queueStatusText((string) $row['queue_status']);
$row['is_self_patient'] = (
(int) ($row['assistant_id'] ?? 0) === $this->adminId
|| (int) ($row['doctor_id'] ?? 0) === $this->adminId
) ? 1 : 0;
}
unset($row);
return $rows;
}
/**
* 候诊位次按 progress.vue 的真实规则计算:同医生、同日、待就诊,按预约时刻和挂号 ID 升序。
* 队列计算读取完整医生队列,只向当前范围列表返回人数,不暴露范围外患者身份。
*
* @param array<int,array<string,mixed>> $rows
* @return array<int,int>
*/
private function queuePositionMap(array $rows): array
{
$doctorIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'doctor_id')))));
$dates = array_values(array_unique(array_filter(array_map('strval', array_column($rows, 'appointment_date')))));
if ($doctorIds === [] || $dates === []) {
return [];
}
$queueRows = Appointment::whereIn('doctor_id', $doctorIds)
->whereIn('appointment_date', $dates)
->where('status', 1)
->field(['id', 'doctor_id', 'appointment_date', 'appointment_time'])
->order('doctor_id', 'asc')
->order('appointment_date', 'asc')
->order('appointment_time', 'asc')
->order('id', 'asc')
->select()
->toArray();
$groupCounts = [];
$positions = [];
foreach ($queueRows as $queueRow) {
$group = (int) ($queueRow['doctor_id'] ?? 0) . '|' . (string) ($queueRow['appointment_date'] ?? '');
$positions[(int) ($queueRow['id'] ?? 0)] = (int) ($groupCounts[$group] ?? 0);
$groupCounts[$group] = (int) ($groupCounts[$group] ?? 0) + 1;
}
return $positions;
}
/**
* 未来七天号源:完全复用 paiban/availableSlots 的生成口径,按医生+日期+时刻去重。
*
* @return array<int,array<string,mixed>>
*/
private function weekSchedule(): array
{
$startDate = date('Y-m-d');
$endDate = date('Y-m-d', strtotime($startDate . ' +6 days'));
$days = [];
for ($offset = 0; $offset < 7; $offset++) {
$date = date('Y-m-d', strtotime($startDate . " +{$offset} days"));
$days[$date] = $this->emptyScheduleDay($date);
}
$doctorIds = $this->visibleDoctorIds($startDate, $endDate);
if ($doctorIds === []) {
return array_values($days);
}
$rosters = Roster::whereIn('doctor_id', $doctorIds)
->whereBetween('date', [$startDate, $endDate])
->where('status', 1)
->whereNull('delete_time')
->field(['doctor_id', 'date', 'period', 'start_time', 'end_time', 'slot_minutes', 'quota'])
->select()
->toArray();
$doctorNames = Admin::whereIn('id', $doctorIds)->column('name', 'id');
$doctorSlotSets = [];
$doctorWindowSets = [];
foreach ($rosters as $roster) {
$date = (string) ($roster['date'] ?? '');
$doctorId = (int) ($roster['doctor_id'] ?? 0);
$window = RosterSegmentService::resolveWindow($roster);
if (!isset($days[$date]) || $doctorId <= 0 || $window === null) {
continue;
}
[$startTime, $endTime] = $window;
$times = RosterSegmentService::generateSlotTimes(
$startTime,
$endTime,
RosterSegmentService::normalizeSlotMinutes($roster['slot_minutes'] ?? 15)
);
$times = RosterSegmentService::applyQuotaCap($times, (int) ($roster['quota'] ?? 0));
foreach ($times as $time) {
$doctorSlotSets[$date][$doctorId][$time] = true;
}
$doctorWindowSets[$date][$doctorId][$startTime . '-' . $endTime] = true;
}
$appointments = Appointment::whereIn('doctor_id', $doctorIds)
->whereBetween('appointment_date', [$startDate, $endDate])
->where('status', 1)
->field(['doctor_id', 'appointment_date', 'appointment_time'])
->select()
->toArray();
$doctorBookedSets = [];
foreach ($appointments as $appointment) {
$date = (string) ($appointment['appointment_date'] ?? '');
$doctorId = (int) ($appointment['doctor_id'] ?? 0);
$time = substr((string) ($appointment['appointment_time'] ?? ''), 0, 5);
if (isset($doctorSlotSets[$date][$doctorId][$time])) {
$doctorBookedSets[$date][$doctorId][$time] = true;
}
}
$today = date('Y-m-d');
$nowHm = date('H:i');
foreach ($days as $date => &$day) {
$doctorDetails = [];
$total = 0;
$booked = 0;
$passed = 0;
$remaining = 0;
foreach ($doctorSlotSets[$date] ?? [] as $doctorId => $slotSet) {
$doctorTotal = count($slotSet);
$bookedSet = $doctorBookedSets[$date][$doctorId] ?? [];
$doctorBooked = count($bookedSet);
$doctorPassed = 0;
$doctorRemaining = 0;
foreach ($slotSet as $time => $_) {
if (isset($bookedSet[$time])) {
continue;
}
// 与挂号选号一致:今日已过(含当前分钟)的未约号源计为过号,其余为剩余可约
$isPassed = $date < $today || ($date === $today && strcmp((string) $time, $nowHm) <= 0);
if ($isPassed) {
$doctorPassed++;
} else {
$doctorRemaining++;
}
}
$total += $doctorTotal;
$booked += $doctorBooked;
$passed += $doctorPassed;
$remaining += $doctorRemaining;
$scheduleWindows = array_values(array_keys($doctorWindowSets[$date][$doctorId] ?? []));
sort($scheduleWindows, SORT_STRING);
$doctorDetails[] = [
'doctor_id' => (int) $doctorId,
'doctor_name' => trim((string) ($doctorNames[$doctorId] ?? '')) ?: '未知医生',
'schedule_windows' => $scheduleWindows,
'total_slots' => $doctorTotal,
'booked_slots' => $doctorBooked,
'passed_slots' => $doctorPassed,
'remaining_slots' => $doctorRemaining,
// 空号对外展示剩余可约;过号/未挂号细分见 passed/remaining
'empty_slots' => $doctorRemaining,
];
}
usort($doctorDetails, static function (array $left, array $right): int {
return $right['remaining_slots'] <=> $left['remaining_slots']
?: $right['booked_slots'] <=> $left['booked_slots']
?: $right['total_slots'] <=> $left['total_slots']
?: strcmp((string) $left['doctor_name'], (string) $right['doctor_name']);
});
$day['total_slots'] = $total;
$day['booked_slots'] = $booked;
$day['passed_slots'] = $passed;
$day['remaining_slots'] = $remaining;
$day['empty_slots'] = $remaining;
$day['doctor_count'] = count($doctorDetails);
$day['doctors'] = $doctorDetails;
}
unset($day);
return array_values($days);
}
/**
* 医助“本人归属”只统计其患者的真实挂号,不再把历史接诊医生的整周号源算到本人名下。
*
* @return array<int,array<string,mixed>>
*/
private function ownershipWeekSchedule(): array
{
$startDate = date('Y-m-d');
$endDate = date('Y-m-d', strtotime($startDate . ' +6 days'));
$days = [];
for ($offset = 0; $offset < 7; $offset++) {
$date = date('Y-m-d', strtotime($startDate . " +{$offset} days"));
$days[$date] = $this->emptyScheduleDay($date);
}
$query = Appointment::alias('ownership_a')
->join('tcm_diagnosis d', 'ownership_a.patient_id = d.id')
->leftJoin('admin ownership_doctor', 'ownership_a.doctor_id = ownership_doctor.id')
->whereNull('d.delete_time')
->where('d.status', 1)
->whereBetween('ownership_a.appointment_date', [$startDate, $endDate])
->whereIn('ownership_a.status', self::EFFECTIVE_STATUSES);
MyPatientLogic::applyScope($query, $this->adminId, $this->adminInfo);
$appointments = $query
->field([
'ownership_a.id', 'ownership_a.doctor_id', 'ownership_a.appointment_date',
'ownership_a.appointment_time', 'ownership_a.status',
'ownership_doctor.name AS doctor_name',
])
->order('ownership_a.appointment_date', 'asc')
->order('ownership_a.appointment_time', 'asc')
->order('ownership_a.id', 'asc')
->select()
->toArray();
$doctorDetails = [];
foreach ($appointments as $appointment) {
$date = (string) ($appointment['appointment_date'] ?? '');
$doctorId = (int) ($appointment['doctor_id'] ?? 0);
if (!isset($days[$date]) || $doctorId <= 0) {
continue;
}
if (!isset($doctorDetails[$date][$doctorId])) {
$doctorDetails[$date][$doctorId] = [
'doctor_id' => $doctorId,
'doctor_name' => trim((string) ($appointment['doctor_name'] ?? '')) ?: '未知医生',
'appointment_time_set' => [],
'total_appointments' => 0,
'waiting_appointments' => 0,
'completed_appointments' => 0,
'missed_appointments' => 0,
];
}
$time = substr((string) ($appointment['appointment_time'] ?? ''), 0, 5);
if ($time !== '') {
$doctorDetails[$date][$doctorId]['appointment_time_set'][$time] = true;
}
$status = (int) ($appointment['status'] ?? 0);
$doctorDetails[$date][$doctorId]['total_appointments']++;
if ($status === 1) {
$doctorDetails[$date][$doctorId]['waiting_appointments']++;
} elseif ($status === 3) {
$doctorDetails[$date][$doctorId]['completed_appointments']++;
} elseif ($status === 4) {
$doctorDetails[$date][$doctorId]['missed_appointments']++;
}
}
foreach ($days as $date => &$day) {
$rows = [];
foreach ($doctorDetails[$date] ?? [] as $doctor) {
$times = array_values(array_keys($doctor['appointment_time_set'] ?? []));
sort($times, SORT_STRING);
unset($doctor['appointment_time_set']);
$doctor['appointment_times'] = $times;
$rows[] = $doctor;
}
usort($rows, static function (array $left, array $right): int {
return $right['waiting_appointments'] <=> $left['waiting_appointments']
?: $right['total_appointments'] <=> $left['total_appointments']
?: strcmp((string) $left['doctor_name'], (string) $right['doctor_name']);
});
$day['total_appointments'] = array_sum(array_column($rows, 'total_appointments'));
$day['waiting_appointments'] = array_sum(array_column($rows, 'waiting_appointments'));
$day['completed_appointments'] = array_sum(array_column($rows, 'completed_appointments'));
$day['missed_appointments'] = array_sum(array_column($rows, 'missed_appointments'));
$day['doctor_count'] = count($rows);
$day['doctors'] = $rows;
}
unset($day);
return array_values($days);
}
private function usesOwnershipSchedule(): bool
{
if ((int) ($this->adminInfo['root'] ?? 0) === 1) {
return false;
}
$roleIds = $this->currentRoleIds();
return in_array(2, $roleIds, true) && array_intersect($roleIds, [3, 7, 8]) === [];
}
/** @return int[] */
private function visibleDoctorIds(string $startDate, string $endDate): array
{
if ((int) ($this->adminInfo['root'] ?? 0) === 1) {
$doctorIds = array_values(array_unique(array_map('intval', Roster::whereBetween('date', [$startDate, $endDate])
->where('status', 1)
->whereNull('delete_time')
->column('doctor_id'))));
return $this->activeDoctorIds($doctorIds);
}
$roleIds = $this->currentRoleIds();
$isTeamRole = array_intersect($roleIds, [3, 7, 8]) !== [];
$isDoctor = in_array(1, $roleIds, true);
$isAssistant = in_array(2, $roleIds, true);
// 纯医生账号的概览只统计本人排班,避免同一患者曾由其他医生接诊时放大到其他医生。
if (!$isTeamRole && $isDoctor && !$isAssistant) {
return $this->activeDoctorIds([$this->adminId]);
}
$query = Appointment::alias('scope_a')
->join('tcm_diagnosis d', 'scope_a.patient_id = d.id')
->whereNull('d.delete_time')
->where('d.status', 1)
->whereIn('scope_a.status', self::EFFECTIVE_STATUSES)
->where('scope_a.doctor_id', '>', 0);
MyPatientLogic::applyScope($query, $this->adminId, $this->adminInfo);
$doctorIds = array_values(array_unique(array_filter(array_map('intval', $query->distinct(true)->column('scope_a.doctor_id')))));
if (!$isTeamRole && $isDoctor) {
$doctorIds[] = $this->adminId;
}
return $this->activeDoctorIds(array_values(array_unique($doctorIds)));
}
/** @return int[] */
private function currentRoleIds(): array
{
return array_values(array_unique(array_filter(array_map('intval', AdminRole::where('admin_id', $this->adminId)->column('role_id')))));
}
/** @param int[] $doctorIds @return int[] */
private function activeDoctorIds(array $doctorIds): array
{
$doctorIds = array_values(array_unique(array_filter(array_map('intval', $doctorIds))));
if ($doctorIds === []) {
return [];
}
$roleDoctorIds = array_values(array_unique(array_map('intval', AdminRole::whereIn('admin_id', $doctorIds)
->where('role_id', 1)
->column('admin_id'))));
if ($roleDoctorIds === []) {
return [];
}
$activeSet = array_fill_keys(array_map('intval', Admin::whereIn('id', $roleDoctorIds)
->where('disable', 0)
->column('id')), true);
return array_values(array_filter($doctorIds, static function (int $doctorId) use ($activeSet): bool {
return isset($activeSet[$doctorId]);
}));
}
/** @return array<string,mixed> */
private function emptyScheduleDay(string $date): array
{
$weekdayLabels = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
$timestamp = strtotime($date) ?: time();
return [
'date' => $date,
'date_text' => date('m-d', $timestamp),
'weekday' => $weekdayLabels[(int) date('w', $timestamp)],
'total_slots' => 0,
'booked_slots' => 0,
'passed_slots' => 0,
'remaining_slots' => 0,
'empty_slots' => 0,
'doctor_count' => 0,
'doctors' => [],
'total_appointments' => 0,
'waiting_appointments' => 0,
'completed_appointments' => 0,
'missed_appointments' => 0,
];
}
private function queueStatus(int $status, bool $confirmed, int $aheadCount): string
{
if ($status === 3) {
return 'completed';
}
if ($status === 4) {
return 'missed';
}
if ($confirmed) {
return 'consulting';
}
return $aheadCount === 0 ? 'next' : 'waiting';
}
private function queueStatusText(string $status): string
{
return [
'completed' => '已完成',
'missed' => '已过号',
'consulting' => '就诊中',
'next' => '待确认',
'waiting' => '等待中',
][$status] ?? '等待中';
}
private function maskPhone(string $phone): string
{
return preg_replace('/^(\d{3})\d{4}(\d{4})$/', '$1****$2', $phone) ?: $phone;
}
private function appointmentTimeText(array $row): string
{
$time = trim((string) ($row['appointment_time'] ?? ''));
if (strlen($time) > 5) {
$time = substr($time, 0, 5);
}
return trim((string) ($row['appointment_date'] ?? '') . ' ' . $time);
}
private function appointmentStatusText(int $status): string
{
return [1 => '已挂号', 3 => '已完成', 4 => '已过号'][$status] ?? '未知';
}
private function appointmentTypeText(string $type): string
{
return AppointmentTypeEnum::description($type);
}
private function progressText(bool $confirmed, bool $completed, bool $prescribed, int $status): string
{
if ($status === 4) {
return '已过号';
}
if (!$confirmed) {
return '待确认诊单';
}
if (!$completed) {
return '待完诊';
}
return $prescribed ? '已开方' : '待开方';
}
}