更新bug
This commit is contained in:
@@ -172,7 +172,7 @@ class AppointmentController extends BaseAdminController
|
||||
public function addDoctorNote()
|
||||
{
|
||||
$params = (new AppointmentValidate())->post()->goCheck('addDoctorNote');
|
||||
if (!DiagnosisLogic::canViewReadonlyDiagnosis(
|
||||
if (!DiagnosisLogic::canManageDiagnosis(
|
||||
(int) $params['diagnosis_id'],
|
||||
$this->adminId,
|
||||
$this->adminInfo
|
||||
|
||||
@@ -330,25 +330,15 @@ class DiagnosisController extends BaseAdminController
|
||||
* @notes 获取通话签名
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getCallSignature()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
|
||||
if (empty($params['diagnosis_id'])) {
|
||||
return $this->fail('诊单ID不能为空');
|
||||
}
|
||||
|
||||
if (empty($params['patient_id'])) {
|
||||
return $this->fail('患者ID不能为空');
|
||||
}
|
||||
|
||||
// 传递当前管理员ID
|
||||
$params['admin_id'] = $this->adminId;
|
||||
|
||||
$result = DiagnosisLogic::getCallSignature($params);
|
||||
if ($result) {
|
||||
return $this->data($result);
|
||||
}
|
||||
public function getCallSignature()
|
||||
{
|
||||
$params = (new DiagnosisValidate())->post()->goCheck('callIdentity');
|
||||
$params['admin_id'] = (int) $this->adminId;
|
||||
|
||||
$result = DiagnosisLogic::getCallSignature($params, $this->adminInfo);
|
||||
if ($result) {
|
||||
return $this->data($result);
|
||||
}
|
||||
return $this->fail(DiagnosisLogic::getError());
|
||||
}
|
||||
|
||||
@@ -525,9 +515,9 @@ class DiagnosisController extends BaseAdminController
|
||||
* @notes 接通后发起腾讯云云端混流录制(需配置 CAM 与云点播)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function startCloudRecording()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
public function startCloudRecording()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
if (empty($params['diagnosis_id'])) {
|
||||
return $this->fail('诊单ID不能为空');
|
||||
}
|
||||
@@ -536,12 +526,32 @@ class DiagnosisController extends BaseAdminController
|
||||
if ($result === false) {
|
||||
return $this->fail(DiagnosisLogic::getError());
|
||||
}
|
||||
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 浏览器本地上传通话录制后,关联到通话记录(合并 recording_urls)
|
||||
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 分片上传本机通话录音或手动视频回放
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function uploadCallRecording()
|
||||
{
|
||||
$params = $this->request->post();
|
||||
if (empty($params['diagnosis_id'])) {
|
||||
return $this->fail('诊单ID不能为空');
|
||||
}
|
||||
|
||||
$params['admin_id'] = (int)$this->adminId;
|
||||
$result = DiagnosisLogic::uploadCallRecording($params);
|
||||
if ($result === false) {
|
||||
return $this->fail(DiagnosisLogic::getError());
|
||||
}
|
||||
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 浏览器本地上传通话录制后,关联到通话记录(合并 recording_urls)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function attachLocalCallRecording()
|
||||
|
||||
@@ -293,20 +293,32 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
$prescribedDiagnosisIds = $rxQ->column('diagnosis_id');
|
||||
$prescribedDiagnosisIds = array_flip($prescribedDiagnosisIds ?: []);
|
||||
}
|
||||
// 当前页预约关联的处方(按 appointment_id,取最新一条):用于「开方/查看」与审核状态
|
||||
// 当前页预约关联的处方(按 appointment_id,优先最新未作废,否则最新一条):
|
||||
// 用于本次挂号的「开方/编辑/查看」与审核状态。
|
||||
$appointmentIds = array_filter(array_map('intval', array_column($lists, 'id')));
|
||||
$rxByAppointmentId = [];
|
||||
$fallbackRxByAppointmentId = [];
|
||||
if (!empty($appointmentIds)) {
|
||||
$rxRows = Prescription::whereIn('appointment_id', $appointmentIds)
|
||||
->whereNull('delete_time')
|
||||
->where('void_status', 0)
|
||||
->order('id', 'desc')
|
||||
->field(['id', 'appointment_id', 'audit_status', 'void_status', 'is_system_auto'])
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($rxRows as $rx) {
|
||||
$aid = (int) ($rx['appointment_id'] ?? 0);
|
||||
if ($aid > 0 && !isset($rxByAppointmentId[$aid])) {
|
||||
if ($aid <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($fallbackRxByAppointmentId[$aid])) {
|
||||
$fallbackRxByAppointmentId[$aid] = $rx;
|
||||
}
|
||||
if ((int) ($rx['void_status'] ?? 0) === 0 && !isset($rxByAppointmentId[$aid])) {
|
||||
$rxByAppointmentId[$aid] = $rx;
|
||||
}
|
||||
}
|
||||
foreach ($fallbackRxByAppointmentId as $aid => $rx) {
|
||||
if (!isset($rxByAppointmentId[$aid])) {
|
||||
$rxByAppointmentId[$aid] = $rx;
|
||||
}
|
||||
}
|
||||
@@ -353,6 +365,8 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
|
||||
$apptId = (int) ($item['id'] ?? 0);
|
||||
$apptRx = $rxByAppointmentId[$apptId] ?? null;
|
||||
$item['current_has_prescription'] = $apptRx !== null ? 1 : 0;
|
||||
$item['current_prescription_id'] = $apptRx !== null ? (int) ($apptRx['id'] ?? 0) : 0;
|
||||
$item['prescription_audit_status'] = $apptRx !== null ? (int) ($apptRx['audit_status'] ?? -1) : -1;
|
||||
$item['prescription_void_status'] = $apptRx !== null ? (int) ($apptRx['void_status'] ?? 0) : 0;
|
||||
$item['prescription_is_system_auto'] = $apptRx !== null ? (int) ($apptRx['is_system_auto'] ?? 0) : 0;
|
||||
|
||||
@@ -282,17 +282,51 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
|
||||
// 关联是否开方:诊单是否有处方记录
|
||||
$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;
|
||||
}
|
||||
$prescriptionMap = [];
|
||||
if (!empty($diagnosisIds)) {
|
||||
$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;
|
||||
}
|
||||
|
||||
// 当前操作必须只认当前挂号的处方。has_prescription 继续表示诊单历史上曾开方,
|
||||
// 不能再用它驱动本次挂号的“开方/编辑/查看”按钮。
|
||||
$currentRxByAppointment = [];
|
||||
$fallbackRxByAppointment = [];
|
||||
$appointmentIds = array_values(array_filter(array_unique(array_map(
|
||||
'intval',
|
||||
array_column($lists, 'appointment_id')
|
||||
))));
|
||||
if ($appointmentIds !== []) {
|
||||
$currentRxRows = Prescription::whereIn('appointment_id', $appointmentIds)
|
||||
->whereNull('delete_time')
|
||||
->field(['id', 'appointment_id', 'audit_status', 'void_status'])
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($currentRxRows as $rx) {
|
||||
$appointmentId = (int) ($rx['appointment_id'] ?? 0);
|
||||
if ($appointmentId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($fallbackRxByAppointment[$appointmentId])) {
|
||||
$fallbackRxByAppointment[$appointmentId] = $rx;
|
||||
}
|
||||
if ((int) ($rx['void_status'] ?? 0) === 0
|
||||
&& !isset($currentRxByAppointment[$appointmentId])) {
|
||||
$currentRxByAppointment[$appointmentId] = $rx;
|
||||
}
|
||||
}
|
||||
foreach ($fallbackRxByAppointment as $appointmentId => $rx) {
|
||||
if (!isset($currentRxByAppointment[$appointmentId])) {
|
||||
$currentRxByAppointment[$appointmentId] = $rx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 复诊展示:业务上「已开方」即算有复诊,取该诊单下最新一条处方的时间与医师(优先未作废)
|
||||
$followupByDiag = [];
|
||||
@@ -346,17 +380,21 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
];
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
foreach ($lists as &$item) {
|
||||
$did = (int) $item['id'];
|
||||
$fu = $followupByDiag[$did] ?? null;
|
||||
$currentAppointmentId = (int) ($item['appointment_id'] ?? 0);
|
||||
$currentRx = $currentRxByAppointment[$currentAppointmentId] ?? 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['current_has_prescription'] = $currentRx !== null ? 1 : 0;
|
||||
$item['current_prescription_id'] = $currentRx !== null ? (int) ($currentRx['id'] ?? 0) : 0;
|
||||
$item['prescription_audit_status'] = $currentRx !== null ? (int) ($currentRx['audit_status'] ?? -1) : -1;
|
||||
$item['prescription_void_status'] = $currentRx !== null ? (int) ($currentRx['void_status'] ?? 0) : 0;
|
||||
// 开方次数即复诊次数:1 张处方 = 第 1 次复诊,以此类推
|
||||
$item['followup_prescription_count'] = (int) ($rxCountByDiag[$did] ?? 0);
|
||||
}
|
||||
|
||||
// 最近一条通话记录状态(列表行展示:通话中 / 已结束等)
|
||||
$latestCallByDiag = [];
|
||||
|
||||
@@ -966,7 +966,8 @@ class ConversionLogic
|
||||
* - 统计区间内 add_external_contact,按 (user_id, external_userid) 去重;
|
||||
* - 同一员工在区间开始前已加过该客户的重加不计(企微「添加时间」仍是首次跟进时间,
|
||||
* 删后再加会再推 add_external_contact,但不能当当天新客,否则会跨日重复计);
|
||||
* - 加粉之后、统计结束前须有 msg_audit_approved(排除未完成链路的幽灵事件);
|
||||
* - add_external_contact 是企微确认客户关系已建立后的权威事件;会话存档同意
|
||||
* msg_audit_approved 属于独立能力,不能作为加粉前置条件,否则未开通会话存档的员工会被整批清零;
|
||||
* - 加粉之后、统计结束前若出现 del_external_contact(客户从企微侧删除)则不计。
|
||||
* 不扣减 del_follow_user:企微客户列表中改名带「删」的客户往往仍在列表中;
|
||||
* - 剔除非投放加粉:跟进人 add_way∈{1 扫一扫, 2 搜索手机号, 3 名片分享};
|
||||
@@ -993,7 +994,7 @@ class ConversionLogic
|
||||
}
|
||||
}
|
||||
|
||||
$baseKey = self::requestRowsCacheKey('fans-effective-v6', [
|
||||
$baseKey = self::requestRowsCacheKey('fans-effective-v7', [
|
||||
$startTimestamp,
|
||||
$endTimestamp,
|
||||
self::mediaChannelCacheKey($mediaChannel),
|
||||
@@ -1035,20 +1036,11 @@ class ConversionLogic
|
||||
$query = Db::name('qywx_external_contact_event')
|
||||
->alias('e')
|
||||
->where('e.change_type', 'add_external_contact')
|
||||
->where('e.event_time', 'between', [$startTimestamp, $endTimestamp])
|
||||
->where('e.user_id', '<>', '')
|
||||
->where('e.external_userid', '<>', '')
|
||||
->whereRaw(
|
||||
'EXISTS (SELECT 1 FROM `' . $eventTable . '` audit_e'
|
||||
. ' WHERE audit_e.user_id = e.user_id'
|
||||
. ' AND audit_e.external_userid = e.external_userid'
|
||||
. ' AND audit_e.change_type = ?'
|
||||
. ' AND audit_e.event_time >= e.event_time'
|
||||
. ' AND audit_e.event_time <= ?)',
|
||||
['msg_audit_approved', $endTimestamp]
|
||||
)
|
||||
->whereRaw(
|
||||
'NOT EXISTS (SELECT 1 FROM `' . $eventTable . '` del_e'
|
||||
->where('e.event_time', 'between', [$startTimestamp, $endTimestamp])
|
||||
->where('e.user_id', '<>', '')
|
||||
->where('e.external_userid', '<>', '')
|
||||
->whereRaw(
|
||||
'NOT EXISTS (SELECT 1 FROM `' . $eventTable . '` del_e'
|
||||
. ' WHERE del_e.user_id = e.user_id'
|
||||
. ' AND del_e.external_userid = e.external_userid'
|
||||
. ' AND del_e.change_type = ?'
|
||||
|
||||
@@ -855,68 +855,77 @@ class DiagnosisLogic extends BaseLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取通话签名
|
||||
* @param array $params
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function getCallSignature(array $params)
|
||||
{
|
||||
try {
|
||||
// 获取配置
|
||||
$config = self::getTrtcConfig();
|
||||
|
||||
if (!$config) {
|
||||
self::setError('请先配置腾讯云TRTC参数');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取当前管理员ID(从参数中获取)
|
||||
$adminId = $params['admin_id'] ?? 0;
|
||||
$patientId = $params['patient_id'] ?? 0;
|
||||
|
||||
if (!$adminId) {
|
||||
self::setError('获取管理员信息失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$patientId) {
|
||||
self::setError('获取患者信息失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 医生userId
|
||||
$doctorUserId = 'doctor_' . $adminId;
|
||||
$query = Diagnosis::where('id', $patientId)
|
||||
->where('delete_time', null)->find();
|
||||
if(!$query){
|
||||
self::setError('患者诊单已被删除');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 患者userId(必须与小程序端一致)
|
||||
$patientUserId = 'patient_' . $patientId;
|
||||
|
||||
// 生成医生的 UserSig
|
||||
$userSig = self::generateUserSig($config['sdkAppId'], $config['secretKey'], $doctorUserId);
|
||||
|
||||
if (!$userSig) {
|
||||
self::setError('生成签名失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 导入医生账号到IM
|
||||
self::importDoctorAccountToIm($adminId, $doctorUserId);
|
||||
|
||||
// 确保患者账号也已导入IM(用于跨平台通话)
|
||||
self::ensurePatientImAccount($patientId, $patientUserId);
|
||||
|
||||
return [
|
||||
'sdkAppId' => (int)$config['sdkAppId'], // 确保返回整数
|
||||
'userId' => $doctorUserId, // 医生的userId
|
||||
'userSig' => $userSig,
|
||||
'assistant_id'=>$query->assistant_id?'doctor_'.$query->assistant_id:'',
|
||||
'patientUserId' => $patientUserId, // 患者的userId(用于发起通话)
|
||||
'expireTime' => 86400, // 24小时
|
||||
* @notes 获取通话签名
|
||||
* @param array $params
|
||||
* @param array $adminInfo
|
||||
* @return array|bool
|
||||
*/
|
||||
public static function getCallSignature(array $params, array $adminInfo = [])
|
||||
{
|
||||
try {
|
||||
$adminId = (int) ($params['admin_id'] ?? 0);
|
||||
$diagnosisId = (int) ($params['diagnosis_id'] ?? 0);
|
||||
$patientId = (int) ($params['patient_id'] ?? 0);
|
||||
|
||||
if ($adminId <= 0) {
|
||||
self::setError('获取管理员信息失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($diagnosisId <= 0 || $patientId <= 0) {
|
||||
self::setError('诊单或患者信息不完整');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Authorize the diagnosis before reading TRTC configuration or importing IM accounts.
|
||||
if (!self::canManageDiagnosis($diagnosisId, $adminId, $adminInfo)) {
|
||||
self::setError('诊单不存在、患者不匹配或无权访问');
|
||||
return false;
|
||||
}
|
||||
|
||||
$diagnosis = Diagnosis::where('id', $diagnosisId)
|
||||
->where('patient_id', $patientId)
|
||||
->whereNull('delete_time')
|
||||
->where('status', 1)
|
||||
->find();
|
||||
if (!$diagnosis) {
|
||||
self::setError('诊单不存在、患者不匹配或无权访问');
|
||||
return false;
|
||||
}
|
||||
|
||||
$config = self::getTrtcConfig();
|
||||
if (!$config) {
|
||||
self::setError('请先配置腾讯云TRTC参数');
|
||||
return false;
|
||||
}
|
||||
|
||||
$doctorUserId = 'doctor_' . $adminId;
|
||||
// 患者userId(必须与小程序端一致)
|
||||
$patientUserId = 'patient_' . $patientId;
|
||||
|
||||
// 生成医生的 UserSig
|
||||
$userSig = self::generateUserSig($config['sdkAppId'], $config['secretKey'], $doctorUserId);
|
||||
|
||||
if (!$userSig) {
|
||||
self::setError('生成签名失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 导入医生账号到IM
|
||||
self::importDoctorAccountToIm($adminId, $doctorUserId);
|
||||
|
||||
// 确保患者账号也已导入IM(用于跨平台通话)
|
||||
self::ensurePatientImAccount($patientId, $patientUserId);
|
||||
|
||||
return [
|
||||
'sdkAppId' => (int)$config['sdkAppId'], // 确保返回整数
|
||||
'userId' => $doctorUserId, // 医生的userId
|
||||
'userSig' => $userSig,
|
||||
'assistant_id' => $diagnosis->assistant_id ? 'doctor_' . $diagnosis->assistant_id : '',
|
||||
'patientUserId' => $patientUserId, // 患者的userId(用于发起通话)
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'patient_id' => $patientId,
|
||||
'expireTime' => 86400, // 24小时
|
||||
// 与 .env [trtc] ISLOCHOSTVOD 一致:true 允许浏览器本地录制并上传
|
||||
'isLochostVod' => (bool)config('trtc.is_lochost_vod', false),
|
||||
];
|
||||
@@ -1645,12 +1654,13 @@ class DiagnosisLogic extends BaseLogic
|
||||
* @param array $params
|
||||
* @return bool
|
||||
*/
|
||||
public static function endCall(array $params): bool
|
||||
{
|
||||
try {
|
||||
// 获取当前管理员ID(从参数中获取)
|
||||
$adminId = (int)($params['admin_id'] ?? 0);
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
public static function endCall(array $params): bool
|
||||
{
|
||||
try {
|
||||
// 获取当前管理员ID(从参数中获取)
|
||||
$adminId = (int)($params['admin_id'] ?? 0);
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
$callRecordId = (int)($params['call_record_id'] ?? 0);
|
||||
|
||||
if ($adminId <= 0) {
|
||||
self::setError('获取管理员信息失败');
|
||||
@@ -1661,26 +1671,47 @@ class DiagnosisLogic extends BaseLogic
|
||||
return false;
|
||||
}
|
||||
|
||||
// 优先匹配「当前医生 + 进行中」,与 startCloudRecording / bindCallRoom 一致
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('caller_id', $adminId)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
if (!$record) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
if ($record) {
|
||||
\think\facade\Log::warning('endCall: 未匹配 caller_id,已回退到该诊单最新进行中记录', [
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'admin_id' => $adminId,
|
||||
'record_caller_id' => $record['caller_id'] ?? null,
|
||||
'call_record_id' => $record['id'] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$record = null;
|
||||
if ($callRecordId > 0) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('id', $callRecordId)
|
||||
->where('diagnosis_id', $diagnosisId)
|
||||
->where('caller_id', $adminId)
|
||||
->where('caller_type', 'doctor')
|
||||
->find();
|
||||
if (!$record) {
|
||||
self::setError('通话记录不存在或无权操作');
|
||||
return false;
|
||||
}
|
||||
if ((int)($record['status'] ?? 0) === 2) {
|
||||
// afterCalling / Store idle may report the same exact call twice.
|
||||
return true;
|
||||
}
|
||||
if ((int)($record['status'] ?? 0) !== 1) {
|
||||
self::setError('通话记录状态不可结束');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Legacy web callers may not yet send call_record_id.
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('caller_id', $adminId)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
if (!$record) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
if ($record) {
|
||||
\think\facade\Log::warning('endCall: 未传 call_record_id 且未匹配 caller_id,已回退到该诊单最新进行中记录', [
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'admin_id' => $adminId,
|
||||
'record_caller_id' => $record['caller_id'] ?? null,
|
||||
'call_record_id' => $record['id'] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$record) {
|
||||
// 前端常重复回调 endCall(afterCalling + Store idle),第一条已结束则不再告警
|
||||
@@ -1861,9 +1892,20 @@ class DiagnosisLogic extends BaseLogic
|
||||
if (is_array($decoded)) {
|
||||
$urls = $decoded;
|
||||
}
|
||||
}
|
||||
}
|
||||
$record['recording_urls_list'] = $urls;
|
||||
$record['recording_status_text'] = self::recordingStatusText((int)($record['recording_status'] ?? 0));
|
||||
$localAudioUrls = [];
|
||||
if (!empty($record['local_audio_urls'])) {
|
||||
$decodedLocalAudioUrls = json_decode((string)$record['local_audio_urls'], true);
|
||||
if (is_array($decodedLocalAudioUrls)) {
|
||||
$localAudioUrls = $decodedLocalAudioUrls;
|
||||
}
|
||||
}
|
||||
$record['local_audio_urls_list'] = $localAudioUrls;
|
||||
$record['local_audio_status_text'] = self::localAudioStatusText(
|
||||
(int)($record['local_audio_status'] ?? 0)
|
||||
);
|
||||
$record['transcription_status_text'] = self::transcriptionStatusText(
|
||||
(string)($record['transcription_status'] ?? '')
|
||||
);
|
||||
@@ -2184,11 +2226,12 @@ class DiagnosisLogic extends BaseLogic
|
||||
* @notes 将 TRTC 房间号写入当前诊单通话记录,并尝试 API 合流云端录制
|
||||
* @return array{cloud_recording?:array}|false 成功返回 data 数组(供接口带给前端);失败 false
|
||||
*/
|
||||
public static function bindCallRoom(array $params)
|
||||
{
|
||||
try {
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
$roomId = trim((string)($params['room_id'] ?? ''));
|
||||
public static function bindCallRoom(array $params)
|
||||
{
|
||||
try {
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
$callRecordId = (int)($params['call_record_id'] ?? 0);
|
||||
$roomId = trim((string)($params['room_id'] ?? ''));
|
||||
if ($diagnosisId <= 0 || $roomId === '') {
|
||||
self::setError('诊单ID或房间号不能为空');
|
||||
return false;
|
||||
@@ -2196,24 +2239,34 @@ class DiagnosisLogic extends BaseLogic
|
||||
|
||||
$adminId = (int)($params['admin_id'] ?? 0);
|
||||
|
||||
// 必须与 startCloudRecording 使用同一条「进行中 + 当前管理员」记录写 room_id,否则会写到别的记录上,合流 API 读到 room_id 仍为空 → 关闭全局录制后无任何文件
|
||||
$record = null;
|
||||
if ($adminId > 0) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('status', 1)
|
||||
->where('caller_id', $adminId)
|
||||
$record = null;
|
||||
if ($callRecordId > 0) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('id', $callRecordId)
|
||||
->where('diagnosis_id', $diagnosisId)
|
||||
->where('status', 1)
|
||||
->where('caller_id', $adminId)
|
||||
->where('caller_type', 'doctor')
|
||||
->find();
|
||||
if (!$record) {
|
||||
self::setError('通话记录不存在或无权绑定房间');
|
||||
return false;
|
||||
}
|
||||
} elseif ($adminId > 0) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('status', 1)
|
||||
->where('caller_id', $adminId)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
}
|
||||
if (!$record) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('status', 1)
|
||||
if (!$record && $callRecordId <= 0) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
}
|
||||
if (!$record) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('room_id', '')
|
||||
if (!$record && $callRecordId <= 0) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('room_id', '')
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
}
|
||||
@@ -2233,10 +2286,11 @@ class DiagnosisLogic extends BaseLogic
|
||||
'message' => '未尝试合流录制(admin_id 为空)',
|
||||
];
|
||||
if ($adminId > 0) {
|
||||
$cloudRec = self::startCloudRecording([
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'admin_id' => $adminId,
|
||||
], true);
|
||||
$cloudRec = self::startCloudRecording([
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'admin_id' => $adminId,
|
||||
'call_record_id' => (int)$record['id'],
|
||||
], true);
|
||||
if ($cloudRec === false) {
|
||||
\think\facade\Log::warning('bindCallRoom: startCloudRecording 未执行或异常', [
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
@@ -2286,10 +2340,11 @@ class DiagnosisLogic extends BaseLogic
|
||||
*/
|
||||
public static function startCloudRecording(array $params, bool $silent = false)
|
||||
{
|
||||
try {
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
$adminId = (int)($params['admin_id'] ?? 0);
|
||||
if ($diagnosisId <= 0 || $adminId <= 0) {
|
||||
try {
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
$adminId = (int)($params['admin_id'] ?? 0);
|
||||
$callRecordId = (int)($params['call_record_id'] ?? 0);
|
||||
if ($diagnosisId <= 0 || $adminId <= 0) {
|
||||
if (!$silent) {
|
||||
self::setError('参数错误');
|
||||
}
|
||||
@@ -2297,24 +2352,40 @@ class DiagnosisLogic extends BaseLogic
|
||||
return false;
|
||||
}
|
||||
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('caller_id', $adminId)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
if (!$record) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
if ($record) {
|
||||
\think\facade\Log::warning('startCloudRecording: 未找到 caller_id 匹配的进行中记录,已回退到该诊单最新进行中记录', [
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'admin_id' => $adminId,
|
||||
'record_caller_id' => $record['caller_id'] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$record = null;
|
||||
if ($callRecordId > 0) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('id', $callRecordId)
|
||||
->where('diagnosis_id', $diagnosisId)
|
||||
->where('caller_id', $adminId)
|
||||
->where('caller_type', 'doctor')
|
||||
->where('status', 1)
|
||||
->find();
|
||||
if (!$record) {
|
||||
if (!$silent) {
|
||||
self::setError('通话记录不存在或无权开启云端录制');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('caller_id', $adminId)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
if (!$record) {
|
||||
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
if ($record) {
|
||||
\think\facade\Log::warning('startCloudRecording: 未传 call_record_id 且未找到 caller_id 匹配的进行中记录,已回退到该诊单最新进行中记录', [
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'admin_id' => $adminId,
|
||||
'record_caller_id' => $record['caller_id'] ?? null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$record) {
|
||||
if (!$silent) {
|
||||
self::setError('没有进行中的通话记录');
|
||||
@@ -2389,9 +2460,9 @@ class DiagnosisLogic extends BaseLogic
|
||||
/**
|
||||
* @notes 医生端浏览器本地录制上传后,将文件访问地址合并写入当前诊单下该医生的最近一条通话记录
|
||||
*/
|
||||
public static function attachLocalCallRecording(array $params): bool
|
||||
{
|
||||
try {
|
||||
public static function attachLocalCallRecording(array $params): bool
|
||||
{
|
||||
try {
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
$adminId = (int)($params['admin_id'] ?? 0);
|
||||
$fileUrl = trim((string)($params['file_url'] ?? ''));
|
||||
@@ -2428,12 +2499,61 @@ class DiagnosisLogic extends BaseLogic
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 诊单回放视频分片上传,完成后显式关联到指定通话记录
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将医生工作站本机录音独立关联到通话记录。
|
||||
*
|
||||
* 本机音频与腾讯云混流视频分别持久化,避免录音上传成功后被误计为
|
||||
* 云端视频已生成。
|
||||
*/
|
||||
public static function attachLocalCallAudio(array $params): bool
|
||||
{
|
||||
try {
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
$adminId = (int)($params['admin_id'] ?? 0);
|
||||
$fileUrl = trim((string)($params['file_url'] ?? ''));
|
||||
$callRecordId = (int)($params['call_record_id'] ?? 0);
|
||||
if ($diagnosisId <= 0 || $adminId <= 0 || $fileUrl === '') {
|
||||
self::setError('参数错误');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$record = self::resolveCallRecordForAttachment($diagnosisId, $adminId, $callRecordId);
|
||||
if (!$record) {
|
||||
self::setError('未找到通话记录');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$previousUrls = [];
|
||||
if (!empty($record->local_audio_urls)) {
|
||||
$decodedUrls = json_decode((string)$record->local_audio_urls, true);
|
||||
if (is_array($decodedUrls)) {
|
||||
$previousUrls = $decodedUrls;
|
||||
}
|
||||
}
|
||||
$mergedUrls = array_values(array_unique(array_merge($previousUrls, [$fileUrl])));
|
||||
|
||||
$record->save([
|
||||
'local_audio_urls' => json_encode($mergedUrls, JSON_UNESCAPED_UNICODE),
|
||||
'local_audio_status' => 2,
|
||||
'update_time' => time(),
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 诊单回放视频分片上传,完成后显式关联到指定通话记录
|
||||
* @param array $params
|
||||
* @return array|false
|
||||
*/
|
||||
@@ -2445,11 +2565,12 @@ class DiagnosisLogic extends BaseLogic
|
||||
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
|
||||
$callRecordId = (int)($params['call_record_id'] ?? 0);
|
||||
$adminId = (int)($params['admin_id'] ?? 0);
|
||||
$uploadId = trim((string)($params['upload_id'] ?? ''));
|
||||
$fileName = trim((string)($params['file_name'] ?? ''));
|
||||
$fileSize = (int)($params['file_size'] ?? 0);
|
||||
$chunkIndex = (int)($params['chunk_index'] ?? -1);
|
||||
$chunkTotal = (int)($params['chunk_total'] ?? 0);
|
||||
$uploadId = trim((string)($params['upload_id'] ?? ''));
|
||||
$fileName = trim((string)($params['file_name'] ?? ''));
|
||||
$mimeType = strtolower(trim((string)($params['mime_type'] ?? '')));
|
||||
$fileSize = (int)($params['file_size'] ?? 0);
|
||||
$chunkIndex = (int)($params['chunk_index'] ?? -1);
|
||||
$chunkTotal = (int)($params['chunk_total'] ?? 0);
|
||||
|
||||
if (
|
||||
$diagnosisId <= 0 ||
|
||||
@@ -2462,13 +2583,27 @@ class DiagnosisLogic extends BaseLogic
|
||||
) {
|
||||
self::setError('上传参数不完整');
|
||||
return false;
|
||||
}
|
||||
|
||||
$ext = strtolower((string)pathinfo($fileName, PATHINFO_EXTENSION));
|
||||
if ($ext === '' || !in_array($ext, config('project.file_video'), true)) {
|
||||
self::setError('视频格式不支持');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$ext = strtolower((string)pathinfo($fileName, PATHINFO_EXTENSION));
|
||||
$audioExtensions = ['webm', 'ogg', 'opus', 'mp3', 'wav', 'm4a', 'aac', 'amr', 'wma'];
|
||||
$videoExtensions = (array)config('project.file_video');
|
||||
$hasAudioMime = str_starts_with($mimeType, 'audio/');
|
||||
$hasVideoMime = str_starts_with($mimeType, 'video/');
|
||||
$isAmbiguousWebm = $ext === 'webm';
|
||||
$isVideo = in_array($ext, $videoExtensions, true)
|
||||
&& (
|
||||
$mimeType === ''
|
||||
|| $hasVideoMime
|
||||
|| (!$isAmbiguousWebm && $hasAudioMime)
|
||||
);
|
||||
$isLocalAudio = !$isVideo
|
||||
&& $hasAudioMime
|
||||
&& in_array($ext, $audioExtensions, true);
|
||||
if (!$isLocalAudio && !$isVideo) {
|
||||
self::setError('音视频格式不支持');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($callRecordId <= 0) {
|
||||
$record = self::createSyntheticCallRecord($diagnosisId, $adminId, $fileName);
|
||||
@@ -2497,10 +2632,12 @@ class DiagnosisLogic extends BaseLogic
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'call_record_id' => $callRecordId,
|
||||
'admin_id' => $adminId,
|
||||
'file_name' => $fileName,
|
||||
'file_size' => $fileSize,
|
||||
'chunk_total' => $chunkTotal,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
'file_name' => $fileName,
|
||||
'file_size' => $fileSize,
|
||||
'mime_type' => $mimeType,
|
||||
'media_kind' => $isLocalAudio ? 'local_audio' : 'video',
|
||||
'chunk_total' => $chunkTotal,
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
|
||||
$chunkPath = $uploadDir . DIRECTORY_SEPARATOR . self::callRecordingChunkName($chunkIndex);
|
||||
$moved = $chunkFile->move($uploadDir, basename($chunkPath));
|
||||
@@ -2527,21 +2664,29 @@ class DiagnosisLogic extends BaseLogic
|
||||
return false;
|
||||
}
|
||||
|
||||
$uploadResult = self::storeMergedCallRecording($mergedPath, $fileName, $adminId);
|
||||
if (empty($uploadResult['uri'])) {
|
||||
self::setError('保存视频失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
$attached = self::attachLocalCallRecording([
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'call_record_id' => $callRecordId,
|
||||
'admin_id' => $adminId,
|
||||
'file_url' => (string)$uploadResult['uri'],
|
||||
]);
|
||||
if (!$attached) {
|
||||
return false;
|
||||
}
|
||||
$uploadResult = self::storeMergedCallRecording(
|
||||
$mergedPath,
|
||||
$fileName,
|
||||
$adminId,
|
||||
$isLocalAudio
|
||||
);
|
||||
if (empty($uploadResult['uri'])) {
|
||||
self::setError($isLocalAudio ? '保存本机录音失败' : '保存视频失败');
|
||||
return false;
|
||||
}
|
||||
|
||||
$attachmentParams = [
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'call_record_id' => $callRecordId,
|
||||
'admin_id' => $adminId,
|
||||
'file_url' => (string)$uploadResult['uri'],
|
||||
];
|
||||
$attached = $isLocalAudio
|
||||
? self::attachLocalCallAudio($attachmentParams)
|
||||
: self::attachLocalCallRecording($attachmentParams);
|
||||
if (!$attached) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self::cleanupCallRecordingChunkDir($uploadDir);
|
||||
|
||||
@@ -2550,10 +2695,12 @@ class DiagnosisLogic extends BaseLogic
|
||||
'uploaded_chunks' => $chunkTotal,
|
||||
'chunk_total' => $chunkTotal,
|
||||
'call_record_id' => $callRecordId,
|
||||
'file_id' => $uploadResult['id'] ?? 0,
|
||||
'file_url' => $uploadResult['uri'] ?? '',
|
||||
'recording_status' => 2,
|
||||
];
|
||||
'file_id' => $uploadResult['id'] ?? 0,
|
||||
'file_url' => $uploadResult['uri'] ?? '',
|
||||
'media_kind' => $isLocalAudio ? 'local_audio' : 'video',
|
||||
'recording_status' => $isLocalAudio ? 0 : 2,
|
||||
'local_audio_status' => $isLocalAudio ? 2 : 0,
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
if ($mergedPath !== '' && is_file($mergedPath)) {
|
||||
@unlink($mergedPath);
|
||||
@@ -2656,19 +2803,31 @@ class DiagnosisLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
private static function recordingStatusText(int $status): string
|
||||
{
|
||||
$map = [
|
||||
private static function recordingStatusText(int $status): string
|
||||
{
|
||||
$map = [
|
||||
0 => '无录制',
|
||||
1 => '录制中',
|
||||
2 => '已生成',
|
||||
3 => '录制失败',
|
||||
];
|
||||
|
||||
return $map[$status] ?? '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
return $map[$status] ?? '未知';
|
||||
}
|
||||
|
||||
private static function localAudioStatusText(int $status): string
|
||||
{
|
||||
$map = [
|
||||
0 => '无本地录音',
|
||||
1 => '上传中',
|
||||
2 => '已保存',
|
||||
3 => '上传失败',
|
||||
];
|
||||
|
||||
return $map[$status] ?? '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 获取TRTC配置
|
||||
* @return array|null
|
||||
*/
|
||||
@@ -4228,12 +4387,52 @@ class DiagnosisLogic extends BaseLogic
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 readonlyDetail 共用的诊单行级可见性入口。
|
||||
* 与诊单列表共用语义的只读行级可见性入口。
|
||||
*
|
||||
* 复用“我的患者”统一行权策略:医生按有效接诊关系,医助按归属关系,
|
||||
* 团队管理角色才使用 DataScope。不存在与越权使用同一错误避免枚举。
|
||||
* 医助仍仅能查看本人归属诊单;其他角色按 DataScope 查看列表范围内的诊单。
|
||||
* “我的患者”的本人接诊关系只用于管理/写操作,不能限制通用诊单只读页。
|
||||
* 不存在与越权使用同一错误,避免枚举诊单。
|
||||
*/
|
||||
public static function canViewReadonlyDiagnosis(int $diagnosisId, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
self::$error = '';
|
||||
if ($diagnosisId <= 0 || $adminId <= 0) {
|
||||
self::setError('诊单不存在或无权访问');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$query = 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)) {
|
||||
$query->where('assistant_id', $adminId);
|
||||
}
|
||||
|
||||
if (DataScopeService::isEnabled()) {
|
||||
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
if ($visibleIds === []) {
|
||||
self::setError('诊单不存在或无权访问');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (is_array($visibleIds)) {
|
||||
$query->whereIn('assistant_id', $visibleIds);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$query->find()) {
|
||||
self::setError('诊单不存在或无权访问');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊单管理/写操作的行级权限入口,保留“我的患者”的本人关系约束。
|
||||
*/
|
||||
public static function canManageDiagnosis(int $diagnosisId, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
self::$error = '';
|
||||
if (!MyPatientLogic::canAccessDiagnosis($diagnosisId, $adminId, $adminInfo)) {
|
||||
@@ -4526,25 +4725,33 @@ class DiagnosisLogic extends BaseLogic
|
||||
}
|
||||
}
|
||||
|
||||
private static function storeMergedCallRecording(string $mergedPath, string $fileName, int $adminId): array
|
||||
{
|
||||
$config = [
|
||||
'default' => ConfigService::get('storage', 'default', 'local'),
|
||||
private static function storeMergedCallRecording(
|
||||
string $mergedPath,
|
||||
string $fileName,
|
||||
int $adminId,
|
||||
bool $isLocalAudio = false
|
||||
): array
|
||||
{
|
||||
$config = [
|
||||
'default' => ConfigService::get('storage', 'default', 'local'),
|
||||
'engine' => ConfigService::get('storage') ?? ['local' => []],
|
||||
];
|
||||
|
||||
$storageDriver = new StorageDriver($config);
|
||||
$storageDriver->setUploadFileByReal($mergedPath);
|
||||
$saveDir = 'uploads/video/' . date('Ymd');
|
||||
if (!$storageDriver->upload($saveDir)) {
|
||||
throw new \RuntimeException($storageDriver->getError() ?: '上传视频到存储失败');
|
||||
}
|
||||
|
||||
$relativePath = $saveDir . '/' . str_replace('\\', '/', $storageDriver->getFileName());
|
||||
$storedFile = FileModel::create([
|
||||
'cid' => 0,
|
||||
'type' => FileEnum::VIDEO_TYPE,
|
||||
'name' => mb_substr($fileName, 0, 128),
|
||||
|
||||
$storageDriver = new StorageDriver($config);
|
||||
$storageDriver->setUploadFileByReal($mergedPath);
|
||||
$saveDir = ($isLocalAudio ? 'uploads/audio/' : 'uploads/video/') . date('Ymd');
|
||||
if (!$storageDriver->upload($saveDir)) {
|
||||
throw new \RuntimeException(
|
||||
$storageDriver->getError()
|
||||
?: ($isLocalAudio ? '上传本机录音到存储失败' : '上传视频到存储失败')
|
||||
);
|
||||
}
|
||||
|
||||
$relativePath = $saveDir . '/' . str_replace('\\', '/', $storageDriver->getFileName());
|
||||
$storedFile = FileModel::create([
|
||||
'cid' => 0,
|
||||
'type' => $isLocalAudio ? FileEnum::FILE_TYPE : FileEnum::VIDEO_TYPE,
|
||||
'name' => mb_substr($fileName, 0, 128),
|
||||
'uri' => $relativePath,
|
||||
'source' => FileEnum::SOURCE_ADMIN,
|
||||
'source_id' => $adminId,
|
||||
|
||||
@@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\tcm;
|
||||
|
||||
use app\adminapi\logic\firstvisit\MyPatientLogic;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\doctor\Appointment;
|
||||
use app\common\model\doctor\Medicine as DoctorMedicine;
|
||||
@@ -938,8 +937,8 @@ class PrescriptionLogic
|
||||
public static function listByDiagnosis(int $diagnosisId, int $viewerAdminId, array $viewerAdminInfo): array
|
||||
{
|
||||
self::$error = '';
|
||||
if (!MyPatientLogic::canAccessDiagnosis($diagnosisId, $viewerAdminId, $viewerAdminInfo)) {
|
||||
self::setError('诊单不存在或无权访问');
|
||||
if (!DiagnosisLogic::canViewReadonlyDiagnosis($diagnosisId, $viewerAdminId, $viewerAdminInfo)) {
|
||||
self::setError(DiagnosisLogic::getError() ?: '诊单不存在或无权访问');
|
||||
|
||||
return [];
|
||||
}
|
||||
@@ -968,16 +967,29 @@ class PrescriptionLogic
|
||||
/**
|
||||
* 根据预约ID获取处方(带权限检查)
|
||||
*/
|
||||
public static function getByAppointment(int $appointmentId, int $viewerAdminId, array $viewerAdminInfo): ?array
|
||||
{
|
||||
public static function getByAppointment(int $appointmentId, int $viewerAdminId, array $viewerAdminInfo): ?array
|
||||
{
|
||||
self::$error = '';
|
||||
$row = Prescription::where('appointment_id', $appointmentId)
|
||||
$rows = Prescription::where('appointment_id', $appointmentId)
|
||||
->whereNull('delete_time')
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
|
||||
if (!$row) {
|
||||
return null;
|
||||
->select();
|
||||
|
||||
$row = null;
|
||||
$fallback = null;
|
||||
foreach ($rows as $candidate) {
|
||||
if ($fallback === null) {
|
||||
$fallback = $candidate;
|
||||
}
|
||||
if ((int) ($candidate->void_status ?? 0) === 0) {
|
||||
$row = $candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$row = $row ?? $fallback;
|
||||
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 权限检查:只返回当前用户有权限查看的处方
|
||||
|
||||
@@ -133,12 +133,21 @@ class DiagnosisValidate extends BaseValidate
|
||||
}
|
||||
|
||||
/** 拉取跟踪备注列表:诊单ID */
|
||||
public function sceneTrackingNotes()
|
||||
{
|
||||
return $this->only(['diagnosis_id']);
|
||||
}
|
||||
|
||||
public function sceneGenerateQrcode()
|
||||
public function sceneTrackingNotes()
|
||||
{
|
||||
return $this->only(['diagnosis_id']);
|
||||
}
|
||||
|
||||
/** IM / video identity: validate shape here; ownership is checked in the logic layer. */
|
||||
public function sceneCallIdentity()
|
||||
{
|
||||
return $this->only(['diagnosis_id', 'patient_id'])
|
||||
->remove('diagnosis_id', 'checkDiagnosisId')
|
||||
->append('diagnosis_id', 'gt:0')
|
||||
->append('patient_id', 'require|integer|gt:0');
|
||||
}
|
||||
|
||||
public function sceneGenerateQrcode()
|
||||
{
|
||||
return $this->only(['diagnosis_id', 'doctor_id', 'patient_id', 'share_user_id', 'mini_program_path'])
|
||||
// The global diagnosis_id rule is required for diagnosis APIs, but
|
||||
|
||||
Reference in New Issue
Block a user