This commit is contained in:
gr
2026-10-15 11:03:54 +08:00
parent 70ddc1a70a
commit bb28d93cec
64 changed files with 12747 additions and 420 deletions
@@ -31,8 +31,10 @@ use app\common\model\auth\Admin;
use app\common\model\auth\AdminDept;
use app\common\model\auth\AdminRole;
use app\common\model\dict\DictData;
use app\common\model\tcm\DiagnosisRevisitDue;
use app\common\lists\ListsSearchInterface;
use app\common\lists\Traits\HasDataScopeFilter;
use app\common\service\tcm\RevisitDueService;
use think\facade\Db;
/**
@@ -143,6 +145,7 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
$this->applyLatestAppointmentFilters($query, $pendingWideSearch);
$this->applyLatestAssignFilters($query, $pendingWideSearch);
$this->applyRevisitDueFilters($query, $pendingWideSearch);
$this->applyPendingAssignBusinessOrderMonthFilter($query);
@@ -460,6 +463,19 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
}
unset($item);
// 复诊到期(契约 C):按快照表批量读取,缺行/不一致时由服务内部自愈(仅小页前 50 行);读取异常由服务降级为 none 占位
$revisitDueMap = [];
if (!empty($diagIdsForUnserved)) {
$revisitDueMap = RevisitDueService::buildListPayload($diagIdsForUnserved, [
'self_heal' => true,
'page_size' => (int) $this->limitLength,
]);
}
foreach ($lists as &$item) {
$item['revisit_due'] = $revisitDueMap[(int) $item['id']] ?? RevisitDueService::emptyPayload();
}
unset($item);
return $lists;
}
@@ -598,6 +614,7 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
$this->applyLatestAppointmentFilters($query, $pendingWideSearch);
$this->applyLatestAssignFilters($query, $pendingWideSearch);
$this->applyRevisitDueFilters($query, $pendingWideSearch);
// 仅已开方(待分配+关键词检索时不限制)
if (!$pendingWideSearch && isset($this->params['only_has_prescription']) && (string) $this->params['only_has_prescription'] === '1') {
@@ -714,11 +731,120 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
}
/**
* 列表默认排序;支持 sort_unserved_days=asc|desc 按未服务天数排序。
* 复诊到期筛选(方案 §3.2 ②):以快照表 tcm_diagnosis_revisit_due 的 EXISTS 子查询实现。
*
* revisit_state:due(超期 ∪ 过号,排除已承诺未到期)| warning(到期前 warn_days 内)| booked(已约且日期未过)
* | visited(已面诊)| contacted(已承诺复诊日未过)| no_days(有有效订单但缺开药天数)| pending(到期日在预警窗之外)
* revisit_days_bucket:14|21|28|30 精确命中档位;other = 未命中档位且有开药天数
* 待分配宽搜不套此过滤;lists() 与 count() 各调用一次保证分页总数一致。
*
* @param mixed $query
*/
private function applyRevisitDueFilters($query, bool $pendingWideSearch): void
{
if ($pendingWideSearch) {
return;
}
$state = strtolower(trim((string) ($this->params['revisit_state'] ?? '')));
$bucket = strtolower(trim((string) ($this->params['revisit_days_bucket'] ?? '')));
if ($state === '' && $bucket === '') {
return;
}
$rdTbl = (new DiagnosisRevisitDue())->getTable();
$diagTbl = (new Diagnosis())->getTable();
$conditions = ["rd.diagnosis_id = {$diagTbl}.id"];
if ($state !== '') {
$warnDays = (int) (RevisitDueService::config()['warn_days'] ?? 3);
if ($warnDays < 0) {
$warnDays = 0;
}
$warnEdge = "DATE_ADD(CURDATE(), INTERVAL {$warnDays} DAY)";
switch ($state) {
case 'due':
$conditions[] = 'rd.closed_state = 0';
$conditions[] = '(rd.appointment_state = 4 OR (rd.due_date IS NOT NULL AND rd.due_date <= CURDATE()))';
$conditions[] = '(rd.promised_date IS NULL OR rd.promised_date < CURDATE())';
break;
case 'warning':
// 与读取侧 deriveState 对齐:过号(4)/已约已过期/已联系(承诺日未到) 的行优先级更高,不落入预警
$conditions[] = 'rd.closed_state = 0';
$conditions[] = 'rd.due_date IS NOT NULL';
$conditions[] = 'rd.due_date > CURDATE()';
$conditions[] = "rd.due_date <= {$warnEdge}";
$conditions[] = 'rd.appointment_state <> 4';
$conditions[] = 'NOT (rd.appointment_state = 1 AND rd.appointment_date < CURDATE())';
$conditions[] = '(rd.promised_date IS NULL OR rd.promised_date < CURDATE())';
break;
case 'booked':
$conditions[] = 'rd.appointment_state = 1';
$conditions[] = 'rd.appointment_date IS NOT NULL';
$conditions[] = 'rd.appointment_date >= CURDATE()';
break;
case 'visited':
$conditions[] = '(rd.closed_state = 3 OR rd.appointment_state = 3)';
break;
case 'contacted':
$conditions[] = 'rd.promised_date IS NOT NULL';
$conditions[] = 'rd.promised_date >= CURDATE()';
break;
case 'no_days':
// 与 deriveState 的 no_days 分支对齐:进行中、有有效订单但算不出预计复诊日(失效行不计)
$conditions[] = 'rd.closed_state = 0';
$conditions[] = 'rd.valid_order_count > 0';
$conditions[] = 'rd.due_date IS NULL';
break;
case 'pending':
$conditions[] = 'rd.closed_state = 0';
$conditions[] = 'rd.due_date IS NOT NULL';
$conditions[] = "rd.due_date > {$warnEdge}";
$conditions[] = 'rd.appointment_state <> 4';
$conditions[] = 'NOT (rd.appointment_state = 1 AND rd.appointment_date < CURDATE())';
$conditions[] = '(rd.promised_date IS NULL OR rd.promised_date < CURDATE())';
break;
default:
// 未知状态值:不做任何收窄(与其它未知筛选参数一致,避免误清空列表)
break;
}
}
if ($bucket !== '') {
if ($bucket === 'other') {
$conditions[] = 'rd.rule_days = 0';
$conditions[] = 'rd.medication_days > 0';
} elseif (ctype_digit($bucket) && (int) $bucket > 0) {
$conditions[] = 'rd.rule_days = ' . (int) $bucket;
}
}
if (count($conditions) <= 1) {
return;
}
$query->whereExists("SELECT 1 FROM {$rdTbl} rd WHERE " . implode(' AND ', $conditions));
}
/**
* 列表默认排序;支持 sort_revisit_due=asc|desc 按复诊到期日排序(优先),
* 或 sort_unserved_days=asc|desc 按未服务天数排序;两者互斥,前者存在时忽略后者。
*/
private function resolveListOrderRaw(bool $pendingWideSearch): string
{
$diagTbl = (new Diagnosis())->getTable();
// 复诊到期日排序(方案 §3.2 ③):单行子查询取进行中快照的 due_date,无快照 / 无到期日的行置后
$sortRevisitDue = strtolower(trim((string) ($this->params['sort_revisit_due'] ?? '')));
if (in_array($sortRevisitDue, ['asc', 'desc'], true)) {
$rdTbl = (new DiagnosisRevisitDue())->getTable();
$dueExpr = "(SELECT rd.due_date FROM {$rdTbl} rd WHERE rd.diagnosis_id = {$diagTbl}.id AND rd.closed_state = 0 LIMIT 1)";
$nullLast = "CASE WHEN {$dueExpr} IS NULL THEN 1 ELSE 0 END ASC";
$dir = $sortRevisitDue === 'desc' ? 'DESC' : 'ASC';
return "{$nullLast}, {$dueExpr} {$dir}, {$diagTbl}.id DESC";
}
$sortUnserved = strtolower(trim((string) ($this->params['sort_unserved_days'] ?? '')));
if (in_array($sortUnserved, ['asc', 'desc'], true)) {
$anchorExpr = $this->unservedAnchorExpr($diagTbl);