Files
zyt/server/app/adminapi/lists/tcm/DiagnosisLists.php
T
2026-05-18 17:43:25 +08:00

723 lines
34 KiB
PHP
Executable File
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
// +----------------------------------------------------------------------
// | likeadmin快速开发前后端分离管理后台(PHP版)
// +----------------------------------------------------------------------
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
// | 开源版本可自由商用,可去除界面版权logo
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
// | github下载:https://github.com/likeshop-github/likeadmin
// | 访问官网:https://www.likeadmin.cn
// | likeadmin团队 版权所有 拥有最终解释权
// +----------------------------------------------------------------------
// | author: likeadminTeam
// +----------------------------------------------------------------------
namespace app\adminapi\lists\tcm;
use app\adminapi\lists\BaseAdminDataLists;
use app\adminapi\logic\dept\DeptLogic;
use app\common\model\tcm\Diagnosis;
use app\common\model\tcm\BloodRecord;
use app\common\model\tcm\DietRecord;
use app\common\model\tcm\ExerciseRecord;
use app\common\model\DiagnosisViewRecord;
use app\common\model\Order;
use app\common\model\doctor\Appointment;
use app\common\model\tcm\Prescription;
use app\common\model\tcm\CallRecord;
use app\common\model\auth\Admin;
use app\common\model\auth\AdminDept;
use app\common\model\auth\AdminRole;
use app\common\lists\ListsSearchInterface;
use app\common\lists\Traits\HasDataScopeFilter;
/**
* 中医辨房病因诊单列表
* Class DiagnosisLists
* @package app\adminapi\lists\tcm
*/
class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
{
use HasDataScopeFilter;
/**
* @notes 设置搜索条件
* @return array
*/
public function setSearch(): array
{
return [
'%like%' => ['patient_name'],
'=' => ['patient_id', 'gender', 'diagnosis_type', 'syndrome_type', 'assistant_id', 'status'],
'between_time' => ['diagnosis_date'],
];
}
/**
* @notes 获取列表
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public function lists(): array
{
// 获取当前管理员的角色ID
$roleIds = AdminRole::where('admin_id', $this->adminId)->column('role_id');
// 待分配医助筛选:assistant_id 为空或 0(放在最前面判断,便于跳过医助角色的自我过滤)
$pendingAssign = isset($this->params['pending_assign']) && (string) $this->params['pending_assign'] === '1';
$pendingWideSearch = $this->isPendingAssignWideKeywordSearchActive();
// 仅「待分配列表」收窄到未分配医助;宽搜不套此池,与「全部」一致可命中已分配医助的诊单
$narrowPendingAssignPool = $pendingAssign && !$pendingWideSearch;
// 构建查询(待分配+关键词检索时不带诊单启用/禁用 status 条件,并弱化挂号/确认等 Tab 残留参数)
$baseWhere = $pendingWideSearch
? $this->searchWhereWithoutDiagnosisStatus($this->searchWhere)
: $this->searchWhere;
$query = Diagnosis::where($baseWhere);
// 医助角色(role_id=2):仅在普通列表(非待分配、非待分配宽搜)下限制为本人诊单;待分配窄列表与宽搜均不设此限
if (in_array(2, $roleIds) && !$pendingAssign && !$pendingWideSearch) {
$query->where('assistant_id', $this->adminId);
}
// 数据权限:待分配(窄列表与宽搜)均不按 assistant 范围过滤;宽搜额外豁免以便全库关键词命中(与即时需求对齐)
if (!$pendingAssign && !$pendingWideSearch) {
$this->applyDataScopeByOwner($query, 'assistant_id');
}
// 关键字:待分配 Tab 用多字段宽搜;其它 Tab 仍为姓名/手机。
$this->applyDiagnosisListKeywordFilter($query, $pendingAssign);
// 待分配医助列表:assistant_id 为 NULL 或 0(宽搜不加此条件)
if ($narrowPendingAssignPool) {
$query->whereRaw('(assistant_id IS NULL OR assistant_id = 0)');
}
// 是否确认诊单:1=已确认 0=未确认(待分配+关键词检索时不限制确认态,避免搜不到)
if (!$pendingWideSearch && isset($this->params['diagnosis_confirmed']) && $this->params['diagnosis_confirmed'] !== '') {
$confirmed = (int)$this->params['diagnosis_confirmed'];
$dvrTbl = (new DiagnosisViewRecord())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$subSql = "SELECT 1 FROM {$dvrTbl} dvr WHERE dvr.diagnosis_id = {$diagTbl}.id AND dvr.is_confirmed = 1 AND dvr.delete_time IS NULL";
if ($confirmed === 1) {
$query->whereExists($subSql);
} else {
$query->whereNotExists($subSql);
}
}
// 挂号日期筛选:当天/明天/后天…(待分配+关键词检索时不限制日期)
if (!$pendingWideSearch && !empty($this->params['appointment_date'])) {
$aptDate = addslashes($this->params['appointment_date']);
$aptTbl = (new Appointment())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$query->whereExists("SELECT 1 FROM {$aptTbl} apt WHERE apt.patient_id = {$diagTbl}.id AND apt.appointment_date = '{$aptDate}' AND apt.status IN (1,3,4)");
}
// 挂号状态:1=已挂号 0=未挂号(待分配+关键词检索时不限制)
if (!$pendingWideSearch && isset($this->params['has_appointment']) && $this->params['has_appointment'] !== '') {
$aptTbl = (new Appointment())->getTable();
$diagTbl = (new Diagnosis())->getTable();
if ((int)$this->params['has_appointment'] === 1) {
$query->whereExists("SELECT 1 FROM {$aptTbl} apt WHERE apt.patient_id = {$diagTbl}.id AND apt.status IN (1,3,4)");
} else {
$query->whereNotExists("SELECT 1 FROM {$aptTbl} apt WHERE apt.patient_id = {$diagTbl}.id AND apt.status IN (1,3,4)");
}
}
// 至少有一条挂号为「已完成」(status=3)(待分配+关键词检索时不限制)
if (!$pendingWideSearch && isset($this->params['completed_appointment']) && (string) $this->params['completed_appointment'] === '1') {
$aptTbl = (new Appointment())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$query->whereExists("SELECT 1 FROM {$aptTbl} apt WHERE apt.patient_id = {$diagTbl}.id AND apt.status = 3");
}
$this->applyPendingAssignBusinessOrderMonthFilter($query);
// 仅已开方(待分配+关键词检索时不限制)
if (!$pendingWideSearch && isset($this->params['only_has_prescription']) && (string) $this->params['only_has_prescription'] === '1') {
$rxTbl = (new Prescription())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$query->whereExists("SELECT 1 FROM {$rxTbl} rx WHERE rx.diagnosis_id = {$diagTbl}.id AND rx.delete_time IS NULL");
}
// 医助所属部门(待分配+关键词检索时不按部门收窄)
if (!$pendingWideSearch && isset($this->params['assistant_dept_id']) && $this->params['assistant_dept_id'] !== '' && (int) $this->params['assistant_dept_id'] > 0) {
$rootDeptId = (int) $this->params['assistant_dept_id'];
$deptIds = DeptLogic::getSelfAndDescendantIds($rootDeptId);
$deptIds = array_values(array_filter(array_map('intval', $deptIds), static function (int $id): bool {
return $id > 0;
}));
if ($deptIds === []) {
$query->whereRaw('0 = 1');
} else {
$inList = implode(',', $deptIds);
$adTbl = (new AdminDept())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$query->whereExists("SELECT 1 FROM {$adTbl} ad WHERE ad.admin_id = {$diagTbl}.assistant_id AND ad.dept_id IN ({$inList})");
}
}
// 按挂号状态优先级排序:已过号(4) > 已预约(1) > 已完成(3),然后按挂号日期+时间升序
// 若传了 appointment_date(当天/明天等筛选),只按「该日」的挂号排序与展示
$diagTbl = (new Diagnosis())->getTable();
$aptTbl = (new Appointment())->getTable();
$minAptDateCond = '';
if (!$pendingWideSearch && !empty($this->params['appointment_date'])) {
$sortAptDate = addslashes((string) $this->params['appointment_date']);
$minAptDateCond = " AND apt.appointment_date = '{$sortAptDate}'";
}
// 获取最早的挂号状态(用于排序优先级)
$minAptStatusExpr = '(SELECT apt.status FROM ' . $aptTbl . ' apt WHERE apt.patient_id = ' . $diagTbl . '.id AND apt.status IN (1,3,4)' . $minAptDateCond . ' ORDER BY CASE apt.status WHEN 4 THEN 1 WHEN 1 THEN 2 WHEN 3 THEN 3 ELSE 4 END, apt.appointment_date ASC, apt.appointment_time ASC LIMIT 1)';
// 获取最早的挂号时间(用于同状态内排序)
$minAptExpr = '(SELECT MIN(CONCAT(apt.appointment_date, \' \', IFNULL(NULLIF(TRIM(apt.appointment_time), \'\'), \'00:00:00\'))) FROM ' . $aptTbl . ' apt WHERE apt.patient_id = ' . $diagTbl . '.id AND apt.status IN (1,3,4)' . $minAptDateCond . ')';
// 「已完成」Tab:按最近一条「已完成」(status=3) 挂号日期+时间降序…
$isCompletedTab = !$pendingWideSearch && isset($this->params['completed_appointment']) && (string) $this->params['completed_appointment'] === '1';
if ($isCompletedTab) {
$maxCompletedAptExpr = '(SELECT MAX(CONCAT(apt.appointment_date, \' \', IFNULL(NULLIF(TRIM(apt.appointment_time), \'\'), \'00:00:00\'))) FROM ' . $aptTbl . ' apt WHERE apt.patient_id = ' . $diagTbl . '.id AND apt.status = 3' . $minAptDateCond . ')';
$orderRaw = $diagTbl . '.assign_read_at IS NULL DESC, IFNULL(' . $maxCompletedAptExpr . ", '1970-01-01 00:00:00') DESC, {$diagTbl}.id DESC";
} else {
$orderRaw = $diagTbl . '.assign_read_at IS NULL DESC, CASE IFNULL(' . $minAptStatusExpr . ', 999) WHEN 4 THEN 1 WHEN 1 THEN 2 WHEN 3 THEN 3 ELSE 4 END ASC, IFNULL(' . $minAptExpr . ", '9999-12-31 23:59:59') ASC, {$diagTbl}.id DESC";
}
$lists = $query
->with(['DiagnosisViewRecord'])
->field(['id', 'patient_id', 'patient_name', 'id_card', 'phone', 'gender', 'age', 'diagnosis_date', 'diagnosis_type', 'syndrome_type', 'assistant_id', 'assign_read_at', 'status', 'create_time', 'update_time'])
->append(['gender_desc', 'status_desc', 'diagnosis_date_text'])
->orderRaw($orderRaw)
->limit($this->limitOffset, $this->limitLength)
->select()
->toArray();
// 关联挂号:doctor_appointment.patient_id 存诊单主键 id(与 Appointment 列表 join 一致)
$diagnosisIds = array_filter(array_unique(array_column($lists, 'id')));
if (!empty($diagnosisIds)) {
$appointmentMap = [];
$subQuery = Appointment::where('patient_id', 'in', $diagnosisIds)
->where('status', 'in', [1, 3, 4]); // 1=已预约 3=已完成 4=已过号
if (!empty($this->params['appointment_date'])) {
$subQuery->where('appointment_date', (string) $this->params['appointment_date']);
}
$subQuery
->field('id, patient_id, doctor_id, appointment_date, appointment_time, status, create_time')
->order('appointment_date', 'asc')
->order('appointment_time', 'asc')
->order('id', 'asc');
$appointments = $subQuery->select()->toArray();
foreach ($appointments as $apt) {
$did = (int) ($apt['patient_id'] ?? 0);
if ($did <= 0) {
continue;
}
if (!isset($appointmentMap[$did])) {
$appointmentMap[$did] = [];
}
$appointmentMap[$did][] = $apt;
}
// 获取医生名称
$doctorIds = [];
foreach ($appointmentMap as $aptList) {
foreach ($aptList as $apt) {
$docId = (int) ($apt['doctor_id'] ?? 0);
if ($docId > 0) {
$doctorIds[] = $docId;
}
}
}
$doctorIds = array_values(array_unique($doctorIds));
$doctorNames = [];
if (!empty($doctorIds)) {
$doctors = Admin::whereIn('id', $doctorIds)->column('name', 'id');
$doctorNames = $doctors ?: [];
}
// 合并到诊单列表
foreach ($lists as &$item) {
$aptList = $appointmentMap[(int) $item['id']] ?? [];
if (!empty($aptList)) {
$apt = $aptList[0];
$item['has_appointment'] = 1;
$item['appointment_id'] = $apt['id'];
$item['appointment_status'] = (int) ($apt['status'] ?? 0);
$item['appointment_doctor_id'] = $apt['doctor_id'];
$item['appointment_doctor_name'] = $doctorNames[$apt['doctor_id']] ?? '-';
$timePart = $apt['appointment_time'] ?? '';
if (strlen($timePart) > 5) {
$timePart = substr($timePart, 0, 5); // 09:00:00 -> 09:00
}
$item['appointment_time_text'] = trim(($apt['appointment_date'] ?? '') . ' ' . $timePart);
$item['appointments'] = array_map(function ($a) use ($doctorNames) {
$timePart = $a['appointment_time'] ?? '';
if (strlen((string) $timePart) > 5) {
$timePart = substr((string) $timePart, 0, 5);
}
$doctorId = (int) ($a['doctor_id'] ?? 0);
return [
'id' => (int) ($a['id'] ?? 0),
'status' => (int) ($a['status'] ?? 0),
'doctor_id' => $doctorId,
'doctor_name' => (string) ($doctorNames[$doctorId] ?? '-'),
'time_text' => trim((string) ($a['appointment_date'] ?? '') . ' ' . (string) $timePart),
];
}, $aptList);
} else {
$item['has_appointment'] = 0;
$item['appointment_id'] = null;
$item['appointment_status'] = 0;
$item['appointment_doctor_id'] = null;
$item['appointment_doctor_name'] = '';
$item['appointment_time_text'] = '';
$item['appointments'] = [];
}
}
} else {
foreach ($lists as &$item) {
$item['has_appointment'] = 0;
$item['appointment_id'] = null;
$item['appointment_status'] = 0;
$item['appointment_doctor_id'] = null;
$item['appointment_doctor_name'] = '';
$item['appointment_time_text'] = '';
$item['appointments'] = [];
}
}
// 关联是否开方:诊单是否有处方记录
$diagnosisIds = array_column($lists, 'id');
$prescriptionMap = [];
if (!empty($diagnosisIds)) {
$prescriptionTbl = (new Prescription())->getTable();
$prescribedIds = Prescription::whereIn('diagnosis_id', $diagnosisIds)
->whereNull('delete_time')
->column('diagnosis_id');
$prescriptionMap = array_fill_keys($prescribedIds, 1);
}
foreach ($lists as &$item) {
$item['has_prescription'] = isset($prescriptionMap[$item['id']]) ? 1 : 0;
}
// 复诊展示:业务上「已开方」即算有复诊,取该诊单下最新一条处方的时间与医师(优先未作废)
$followupByDiag = [];
$rxCountByDiag = [];
if ($diagnosisIds !== []) {
$rxAll = Prescription::whereIn('diagnosis_id', $diagnosisIds)
->whereNull('delete_time')
->field(['id', 'diagnosis_id', 'doctor_name', 'creator_id', 'prescription_date', 'create_time', 'void_status', 'audit_status'])
->order('id', 'desc')
->select()
->toArray();
foreach ($rxAll as $rx) {
$dCount = (int) ($rx['diagnosis_id'] ?? 0);
if ($dCount > 0) {
$rxCountByDiag[$dCount] = ($rxCountByDiag[$dCount] ?? 0) + 1;
}
}
foreach ($rxAll as $rx) {
$d = (int) ($rx['diagnosis_id'] ?? 0);
if ($d <= 0 || isset($followupByDiag[$d])) {
continue;
}
if ((int) ($rx['void_status'] ?? 0) === 0) {
$followupByDiag[$d] = $rx;
}
}
foreach ($rxAll as $rx) {
$d = (int) ($rx['diagnosis_id'] ?? 0);
if ($d <= 0 || isset($followupByDiag[$d])) {
continue;
}
$followupByDiag[$d] = $rx;
}
$creatorIds = array_values(array_unique(array_filter(array_column($followupByDiag, 'creator_id'))));
$creatorNames = [];
if ($creatorIds !== []) {
$creatorNames = Admin::whereIn('id', $creatorIds)->whereNull('delete_time')->column('name', 'id');
}
foreach ($followupByDiag as $d => $rx) {
$doctorName = trim((string) ($rx['doctor_name'] ?? ''));
$cid = (int) ($rx['creator_id'] ?? 0);
if ($doctorName === '' && $cid > 0) {
$doctorName = (string) ($creatorNames[$cid] ?? '');
}
$followupByDiag[$d] = [
'time_text' => $this->formatPrescriptionFollowupTime($rx),
'doctor_name' => $doctorName !== '' ? $doctorName : '—',
'voided' => (int) ($rx['void_status'] ?? 0) !== 0,
'void_status' => (int) ($rx['void_status'] ?? 0),
'audit_status' => (int) ($rx['audit_status'] ?? -1),
];
}
}
foreach ($lists as &$item) {
$did = (int) $item['id'];
$fu = $followupByDiag[$did] ?? null;
$item['followup_time_text'] = ($item['has_prescription'] && $fu) ? ($fu['time_text'] ?? '') : '';
$item['followup_doctor_name'] = ($item['has_prescription'] && $fu) ? ($fu['doctor_name'] ?? '—') : '';
$item['followup_rx_voided'] = ($item['has_prescription'] && $fu && !empty($fu['voided'])) ? 1 : 0;
$item['prescription_audit_status'] = ($item['has_prescription'] && $fu) ? (int) ($fu['audit_status'] ?? -1) : -1;
$item['prescription_void_status'] = ($item['has_prescription'] && $fu) ? (int) ($fu['void_status'] ?? 0) : 0;
// 开方次数即复诊次数:1 张处方 = 第 1 次复诊,以此类推
$item['followup_prescription_count'] = (int) ($rxCountByDiag[$did] ?? 0);
}
// 最近一条通话记录状态(列表行展示:通话中 / 已结束等)
$latestCallByDiag = [];
if (!empty($diagnosisIds)) {
$agg = CallRecord::whereIn('diagnosis_id', $diagnosisIds)
->field('diagnosis_id, MAX(id) AS max_id')
->group('diagnosis_id')
->select()
->toArray();
$maxIds = array_filter(array_column($agg, 'max_id'));
if (!empty($maxIds)) {
$recList = CallRecord::whereIn('id', $maxIds)->select()->toArray();
foreach ($recList as $r) {
$latestCallByDiag[(int)$r['diagnosis_id']] = $r;
}
}
}
foreach ($lists as &$item) {
$item['video_call_hint'] = $this->buildVideoCallHint($latestCallByDiag[$item['id']] ?? null);
}
unset($item);
// 未服务天数:血糖 / 饮食 / 运动任一记录最近一条 record_date 为锚点
$diagIdsForUnserved = array_values(array_filter(array_unique(array_column($lists, 'id'))));
$maxRecordByDiag = [];
if (!empty($diagIdsForUnserved)) {
$maxBlood = BloodRecord::whereIn('diagnosis_id', $diagIdsForUnserved)
->whereNull('delete_time')
->group('diagnosis_id')
->column('MAX(record_date)', 'diagnosis_id');
$maxDiet = DietRecord::whereIn('diagnosis_id', $diagIdsForUnserved)
->whereNull('delete_time')
->group('diagnosis_id')
->column('MAX(record_date)', 'diagnosis_id');
$maxExercise = ExerciseRecord::whereIn('diagnosis_id', $diagIdsForUnserved)
->whereNull('delete_time')
->group('diagnosis_id')
->column('MAX(record_date)', 'diagnosis_id');
foreach ($diagIdsForUnserved as $did) {
$candidates = [
(int) ($maxBlood[$did] ?? 0),
(int) ($maxDiet[$did] ?? 0),
(int) ($maxExercise[$did] ?? 0),
];
$maxRecordByDiag[$did] = max($candidates);
}
}
$nowTs = time();
foreach ($lists as &$item) {
$last = (int) ($maxRecordByDiag[(int) $item['id']] ?? 0);
$item['last_blood_record_at'] = $last > 0 ? date('Y-m-d', $last) : null;
$item['unserved_days'] = $last > 0 ? max(0, (int) floor(($nowTs - $last) / 86400)) : null;
}
unset($item);
return $lists;
}
/**
* @param array<string,mixed> $rx 处方一行
*/
private function formatPrescriptionFollowupTime(array $rx): string
{
$ct = (int) ($rx['create_time'] ?? 0);
$pd = trim((string) ($rx['prescription_date'] ?? ''));
if ($pd !== '') {
return $pd . ($ct > 0 ? ' ' . date('H:i', $ct) : '');
}
return $ct > 0 ? date('Y-m-d H:i', $ct) : '—';
}
/**
* @param array<string,mixed>|null $rec tcm_call_record 一行
* @return array{state:string,label:string,end_time?:int,start_time?:int}
*/
private function buildVideoCallHint(?array $rec): array
{
if (!$rec) {
return ['state' => 'none', 'label' => ''];
}
$st = (int)($rec['status'] ?? 0);
$roomOk = trim((string)($rec['room_id'] ?? '')) !== '';
if ($st === 1 && $roomOk) {
$t = (int)($rec['start_time'] ?? 0);
return [
'state' => 'live',
'label' => '视频通话进行中',
'start_time' => $t,
];
}
if ($st === 1 && !$roomOk) {
return [
'state' => 'pending_room',
'label' => '通话发起中,待同步房间',
];
}
if ($st === 2) {
$end = (int)($rec['end_time'] ?? 0);
$timePart = $end > 0 ? date('m-d H:i', $end) : '';
return [
'state' => 'ended',
'label' => $timePart !== '' ? ('视频已结束 · ' . $timePart) : '视频已结束',
'end_time' => $end,
];
}
if ($st === 3) {
return ['state' => 'missed', 'label' => '上次通话未接听'];
}
if ($st === 4) {
return ['state' => 'cancelled', 'label' => '上次通话已取消'];
}
return ['state' => 'none', 'label' => ''];
}
/**
* @notes 获取数量
* @return int
*/
public function count(): int
{
// 获取当前管理员的角色ID
$roleIds = AdminRole::where('admin_id', $this->adminId)->column('role_id');
$pendingAssign = isset($this->params['pending_assign']) && (string) $this->params['pending_assign'] === '1';
// 构建查询
$pendingWideSearch = $this->isPendingAssignWideKeywordSearchActive();
$narrowPendingAssignPool = $pendingAssign && !$pendingWideSearch;
$baseWhere = $pendingWideSearch
? $this->searchWhereWithoutDiagnosisStatus($this->searchWhere)
: $this->searchWhere;
$query = Diagnosis::where($baseWhere);
if (in_array(2, $roleIds) && !$pendingAssign && !$pendingWideSearch) {
$query->where('assistant_id', $this->adminId);
}
if (!$pendingAssign && !$pendingWideSearch) {
$this->applyDataScopeByOwner($query, 'assistant_id');
}
$this->applyDiagnosisListKeywordFilter($query, $pendingAssign);
if ($narrowPendingAssignPool) {
$query->whereRaw('(assistant_id IS NULL OR assistant_id = 0)');
}
// 是否确认诊单
if (!$pendingWideSearch && isset($this->params['diagnosis_confirmed']) && $this->params['diagnosis_confirmed'] !== '') {
$confirmed = (int)$this->params['diagnosis_confirmed'];
$dvrTbl = (new DiagnosisViewRecord())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$subSql = "SELECT 1 FROM {$dvrTbl} dvr WHERE dvr.diagnosis_id = {$diagTbl}.id AND dvr.is_confirmed = 1 AND dvr.delete_time IS NULL";
if ($confirmed === 1) {
$query->whereExists($subSql);
} else {
$query->whereNotExists($subSql);
}
}
// 挂号日期筛选
if (!$pendingWideSearch && !empty($this->params['appointment_date'])) {
$aptDate = addslashes($this->params['appointment_date']);
$aptTbl = (new Appointment())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$query->whereExists("SELECT 1 FROM {$aptTbl} apt WHERE apt.patient_id = {$diagTbl}.id AND apt.appointment_date = '{$aptDate}' AND apt.status IN (1,3,4)");
}
// 挂号状态
if (!$pendingWideSearch && isset($this->params['has_appointment']) && $this->params['has_appointment'] !== '') {
$aptTbl = (new Appointment())->getTable();
$diagTbl = (new Diagnosis())->getTable();
if ((int)$this->params['has_appointment'] === 1) {
$query->whereExists("SELECT 1 FROM {$aptTbl} apt WHERE apt.patient_id = {$diagTbl}.id AND apt.status IN (1,3,4)");
} else {
$query->whereNotExists("SELECT 1 FROM {$aptTbl} apt WHERE apt.patient_id = {$diagTbl}.id AND apt.status IN (1,3,4)");
}
}
// 至少有一条挂号为「已完成」(status=3)
if (!$pendingWideSearch && isset($this->params['completed_appointment']) && (string) $this->params['completed_appointment'] === '1') {
$aptTbl = (new Appointment())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$query->whereExists("SELECT 1 FROM {$aptTbl} apt WHERE apt.patient_id = {$diagTbl}.id AND apt.status = 3");
}
// 仅已开方(待分配+关键词检索时不限制)
if (!$pendingWideSearch && isset($this->params['only_has_prescription']) && (string) $this->params['only_has_prescription'] === '1') {
$rxTbl = (new Prescription())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$query->whereExists("SELECT 1 FROM {$rxTbl} rx WHERE rx.diagnosis_id = {$diagTbl}.id AND rx.delete_time IS NULL");
}
// 医助所属部门(待分配+关键词检索时不按部门收窄)
if (!$pendingWideSearch && isset($this->params['assistant_dept_id']) && $this->params['assistant_dept_id'] !== '' && (int) $this->params['assistant_dept_id'] > 0) {
$rootDeptId = (int) $this->params['assistant_dept_id'];
$deptIds = DeptLogic::getSelfAndDescendantIds($rootDeptId);
$deptIds = array_values(array_filter(array_map('intval', $deptIds), static function (int $id): bool {
return $id > 0;
}));
if ($deptIds === []) {
$query->whereRaw('0 = 1');
} else {
$inList = implode(',', $deptIds);
$adTbl = (new AdminDept())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$query->whereExists("SELECT 1 FROM {$adTbl} ad WHERE ad.admin_id = {$diagTbl}.assistant_id AND ad.dept_id IN ({$inList})");
}
}
$this->applyPendingAssignBusinessOrderMonthFilter($query);
return $query->count();
}
/**
* 待分配 Tab 且有关键词时:放宽列表条件(与 `applyDiagnosisListKeywordFilter` 判定一致)。
*/
private function isPendingAssignWideKeywordSearchActive(): bool
{
$pending = isset($this->params['pending_assign']) && (string) $this->params['pending_assign'] === '1';
if (!$pending) {
return false;
}
$wideKw = trim((string) ($this->params['pending_assign_keyword'] ?? ''));
if ($wideKw === '') {
$wideKw = trim((string) ($this->params['keyword'] ?? ''));
}
return $wideKw !== '';
}
/**
* 从 `createWhere` 结果中移除诊单 `status`(启用/禁用),便于宽检索命中任意状态诊单。
*
* @param list<array{0:mixed,1?:mixed,2?:mixed}> $searchWhere
* @return list<array{0:mixed,1?:mixed,2?:mixed}>
*/
private function searchWhereWithoutDiagnosisStatus(array $searchWhere): array
{
$out = [];
foreach ($searchWhere as $cond) {
if (is_array($cond) && isset($cond[0]) && $cond[0] === 'status') {
continue;
}
$out[] = $cond;
}
return $out;
}
/**
* 列表关键字:待分配医助 Tab 为「多字段」检索(pending_assign_keyword 优先,否则回落 keyword);
* 其它 Tab 仍为姓名、手机。待分配 Tab 本身已不套 assistant_id 数据权限,检索可触达全部待分配诊单。
*/
private function applyDiagnosisListKeywordFilter($query, bool $pendingAssign): void
{
if ($pendingAssign) {
$wideKw = trim((string) ($this->params['pending_assign_keyword'] ?? ''));
if ($wideKw === '') {
$wideKw = trim((string) ($this->params['keyword'] ?? ''));
}
if ($wideKw !== '') {
$this->applyPendingAssignWideKeywordSearch($query, $wideKw);
}
return;
}
if (isset($this->params['keyword']) && trim((string) $this->params['keyword']) !== '') {
$keyword = trim((string) $this->params['keyword']);
$query->where(function ($q) use ($keyword) {
$q->whereLike('patient_name', '%' . $keyword . '%')
->whereOr('phone', 'like', '%' . $keyword . '%');
});
}
}
/**
* 待分配医助场景:多列模糊 + 纯数字时诊单 id / 患者号精确匹配。
*/
private function applyPendingAssignWideKeywordSearch($query, string $keyword): void
{
$kw = trim($keyword);
if ($kw === '') {
return;
}
$query->where(function ($q) use ($kw) {
$q->whereLike('patient_name', '%' . $kw . '%')
->whereOr('phone', 'like', '%' . $kw . '%')
->whereOr('id_card', 'like', '%' . $kw . '%')
->whereOr('remark', 'like', '%' . $kw . '%')
->whereOr('symptoms', 'like', '%' . $kw . '%')
->whereOr('current_medications', 'like', '%' . $kw . '%');
if (preg_match('/^\d+$/', $kw)) {
$n = (int) $kw;
if ($n > 0) {
$q->whereOr('id', '=', $n)
->whereOr('patient_id', '=', $n);
}
}
});
}
/**
* 「待分配医助」Tab:按月份筛选——诊单在指定自然月内需存在业务订单(order.patient_id = 诊单 id
* create_time 兼容整型时间戳与 datetime 字符串
*/
private function applyPendingAssignBusinessOrderMonthFilter($query): void
{
if ($this->isPendingAssignWideKeywordSearchActive()) {
return;
}
$pendingAssign = isset($this->params['pending_assign']) && (string) $this->params['pending_assign'] === '1';
if (!$pendingAssign || empty($this->params['pending_assign_order_month'])) {
return;
}
$month = trim((string) $this->params['pending_assign_order_month']);
if (!preg_match('/^(\d{4})-(\d{2})$/', $month, $m)) {
return;
}
$y = (int) $m[1];
$mo = (int) $m[2];
if ($y < 1970 || $y > 2100 || $mo < 1 || $mo > 12) {
return;
}
$tStart = strtotime(sprintf('%04d-%02d-01 00:00:00', $y, $mo));
if ($tStart === false) {
return;
}
$tEnd = strtotime(date('Y-m-t 23:59:59', $tStart));
if ($tEnd === false) {
return;
}
$dsStart = date('Y-m-d H:i:s', $tStart);
$dsEnd = date('Y-m-d H:i:s', $tEnd);
$orderTbl = (new Order())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$sql = "SELECT 1 FROM `{$orderTbl}` o WHERE o.patient_id = {$diagTbl}.id "
. 'AND o.delete_time IS NULL AND ('
. "(o.create_time >= {$tStart} AND o.create_time <= {$tEnd}) OR "
. "(o.create_time >= '{$dsStart}' AND o.create_time <= '{$dsEnd}')"
. ')';
$query->whereExists($sql);
}
}