1
This commit is contained in:
@@ -113,12 +113,31 @@ class DiagnosisController extends BaseAdminController
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
|
||||
|
||||
$params = (new DiagnosisValidate())->goCheck('id');
|
||||
$result = DiagnosisLogic::detail($params);
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 二诊只读病例详情(患者信息 + 病例 + 跟踪记录)
|
||||
*
|
||||
* 路由:GET /tcm.diagnosis/readonlyDetail?id=:diagnosisId
|
||||
* 权限:tcm.diagnosis/readonlyDetail
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function readonlyDetail()
|
||||
{
|
||||
$params = (new DiagnosisValidate())->goCheck('readonlyDetail');
|
||||
$result = DiagnosisLogic::readonlyDetail($params, $this->adminId, $this->adminInfo);
|
||||
if (empty($result)) {
|
||||
return $this->fail(DiagnosisLogic::getError() ?: '诊单不存在或无权访问');
|
||||
}
|
||||
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 诊单挂号 / 取消挂号 操作日志(谁在何时操作)
|
||||
*/
|
||||
|
||||
@@ -17,6 +17,7 @@ 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\DiagnosisViewRecord;
|
||||
use app\common\model\doctor\Appointment;
|
||||
use app\common\model\tcm\Prescription;
|
||||
@@ -381,6 +382,25 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
foreach ($lists as &$item) {
|
||||
$item['video_call_hint'] = $this->buildVideoCallHint($latestCallByDiag[$item['id']] ?? null);
|
||||
}
|
||||
unset($item);
|
||||
|
||||
// 未服务天数:以血糖记录最近一条 record_date 为锚点(口径见 prd T3)
|
||||
// 单次 GROUP BY 聚合,不会成 N+1;走 idx_diagnosis_id 索引。
|
||||
$diagIdsForUnserved = array_values(array_filter(array_unique(array_column($lists, 'id'))));
|
||||
$maxBloodByDiag = [];
|
||||
if (!empty($diagIdsForUnserved)) {
|
||||
$maxBloodByDiag = BloodRecord::whereIn('diagnosis_id', $diagIdsForUnserved)
|
||||
->whereNull('delete_time')
|
||||
->group('diagnosis_id')
|
||||
->column('MAX(record_date)', 'diagnosis_id');
|
||||
}
|
||||
$nowTs = time();
|
||||
foreach ($lists as &$item) {
|
||||
$last = (int) ($maxBloodByDiag[(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;
|
||||
}
|
||||
|
||||
@@ -561,13 +561,13 @@ class AppointmentLogic extends BaseLogic
|
||||
|
||||
/**
|
||||
* 字典翻译:把诊单中以 code 存储的字段(字典/枚举)补一份中文 label 字段,
|
||||
* 便于前端(接诊台等只读场景)直接渲染,无需加载字典。
|
||||
* 便于前端(接诊台 / 只读病例页等只读场景)直接渲染,无需加载字典。
|
||||
* 原始 code 字段保留不动,edit 场景仍可使用。
|
||||
*
|
||||
* @param array $diagnosis
|
||||
* @return array
|
||||
*/
|
||||
private static function enrichDiagnosisLabels(array $diagnosis): array
|
||||
public static function enrichDiagnosisLabels(array $diagnosis): array
|
||||
{
|
||||
if (empty($diagnosis)) {
|
||||
return $diagnosis;
|
||||
|
||||
@@ -19,9 +19,17 @@ use app\common\model\tcm\Diagnosis;
|
||||
use app\common\model\tcm\DiagnosisGuahaoLog;
|
||||
use app\common\model\tcm\DiagnosisAssignLog;
|
||||
use app\common\model\tcm\ImChatMessage;
|
||||
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\doctor\Appointment;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\adminapi\logic\doctor\DoctorNoteLogic;
|
||||
use app\adminapi\logic\doctor\AppointmentLogic;
|
||||
use app\common\service\FileService;
|
||||
use app\common\service\DataScope\DataScopeService;
|
||||
use think\facade\Db;
|
||||
/**
|
||||
* 中医辨房病因诊单逻辑
|
||||
@@ -3324,4 +3332,237 @@ class DiagnosisLogic extends BaseLogic
|
||||
|
||||
return $logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 二诊只读病例详情
|
||||
*
|
||||
* 用途:医助从诊单列表「查看 / 双击」进入只读页(T1+T2),返回与接诊台同结构的
|
||||
* 三块内容(appointment + diagnosis + 跟踪记录)。
|
||||
*
|
||||
* 数据权限(与列表 lists 接口一致):
|
||||
* - 医助 (role_id=2):仅能访问 assistant_id == self 的诊单
|
||||
* - DataScope 启用:assistant_id 须落在 visibleAdminIds
|
||||
* - 越权 → setError + 返回 []
|
||||
*
|
||||
* @param array $params 至少含 id
|
||||
* @param int $adminId
|
||||
* @param array<string,mixed> $adminInfo request->adminInfo
|
||||
* @return array
|
||||
*/
|
||||
public static function readonlyDetail(array $params, int $adminId, array $adminInfo): array
|
||||
{
|
||||
$diagnosisId = (int) ($params['id'] ?? 0);
|
||||
if ($diagnosisId <= 0) {
|
||||
self::setError('诊单 id 不能为空');
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// 1) 数据权限闸 — 不通过则返回「不存在或无权访问」
|
||||
$accessQuery = Diagnosis::where('id', $diagnosisId)->whereNull('delete_time');
|
||||
|
||||
$roleIds = array_map('intval', AdminRole::where('admin_id', $adminId)->column('role_id'));
|
||||
if (in_array(2, $roleIds, true)) {
|
||||
// 医助仅看自己被指派的
|
||||
$accessQuery->where('assistant_id', $adminId);
|
||||
}
|
||||
|
||||
if (DataScopeService::isEnabled()) {
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
if ($visibleIds === []) {
|
||||
self::setError('诊单不存在或无权访问');
|
||||
|
||||
return [];
|
||||
}
|
||||
if (is_array($visibleIds)) {
|
||||
$accessQuery->whereIn('assistant_id', $visibleIds);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$accessQuery->find()) {
|
||||
self::setError('诊单不存在或无权访问');
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// 2) 诊单详情(含图片聚合等)+ 字典翻译
|
||||
$diagnosis = self::detail(['id' => $diagnosisId]);
|
||||
if (empty($diagnosis)) {
|
||||
self::setError('诊单数据为空');
|
||||
|
||||
return [];
|
||||
}
|
||||
$diagnosis = AppointmentLogic::enrichDiagnosisLabels($diagnosis);
|
||||
|
||||
// 3) 最近一条挂号(PatientInfoCard 显示用,无挂号则给默认空对象)
|
||||
$appointment = self::buildLatestAppointmentForReadonly((int) ($diagnosis['id'] ?? $diagnosisId), $diagnosis);
|
||||
|
||||
// 4) 最近 30 天的跟踪记录(一次性 join 出三类,前端透视为矩阵)
|
||||
$windowDays = 30;
|
||||
$sinceTs = strtotime('-' . $windowDays . ' days');
|
||||
$sinceTs = $sinceTs > 0 ? $sinceTs : 0;
|
||||
|
||||
$bloodRecords = self::fetchBloodRecordsForReadonly($diagnosisId, $sinceTs);
|
||||
$dietRecords = self::fetchDietRecordsForReadonly($diagnosisId, $sinceTs);
|
||||
$exerciseRecords = self::fetchExerciseRecordsForReadonly($diagnosisId, $sinceTs);
|
||||
|
||||
// 5) 医生备注
|
||||
$doctorNotes = DoctorNoteLogic::getByDiagnosis($diagnosisId);
|
||||
|
||||
// 6) 未服务天数(与 T3 列同口径)
|
||||
$maxBloodTs = BloodRecord::where('diagnosis_id', $diagnosisId)
|
||||
->max('record_date');
|
||||
$maxBloodTs = (int) $maxBloodTs;
|
||||
$unservedDays = $maxBloodTs > 0 ? max(0, (int) floor((time() - $maxBloodTs) / 86400)) : null;
|
||||
$lastBloodRecordAt = $maxBloodTs > 0 ? date('Y-m-d', $maxBloodTs) : null;
|
||||
|
||||
return [
|
||||
'appointment' => $appointment,
|
||||
'diagnosis' => $diagnosis,
|
||||
'blood_records' => $bloodRecords,
|
||||
'diet_records' => $dietRecords,
|
||||
'exercise_records' => $exerciseRecords,
|
||||
'doctor_notes' => $doctorNotes,
|
||||
'unserved_days' => $unservedDays,
|
||||
'last_blood_record_at' => $lastBloodRecordAt,
|
||||
'window_days' => $windowDays,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼一份「最近一条挂号」给只读页 PatientInfoCard 用;没有挂号则给默认空骨架。
|
||||
*
|
||||
* @param int $diagnosisId
|
||||
* @param array<string,mixed> $diagnosis 已字典翻译过的诊单数据
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private static function buildLatestAppointmentForReadonly(int $diagnosisId, array $diagnosis): array
|
||||
{
|
||||
$apt = Appointment::where('patient_id', $diagnosisId)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
|
||||
$patientName = (string) ($diagnosis['patient_name'] ?? '');
|
||||
$patientPhone = (string) ($diagnosis['phone'] ?? '');
|
||||
|
||||
if (!$apt) {
|
||||
return [
|
||||
'id' => 0,
|
||||
'patient_id' => $diagnosisId,
|
||||
'patient_name' => $patientName,
|
||||
'patient_phone' => $patientPhone,
|
||||
'doctor_id' => 0,
|
||||
'doctor_name' => '',
|
||||
'assistant_id' => (int) ($diagnosis['assistant_id'] ?? 0),
|
||||
'assistant_name' => self::resolveAssistantName((int) ($diagnosis['assistant_id'] ?? 0)),
|
||||
'appointment_date' => '',
|
||||
'appointment_time' => '',
|
||||
'period' => '',
|
||||
'status' => 0,
|
||||
'status_desc' => '无挂号',
|
||||
'has_prescription' => 0,
|
||||
'remark' => '',
|
||||
];
|
||||
}
|
||||
|
||||
$aptArr = $apt->toArray();
|
||||
$aptArr['patient_name'] = $patientName;
|
||||
$aptArr['patient_phone'] = $patientPhone;
|
||||
// 医生姓名
|
||||
$aptArr['doctor_name'] = '';
|
||||
$docId = (int) ($aptArr['doctor_id'] ?? 0);
|
||||
if ($docId > 0) {
|
||||
$aptArr['doctor_name'] = (string) Admin::where('id', $docId)->value('name');
|
||||
}
|
||||
$aptArr['assistant_id'] = (int) ($diagnosis['assistant_id'] ?? 0);
|
||||
$aptArr['assistant_name'] = self::resolveAssistantName((int) ($diagnosis['assistant_id'] ?? 0));
|
||||
// status_desc 与现有列表口径保持简单映射
|
||||
$statusMap = [
|
||||
0 => '未挂号',
|
||||
1 => '已预约',
|
||||
2 => '已取消',
|
||||
3 => '已完成',
|
||||
4 => '已过号',
|
||||
];
|
||||
$aptArr['status_desc'] = $statusMap[(int) ($aptArr['status'] ?? 0)] ?? '';
|
||||
$aptArr['has_prescription'] = (int) ($diagnosis['has_prescription'] ?? 0);
|
||||
|
||||
return $aptArr;
|
||||
}
|
||||
|
||||
private static function resolveAssistantName(int $assistantId): string
|
||||
{
|
||||
if ($assistantId <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) Admin::where('id', $assistantId)->value('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
private static function fetchBloodRecordsForReadonly(int $diagnosisId, int $sinceTs): array
|
||||
{
|
||||
$query = BloodRecord::where('diagnosis_id', $diagnosisId);
|
||||
if ($sinceTs > 0) {
|
||||
$query->where('record_date', '>=', $sinceTs);
|
||||
}
|
||||
$rows = $query->order('record_date', 'desc')
|
||||
->order('record_time', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
// 给前端一个 Y-m-d 字符串方便按日期透视;保留原 record_date 整数戳作日期窗口比较
|
||||
foreach ($rows as &$row) {
|
||||
$ts = (int) ($row['record_date'] ?? 0);
|
||||
$row['record_date_ts'] = $ts;
|
||||
$row['record_date'] = $ts > 0 ? date('Y-m-d', $ts) : '';
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
private static function fetchDietRecordsForReadonly(int $diagnosisId, int $sinceTs): array
|
||||
{
|
||||
$query = DietRecord::where('diagnosis_id', $diagnosisId);
|
||||
if ($sinceTs > 0) {
|
||||
$query->where('record_date', '>=', $sinceTs);
|
||||
}
|
||||
$rows = $query->order('record_date', 'desc')->select()->toArray();
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$ts = (int) ($row['record_date'] ?? 0);
|
||||
$row['record_date_ts'] = $ts;
|
||||
$row['record_date'] = $ts > 0 ? date('Y-m-d', $ts) : '';
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
private static function fetchExerciseRecordsForReadonly(int $diagnosisId, int $sinceTs): array
|
||||
{
|
||||
$query = ExerciseRecord::where('diagnosis_id', $diagnosisId);
|
||||
if ($sinceTs > 0) {
|
||||
$query->where('record_date', '>=', $sinceTs);
|
||||
}
|
||||
$rows = $query->order('record_date', 'desc')
|
||||
->append(['intensity_text'])
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
foreach ($rows as &$row) {
|
||||
$ts = (int) ($row['record_date'] ?? 0);
|
||||
$row['record_date_ts'] = $ts;
|
||||
$row['record_date'] = $ts > 0 ? date('Y-m-d', $ts) : '';
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ class DiagnosisValidate extends BaseValidate
|
||||
{
|
||||
return $this->only(['id']);
|
||||
}
|
||||
|
||||
public function sceneReadonlyDetail()
|
||||
{
|
||||
return $this->only(['id']);
|
||||
}
|
||||
|
||||
public function sceneGenerateQrcode()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user