368 lines
14 KiB
PHP
368 lines
14 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\lists\ListsExtendInterface;
|
|
use app\common\lists\ListsSearchInterface;
|
|
use app\common\model\DiagnosisViewRecord;
|
|
use app\common\model\auth\Admin;
|
|
use app\common\model\doctor\Appointment;
|
|
use app\common\model\tcm\Diagnosis;
|
|
use think\db\Query;
|
|
use think\facade\Db;
|
|
|
|
class MyPatientLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
|
{
|
|
private const EFFECTIVE_APPOINTMENT_STATUSES = [1, 3, 4];
|
|
|
|
public function setSearch(): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function lists(): array
|
|
{
|
|
$query = $this->buildQuery(true, true);
|
|
$appointmentTable = (new Appointment())->getTable();
|
|
$today = date('Y-m-d');
|
|
$upcomingSql = "SELECT MIN(CONCAT(sort_apt.appointment_date, ' ', IFNULL(NULLIF(TRIM(sort_apt.appointment_time), ''), '00:00:00')))"
|
|
. " FROM {$appointmentTable} sort_apt"
|
|
. ' WHERE sort_apt.patient_id = d.id'
|
|
. ' AND sort_apt.status IN (1,4)'
|
|
. " AND sort_apt.appointment_date >= '{$today}'";
|
|
|
|
$rows = $query
|
|
->field([
|
|
'd.id', 'd.patient_id', 'd.patient_name', 'd.phone', 'd.gender', 'd.age',
|
|
'd.diagnosis_date', 'd.diagnosis_type', 'd.syndrome_type', 'd.assistant_id',
|
|
'd.assign_read_at', 'd.create_time',
|
|
])
|
|
->orderRaw("CASE WHEN ({$upcomingSql}) IS NULL THEN 1 ELSE 0 END ASC")
|
|
->orderRaw("IFNULL(({$upcomingSql}), '9999-12-31 23:59:59') ASC")
|
|
->order('d.id', 'desc')
|
|
->limit($this->limitOffset, $this->limitLength)
|
|
->select()
|
|
->toArray();
|
|
|
|
return $this->appendRelations($rows);
|
|
}
|
|
|
|
public function count(): int
|
|
{
|
|
return (int) $this->buildQuery(true, true)->count('d.id');
|
|
}
|
|
|
|
public function extend(): array
|
|
{
|
|
$today = date('Y-m-d');
|
|
$tomorrow = date('Y-m-d', strtotime('+1 day'));
|
|
$dayAfter = date('Y-m-d', strtotime('+2 days'));
|
|
|
|
return [
|
|
'summary' => [
|
|
'today' => $this->countByAppointmentDate($today),
|
|
'tomorrow' => $this->countByAppointmentDate($tomorrow),
|
|
'day_after' => $this->countByAppointmentDate($dayAfter),
|
|
],
|
|
'scope' => MyPatientLogic::scopeMeta($this->adminId, $this->adminInfo),
|
|
'dates' => [
|
|
'today' => $today,
|
|
'tomorrow' => $tomorrow,
|
|
'day_after' => $dayAfter,
|
|
],
|
|
];
|
|
}
|
|
|
|
private function buildQuery(bool $applyStatusFilter, bool $applyDateFilter): Query
|
|
{
|
|
$diagnosisTable = (new Diagnosis())->getTable();
|
|
$query = Db::table($diagnosisTable)
|
|
->alias('d')
|
|
->whereNull('d.delete_time')
|
|
->where('d.status', 1);
|
|
|
|
MyPatientLogic::applyScope($query, $this->adminId, $this->adminInfo);
|
|
$this->applyKeyword($query);
|
|
|
|
$statusFilter = $applyStatusFilter ? trim((string) ($this->params['status_filter'] ?? '')) : '';
|
|
if ($statusFilter === 'unconfirmed') {
|
|
$viewTable = (new DiagnosisViewRecord())->getTable();
|
|
$query->whereNotExists(
|
|
"SELECT 1 FROM {$viewTable} confirm_row"
|
|
. ' WHERE confirm_row.diagnosis_id = d.id'
|
|
. ' AND confirm_row.is_confirmed = 1'
|
|
. ' AND confirm_row.delete_time IS NULL'
|
|
);
|
|
}
|
|
|
|
$appointmentStatuses = self::EFFECTIVE_APPOINTMENT_STATUSES;
|
|
if ($statusFilter === 'booked') {
|
|
$appointmentStatuses = [1];
|
|
} elseif ($statusFilter === 'completed') {
|
|
$appointmentStatuses = [3];
|
|
} elseif ($statusFilter === 'missed') {
|
|
$appointmentStatuses = [4];
|
|
}
|
|
|
|
$needsAppointmentFilter = in_array($statusFilter, ['booked', 'completed', 'missed'], true);
|
|
[$startDate, $endDate] = $applyDateFilter ? $this->dateRange() : ['', ''];
|
|
if ($startDate !== '' || $endDate !== '') {
|
|
$needsAppointmentFilter = true;
|
|
}
|
|
|
|
if ($needsAppointmentFilter) {
|
|
$appointmentTable = (new Appointment())->getTable();
|
|
$conditions = [
|
|
'filter_apt.patient_id = d.id',
|
|
'filter_apt.status IN (' . implode(',', $appointmentStatuses) . ')',
|
|
];
|
|
if ($startDate !== '') {
|
|
$conditions[] = "filter_apt.appointment_date >= '{$startDate}'";
|
|
}
|
|
if ($endDate !== '') {
|
|
$conditions[] = "filter_apt.appointment_date <= '{$endDate}'";
|
|
}
|
|
$query->whereExists(
|
|
"SELECT 1 FROM {$appointmentTable} filter_apt WHERE " . implode(' AND ', $conditions)
|
|
);
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
|
|
private function applyKeyword(Query $query): void
|
|
{
|
|
$keyword = trim((string) ($this->params['keyword'] ?? ''));
|
|
if ($keyword === '') {
|
|
return;
|
|
}
|
|
|
|
$needle = addslashes($keyword);
|
|
$adminTable = (new Admin())->getTable();
|
|
$appointmentTable = (new Appointment())->getTable();
|
|
$query->whereRaw(
|
|
"(d.patient_name LIKE '%{$needle}%'"
|
|
. " OR d.phone LIKE '%{$needle}%'"
|
|
. " OR EXISTS (SELECT 1 FROM {$adminTable} assistant_admin"
|
|
. ' WHERE assistant_admin.id = CAST(d.assistant_id AS UNSIGNED)'
|
|
. ' AND assistant_admin.delete_time IS NULL'
|
|
. " AND assistant_admin.name LIKE '%{$needle}%')"
|
|
. " OR EXISTS (SELECT 1 FROM {$appointmentTable} keyword_apt"
|
|
. " INNER JOIN {$adminTable} doctor_admin ON doctor_admin.id = keyword_apt.doctor_id"
|
|
. ' AND doctor_admin.delete_time IS NULL'
|
|
. ' WHERE keyword_apt.patient_id = d.id'
|
|
. ' AND keyword_apt.status IN (1,3,4)'
|
|
. " AND doctor_admin.name LIKE '%{$needle}%'))"
|
|
);
|
|
}
|
|
|
|
/** @return array{0:string,1:string} */
|
|
private function dateRange(): array
|
|
{
|
|
$startDate = $this->normalizeDate($this->params['start_date'] ?? '');
|
|
$endDate = $this->normalizeDate($this->params['end_date'] ?? '');
|
|
if ($startDate === '' && $endDate !== '') {
|
|
$startDate = $endDate;
|
|
}
|
|
if ($endDate === '' && $startDate !== '') {
|
|
$endDate = $startDate;
|
|
}
|
|
if ($startDate !== '' && $endDate !== '' && $startDate > $endDate) {
|
|
[$startDate, $endDate] = [$endDate, $startDate];
|
|
}
|
|
|
|
return [$startDate, $endDate];
|
|
}
|
|
|
|
private function normalizeDate($value): string
|
|
{
|
|
$value = trim((string) $value);
|
|
|
|
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) ? $value : '';
|
|
}
|
|
|
|
private function countByAppointmentDate(string $date): int
|
|
{
|
|
$query = $this->buildQuery(false, false);
|
|
$appointmentTable = (new Appointment())->getTable();
|
|
$query->whereExists(
|
|
"SELECT 1 FROM {$appointmentTable} summary_apt"
|
|
. ' WHERE summary_apt.patient_id = d.id'
|
|
. " AND summary_apt.appointment_date = '{$date}'"
|
|
. ' AND summary_apt.status IN (1,3,4)'
|
|
);
|
|
|
|
return (int) $query->count('d.id');
|
|
}
|
|
|
|
/**
|
|
* @param array<int, array<string, mixed>> $rows
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function appendRelations(array $rows): array
|
|
{
|
|
if ($rows === []) {
|
|
return [];
|
|
}
|
|
|
|
$diagnosisIds = array_values(array_unique(array_map('intval', array_column($rows, 'id'))));
|
|
$assistantIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'assistant_id')))));
|
|
$appointmentTable = (new Appointment())->getTable();
|
|
$appointments = Db::table($appointmentTable)
|
|
->whereIn('patient_id', $diagnosisIds)
|
|
->whereIn('status', self::EFFECTIVE_APPOINTMENT_STATUSES)
|
|
->field(['id', 'patient_id', 'doctor_id', 'appointment_date', 'appointment_time', 'status'])
|
|
->order('appointment_date', 'asc')
|
|
->order('appointment_time', 'asc')
|
|
->order('id', 'asc')
|
|
->select()
|
|
->toArray();
|
|
|
|
$doctorIds = array_values(array_unique(array_filter(array_map('intval', array_column($appointments, 'doctor_id')))));
|
|
$adminIds = array_values(array_unique(array_merge($assistantIds, $doctorIds)));
|
|
$adminNames = $adminIds === [] ? [] : Admin::whereIn('id', $adminIds)->whereNull('delete_time')->column('name', 'id');
|
|
|
|
$appointmentMap = [];
|
|
foreach ($appointments as $appointment) {
|
|
$diagnosisId = (int) ($appointment['patient_id'] ?? 0);
|
|
if ($diagnosisId > 0) {
|
|
$appointmentMap[$diagnosisId][] = $appointment;
|
|
}
|
|
}
|
|
|
|
$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);
|
|
[$rangeStart, $rangeEnd] = $this->dateRange();
|
|
$today = date('Y-m-d');
|
|
$statusFilter = trim((string) ($this->params['status_filter'] ?? ''));
|
|
$preferredStatuses = [
|
|
'booked' => [1],
|
|
'completed' => [3],
|
|
'missed' => [4],
|
|
][$statusFilter] ?? [];
|
|
|
|
foreach ($rows as &$row) {
|
|
$diagnosisId = (int) $row['id'];
|
|
$rowAppointments = $appointmentMap[$diagnosisId] ?? [];
|
|
$primary = $this->pickPrimaryAppointment(
|
|
$rowAppointments,
|
|
$rangeStart,
|
|
$rangeEnd,
|
|
$today,
|
|
$preferredStatuses
|
|
);
|
|
$completedCount = count(array_filter($rowAppointments, static function (array $appointment): bool {
|
|
return (int) ($appointment['status'] ?? 0) === 3;
|
|
}));
|
|
$assistantId = (int) ($row['assistant_id'] ?? 0);
|
|
|
|
$row['diagnosis_id'] = $diagnosisId;
|
|
$row['source_patient_id'] = (int) ($row['patient_id'] ?? 0);
|
|
$row['phone_masked'] = $this->maskPhone((string) ($row['phone'] ?? ''));
|
|
unset($row['phone']);
|
|
$row['gender_desc'] = (int) ($row['gender'] ?? 0) === 1 ? '男' : '女';
|
|
$row['diagnosis_date_text'] = $this->formatDiagnosisDate($row['diagnosis_date'] ?? '');
|
|
$row['assistant_name'] = (string) ($adminNames[$assistantId] ?? '未分配');
|
|
$row['confirmed'] = isset($confirmedSet[$diagnosisId]) ? 1 : 0;
|
|
$row['confirmation_text'] = $row['confirmed'] ? '已确认' : '待确认';
|
|
$row['visit_count'] = $completedCount;
|
|
$row['revisit_count'] = max(0, $completedCount - 1);
|
|
$row['appointment_id'] = $primary ? (int) $primary['id'] : 0;
|
|
$row['appointment_status'] = $primary ? (int) $primary['status'] : 0;
|
|
$row['appointment_status_text'] = $this->appointmentStatusText((int) ($primary['status'] ?? 0));
|
|
$row['appointment_doctor_id'] = $primary ? (int) $primary['doctor_id'] : 0;
|
|
$row['appointment_doctor_name'] = $primary
|
|
? (string) ($adminNames[(int) $primary['doctor_id']] ?? '未知医生')
|
|
: '未预约';
|
|
$row['appointment_time_text'] = $primary ? $this->appointmentTimeText($primary) : '';
|
|
$row['has_appointment'] = $primary !== null ? 1 : 0;
|
|
}
|
|
unset($row);
|
|
|
|
return $rows;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, array<string, mixed>> $appointments
|
|
* @return array<string, mixed>|null
|
|
*/
|
|
private function pickPrimaryAppointment(
|
|
array $appointments,
|
|
string $rangeStart,
|
|
string $rangeEnd,
|
|
string $today,
|
|
array $preferredStatuses = []
|
|
): ?array
|
|
{
|
|
if ($appointments === []) {
|
|
return null;
|
|
}
|
|
|
|
$candidates = $appointments;
|
|
if ($preferredStatuses !== []) {
|
|
$candidates = array_values(array_filter($candidates, static function (array $appointment) use ($preferredStatuses): bool {
|
|
return in_array((int) ($appointment['status'] ?? 0), $preferredStatuses, true);
|
|
}));
|
|
}
|
|
if ($rangeStart !== '' || $rangeEnd !== '') {
|
|
$candidates = array_values(array_filter($candidates, static function (array $appointment) use ($rangeStart, $rangeEnd): bool {
|
|
$date = (string) ($appointment['appointment_date'] ?? '');
|
|
|
|
return ($rangeStart === '' || $date >= $rangeStart) && ($rangeEnd === '' || $date <= $rangeEnd);
|
|
}));
|
|
}
|
|
if ($candidates === []) {
|
|
$candidates = $appointments;
|
|
}
|
|
|
|
foreach ($candidates as $appointment) {
|
|
$status = (int) ($appointment['status'] ?? 0);
|
|
$date = (string) ($appointment['appointment_date'] ?? '');
|
|
if (in_array($status, [1, 4], true) && $date >= $today) {
|
|
return $appointment;
|
|
}
|
|
}
|
|
|
|
return $candidates[count($candidates) - 1] ?? null;
|
|
}
|
|
|
|
private function maskPhone(string $phone): string
|
|
{
|
|
return preg_replace('/^(\d{3})\d{4}(\d{4})$/', '$1****$2', $phone) ?: $phone;
|
|
}
|
|
|
|
private function formatDiagnosisDate($value): string
|
|
{
|
|
if (is_numeric($value)) {
|
|
return (int) $value > 0 ? date('Y-m-d', (int) $value) : '';
|
|
}
|
|
$text = trim((string) $value);
|
|
|
|
return $text === '' ? '' : substr($text, 0, 10);
|
|
}
|
|
|
|
private function appointmentTimeText(array $appointment): string
|
|
{
|
|
$time = trim((string) ($appointment['appointment_time'] ?? ''));
|
|
if (strlen($time) > 5) {
|
|
$time = substr($time, 0, 5);
|
|
}
|
|
|
|
return trim((string) ($appointment['appointment_date'] ?? '') . ' ' . $time);
|
|
}
|
|
|
|
private function appointmentStatusText(int $status): string
|
|
{
|
|
return [1 => '已挂号', 3 => '已完成', 4 => '已过号'][$status] ?? '未挂号';
|
|
}
|
|
}
|