1266 lines
46 KiB
PHP
1266 lines
46 KiB
PHP
<?php
|
||
|
||
namespace app\adminapi\logic\doctor;
|
||
|
||
use app\common\logic\BaseLogic;
|
||
use app\common\model\doctor\Appointment;
|
||
use app\common\model\doctor\Roster;
|
||
use app\common\service\doctor\RosterSegmentService;
|
||
use app\common\model\tcm\Diagnosis;
|
||
use app\common\model\tcm\DiagnosisGuahaoLog;
|
||
use app\common\model\auth\Admin;
|
||
use app\common\model\auth\AdminRole;
|
||
use app\common\service\DataScope\DataScopeService;
|
||
use app\api\logic\ChatNotifyLogic;
|
||
use app\adminapi\logic\tcm\PrescriptionLogic;
|
||
use app\adminapi\logic\tcm\DiagnosisLogic;
|
||
use app\adminapi\logic\tcm\BloodRecordLogic;
|
||
use app\adminapi\logic\tcm\TrackingNoteLogic;
|
||
use app\adminapi\logic\doctor\DoctorNoteLogic;
|
||
use app\common\model\tcm\Prescription;
|
||
use app\common\service\wechat\WechatWorkAppMessageService;
|
||
use app\common\model\dict\DictData;
|
||
use think\facade\Db;
|
||
use think\facade\Log;
|
||
|
||
/**
|
||
* 医生预约逻辑
|
||
* Class AppointmentLogic
|
||
* @package app\adminapi\logic\doctor
|
||
*/
|
||
class AppointmentLogic extends BaseLogic
|
||
{
|
||
/** 仅这些字典名称需填写 channel_source_detail(与 admin 预约弹窗白名单一致,按 name 全等) */
|
||
private const CHANNEL_NAMES_REQUIRING_SOURCE_DETAIL = [
|
||
'自媒体4H',
|
||
'自媒体3Q',
|
||
'自媒体3H',
|
||
'自媒体2H',
|
||
'自媒体2Q',
|
||
];
|
||
|
||
private static function channelDictNameRequiresSourceDetail(string $name): bool
|
||
{
|
||
return in_array($name, self::CHANNEL_NAMES_REQUIRING_SOURCE_DETAIL, true);
|
||
}
|
||
|
||
/**
|
||
* @param array<int|string, mixed> $cols Db::name('doctor_appointment')->getTableFields()
|
||
*/
|
||
private static function assertAppointmentChannelWritable(array $cols, string $chSrc): ?string
|
||
{
|
||
$hasChannelSource = in_array('channel_source', $cols, true);
|
||
$hasChannels = in_array('channels', $cols, true);
|
||
if (!$hasChannelSource && !$hasChannels) {
|
||
return '挂号表缺少渠道字段(channel_source 或 channels),无法保存渠道来源';
|
||
}
|
||
if (!$hasChannelSource && $hasChannels && $chSrc !== '' && !is_numeric($chSrc)) {
|
||
return '当前挂号表仅有数值型渠道字段 channels,无法写入该渠道;请在数据库增加 channel_source 列';
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* @param array<string, mixed> $row
|
||
* @param array<int|string, mixed> $cols
|
||
*/
|
||
private static function mergeAppointmentChannelIntoRow(array &$row, array $cols, string $chSrc, string $chDetail): void
|
||
{
|
||
$hasChannelSource = in_array('channel_source', $cols, true);
|
||
$hasChannelDetail = in_array('channel_source_detail', $cols, true);
|
||
$hasChannels = in_array('channels', $cols, true);
|
||
if ($hasChannelSource) {
|
||
$row['channel_source'] = $chSrc;
|
||
}
|
||
if ($hasChannelDetail) {
|
||
$row['channel_source_detail'] = $chDetail;
|
||
}
|
||
if ($hasChannels && is_numeric($chSrc)) {
|
||
$row['channels'] = (int) $chSrc;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param array<string, mixed> $row
|
||
* @param array<int|string, mixed> $cols
|
||
*
|
||
* @return array<string, mixed>
|
||
*/
|
||
private static function filterAppointmentRowByExistingColumns(array $row, array $cols): array
|
||
{
|
||
$out = [];
|
||
foreach ($row as $k => $v) {
|
||
if (in_array((string) $k, $cols, true)) {
|
||
$out[$k] = $v;
|
||
}
|
||
}
|
||
|
||
return $out;
|
||
}
|
||
|
||
/**
|
||
* @notes 获取可用时间段
|
||
* @param array $params
|
||
* @return array
|
||
*/
|
||
public static function getAvailableSlots(array $params)
|
||
{
|
||
try {
|
||
$doctorId = $params['doctor_id'];
|
||
$date = $params['appointment_date'];
|
||
$period = $params['period'] ?? 'all';
|
||
|
||
// 记录请求参数
|
||
\think\facade\Log::info('获取可用时段 - 请求参数', [
|
||
'doctor_id' => $doctorId,
|
||
'date' => $date,
|
||
'period' => $period
|
||
]);
|
||
|
||
// 1. 检查医生在该日期是否有出诊排班(只查询status=1的记录)
|
||
$rosters = Roster::where([
|
||
'doctor_id' => $doctorId,
|
||
'date' => $date,
|
||
'status' => 1 // 只查询出诊状态
|
||
])->select()->toArray();
|
||
|
||
// 记录查询到的排班
|
||
\think\facade\Log::info('查询到的排班数据', [
|
||
'doctor_id' => $doctorId,
|
||
'date' => $date,
|
||
'count' => count($rosters),
|
||
'rosters' => $rosters
|
||
]);
|
||
|
||
// 额外记录:查询该日期所有排班(用于调试)
|
||
$allRosters = Roster::where([
|
||
'doctor_id' => $doctorId,
|
||
'date' => $date
|
||
])->select()->toArray();
|
||
\think\facade\Log::info('该日期所有排班数据(包括非出诊)', [
|
||
'count' => count($allRosters),
|
||
'all_rosters' => $allRosters
|
||
]);
|
||
|
||
// 如果没有出诊排班,返回空数组
|
||
if (empty($rosters)) {
|
||
\think\facade\Log::warning('没有找到出诊排班', [
|
||
'doctor_id' => $doctorId,
|
||
'date' => $date
|
||
]);
|
||
return ['slots' => []];
|
||
}
|
||
|
||
// 2. 按每段排班的起止时间与号源间隔生成时间段
|
||
$slots = [];
|
||
|
||
foreach ($rosters as $roster) {
|
||
if ((int) $roster['status'] !== 1) {
|
||
\think\facade\Log::warning('跳过非出诊排班', [
|
||
'roster_id' => $roster['id'],
|
||
'status' => $roster['status'],
|
||
]);
|
||
continue;
|
||
}
|
||
|
||
$window = RosterSegmentService::resolveWindow($roster);
|
||
if ($window === null) {
|
||
\think\facade\Log::warning('无法解析排班起止时间,跳过', [
|
||
'roster_id' => $roster['id'],
|
||
'period' => $roster['period'] ?? null,
|
||
]);
|
||
continue;
|
||
}
|
||
|
||
[$startHm, $endHm] = $window;
|
||
$slotMinutes = RosterSegmentService::normalizeSlotMinutes($roster['slot_minutes'] ?? 15);
|
||
$times = RosterSegmentService::generateSlotTimes($startHm, $endHm, $slotMinutes);
|
||
$times = RosterSegmentService::applyQuotaCap($times, (int) ($roster['quota'] ?? 0));
|
||
|
||
\think\facade\Log::info('处理排班时段', [
|
||
'roster_id' => $roster['id'],
|
||
'window' => $window,
|
||
'slot_minutes' => $slotMinutes,
|
||
'slot_count' => count($times),
|
||
]);
|
||
|
||
foreach ($times as $time) {
|
||
$slots[] = [
|
||
'time' => $time,
|
||
'available' => true,
|
||
'quota' => 1,
|
||
'period' => $roster['period'] ?? 'segment',
|
||
];
|
||
}
|
||
}
|
||
|
||
\think\facade\Log::info('生成的时间段数量(去重前)', [
|
||
'count' => count($slots),
|
||
'sample_slots' => array_slice($slots, 0, 3)
|
||
]);
|
||
|
||
// 如果没有生成任何时间段,返回空数组
|
||
if (empty($slots)) {
|
||
\think\facade\Log::warning('没有生成任何时间段');
|
||
return ['slots' => []];
|
||
}
|
||
|
||
// 3. 去重:按时间去重(因为可能有重复的排班记录)
|
||
$uniqueSlots = [];
|
||
$timeMap = [];
|
||
foreach ($slots as $slot) {
|
||
$time = $slot['time'];
|
||
if (!isset($timeMap[$time])) {
|
||
$timeMap[$time] = true;
|
||
$uniqueSlots[] = $slot;
|
||
}
|
||
}
|
||
$slots = $uniqueSlots;
|
||
|
||
\think\facade\Log::info('生成的时间段数量(去重后)', [
|
||
'count' => count($slots),
|
||
'first_3_slots' => array_slice($slots, 0, 3),
|
||
'last_3_slots' => array_slice($slots, -3),
|
||
]);
|
||
|
||
// 4. 查询已预约的时间段
|
||
$appointmentTimes = Appointment::where([
|
||
'doctor_id' => $doctorId,
|
||
'appointment_date' => $date,
|
||
'status' => 1 // 只查询有效预约
|
||
])->column('appointment_time');
|
||
|
||
// 将时间格式统一为 HH:MM(去掉秒)
|
||
$appointments = array_map(function($time) {
|
||
// 如果是 HH:MM:SS 格式,截取前5位
|
||
return substr($time, 0, 5);
|
||
}, $appointmentTimes);
|
||
|
||
// 4. 标记已占用的时间段
|
||
foreach ($slots as &$slot) {
|
||
if (in_array($slot['time'], $appointments)) {
|
||
$slot['available'] = false;
|
||
$slot['quota'] = 0;
|
||
}
|
||
}
|
||
|
||
// 5. 按时间排序
|
||
usort($slots, function($a, $b) {
|
||
return strcmp($a['time'], $b['time']);
|
||
});
|
||
|
||
return ['slots' => $slots];
|
||
} catch (\Exception $e) {
|
||
self::setError($e->getMessage());
|
||
return ['slots' => []];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @notes 创建预约
|
||
* @param array $params
|
||
* @return array|bool
|
||
*/
|
||
public static function create(array $params, int $operatorAdminId = 0, array $operatorAdminInfo = [])
|
||
{
|
||
try {
|
||
Db::startTrans();
|
||
|
||
// 同一诊单患者在「所选预约日」仅允许一条「已预约」或「已过号」记录(与 appointment_date 一致,不能误用服务器当天拦其它日期)
|
||
$apptDate = trim((string) ($params['appointment_date'] ?? ''));
|
||
if ($apptDate === '') {
|
||
self::setError('预约日期不能为空');
|
||
Db::rollback();
|
||
|
||
return false;
|
||
}
|
||
$patientSameDay = Appointment::where('patient_id', (int) $params['patient_id'])
|
||
->where('appointment_date', $apptDate)
|
||
->whereIn('status', [1, 4])
|
||
->find();
|
||
if ($patientSameDay) {
|
||
self::setError('该患者在所选日期已有挂号(已预约或已过号),无法重复预约');
|
||
Db::rollback();
|
||
|
||
return false;
|
||
}
|
||
|
||
// 1. 检查时间段是否已被预约(每个时间段只能预约1次)
|
||
// 统一时间格式为 HH:MM:SS
|
||
$appointmentTime = strlen($params['appointment_time']) == 5
|
||
? $params['appointment_time'] . ':00'
|
||
: $params['appointment_time'];
|
||
|
||
$exists = Appointment::where([
|
||
'doctor_id' => $params['doctor_id'],
|
||
'appointment_date' => $params['appointment_date'],
|
||
'status' => 1
|
||
])->where('appointment_time', $appointmentTime)
|
||
->find();
|
||
|
||
if ($exists) {
|
||
self::setError('该时间段已被预约');
|
||
Db::rollback();
|
||
return false;
|
||
}
|
||
|
||
$chSrc = trim((string) ($params['channel_source'] ?? ''));
|
||
$chDetail = trim((string) ($params['channel_source_detail'] ?? ''));
|
||
$channelDictName = trim((string) (DictData::where('type_value', 'channels')
|
||
->where('value', $chSrc)
|
||
->value('name') ?? ''));
|
||
if ($channelDictName !== '' && self::channelDictNameRequiresSourceDetail($channelDictName)) {
|
||
if ($chDetail === '') {
|
||
self::setError('请填写自媒体渠道补充信息');
|
||
Db::rollback();
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
$cols = Db::name('doctor_appointment')->getTableFields();
|
||
$cols = is_array($cols) ? $cols : [];
|
||
$channelErr = self::assertAppointmentChannelWritable($cols, $chSrc);
|
||
if ($channelErr !== null) {
|
||
self::setError($channelErr);
|
||
Db::rollback();
|
||
|
||
return false;
|
||
}
|
||
|
||
$insert = [
|
||
'patient_id' => (int) $params['patient_id'],
|
||
'doctor_id' => (int) $params['doctor_id'],
|
||
'appointment_date' => $params['appointment_date'],
|
||
'appointment_time' => $appointmentTime,
|
||
'appointment_type' => $params['appointment_type'] ?? 'video',
|
||
'remark' => $params['remark'] ?? '',
|
||
'status' => 1,
|
||
'create_time' => time(),
|
||
'update_time' => time(),
|
||
];
|
||
if (in_array('roster_id', $cols, true)) {
|
||
$insert['roster_id'] = 0;
|
||
}
|
||
if (in_array('assistant_id', $cols, true)) {
|
||
$insert['assistant_id'] = (int) ($params['assistant_id'] ?? 0);
|
||
}
|
||
$periodVal = $params['period'] ?? 'all';
|
||
if (in_array('period', $cols, true)) {
|
||
$insert['period'] = $periodVal;
|
||
} elseif (in_array('type', $cols, true)) {
|
||
$insert['type'] = $periodVal;
|
||
}
|
||
self::mergeAppointmentChannelIntoRow($insert, $cols, $chSrc, $chDetail);
|
||
|
||
$insert = self::filterAppointmentRowByExistingColumns($insert, $cols);
|
||
$newId = (int) Db::name('doctor_appointment')->insertGetId($insert);
|
||
if ($newId <= 0) {
|
||
self::setError('预约创建失败');
|
||
Db::rollback();
|
||
|
||
return false;
|
||
}
|
||
|
||
Db::commit();
|
||
|
||
$doctorName = (string) (Admin::where('id', (int) $params['doctor_id'])->value('name') ?? '');
|
||
$hm = substr((string) $appointmentTime, 0, 5);
|
||
$summary = sprintf(
|
||
'挂号 #%d:医生「%s」,%s %s',
|
||
$newId,
|
||
$doctorName !== '' ? $doctorName : ('ID:' . (int) $params['doctor_id']),
|
||
(string) $params['appointment_date'],
|
||
$hm
|
||
);
|
||
self::appendDiagnosisGuahaoLog(
|
||
(int) $params['patient_id'],
|
||
$newId,
|
||
$operatorAdminId,
|
||
$operatorAdminInfo,
|
||
'create',
|
||
$summary
|
||
);
|
||
|
||
return ['id' => $newId];
|
||
} catch (\Exception $e) {
|
||
Db::rollback();
|
||
self::setError($e->getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @notes 取消预约
|
||
* @param array $params
|
||
* @return bool
|
||
*/
|
||
public static function cancel(array $params, int $operatorAdminId = 0, array $operatorAdminInfo = [])
|
||
{
|
||
try {
|
||
$appointment = Appointment::findOrEmpty($params['id']);
|
||
if ($appointment->isEmpty()) {
|
||
self::setError('预约记录不存在');
|
||
return false;
|
||
}
|
||
|
||
$st = (int) $appointment->status;
|
||
if ($st === 2) {
|
||
return true;
|
||
}
|
||
if ($st === 3) {
|
||
self::setError('预约已完成,无法取消');
|
||
return false;
|
||
}
|
||
if ($st !== 1 && $st !== 4) {
|
||
self::setError('当前状态不可取消');
|
||
return false;
|
||
}
|
||
|
||
$diagId = (int) $appointment->patient_id;
|
||
$aptId = (int) $appointment->id;
|
||
$doctorId = (int) $appointment->doctor_id;
|
||
$aptDate = (string) ($appointment->appointment_date ?? '');
|
||
$aptTimeRaw = (string) ($appointment->appointment_time ?? '');
|
||
$hm = strlen($aptTimeRaw) >= 5 ? substr($aptTimeRaw, 0, 5) : $aptTimeRaw;
|
||
|
||
$appointment->status = 2; // 已取消
|
||
$appointment->update_time = time();
|
||
$appointment->save();
|
||
|
||
$doctorName = (string) (Admin::where('id', $doctorId)->value('name') ?? '');
|
||
$summary = sprintf(
|
||
'取消挂号 #%d:医生「%s」,%s %s',
|
||
$aptId,
|
||
$doctorName !== '' ? $doctorName : ('ID:' . $doctorId),
|
||
$aptDate,
|
||
$hm
|
||
);
|
||
self::appendDiagnosisGuahaoLog(
|
||
$diagId,
|
||
$aptId,
|
||
$operatorAdminId,
|
||
$operatorAdminInfo,
|
||
'cancel',
|
||
$summary
|
||
);
|
||
|
||
return true;
|
||
} catch (\Exception $e) {
|
||
self::setError($e->getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @notes 预约详情
|
||
* @param array $params
|
||
* @return array
|
||
*/
|
||
public static function detail(array $params)
|
||
{
|
||
$appointment = Appointment::alias('a')
|
||
->leftJoin('zyt_tcm_diagnosis u', 'a.patient_id = u.id')
|
||
->leftJoin('zyt_admin ad', 'a.doctor_id = ad.id')
|
||
->field('a.*, u.patient_name as patient_name, u.phone as patient_phone, ad.name as doctor_name')
|
||
->where('a.id', $params['id'])
|
||
->findOrEmpty()
|
||
->toArray();
|
||
|
||
if (empty($appointment)) {
|
||
return [];
|
||
}
|
||
|
||
// 添加状态和类型描述
|
||
$statusMap = [
|
||
1 => '已预约',
|
||
2 => '已取消',
|
||
3 => '已完成',
|
||
];
|
||
$appointment['status_desc'] = $statusMap[$appointment['status']] ?? '未知';
|
||
|
||
$typeMap = [
|
||
'video' => '视频问诊',
|
||
'text' => '图文问诊',
|
||
'phone' => '电话问诊',
|
||
];
|
||
$appointment['appointment_type_desc'] = $typeMap[$appointment['appointment_type']] ?? '未知';
|
||
|
||
// 格式化时间戳为日期时间
|
||
if (isset($appointment['create_time']) && is_numeric($appointment['create_time'])) {
|
||
$appointment['create_time'] = date('Y-m-d H:i:s', $appointment['create_time']);
|
||
}
|
||
if (isset($appointment['update_time']) && is_numeric($appointment['update_time'])) {
|
||
$appointment['update_time'] = date('Y-m-d H:i:s', $appointment['update_time']);
|
||
}
|
||
|
||
return $appointment;
|
||
}
|
||
|
||
/**
|
||
* @notes 获取医生某天的可用号源总数
|
||
* @param int $doctorId
|
||
* @param string $date
|
||
* @return int
|
||
*/
|
||
public static function getDoctorAvailability($doctorId, $date)
|
||
{
|
||
try {
|
||
$rosters = Roster::where([
|
||
'doctor_id' => $doctorId,
|
||
'date' => $date,
|
||
'status' => 1,
|
||
])->select()->toArray();
|
||
|
||
if (empty($rosters)) {
|
||
return 0;
|
||
}
|
||
|
||
$bookedHm = Appointment::where([
|
||
'doctor_id' => $doctorId,
|
||
'appointment_date' => $date,
|
||
'status' => 1,
|
||
])->column('appointment_time');
|
||
$bookedHm = array_map(static function ($t) {
|
||
return substr((string) $t, 0, 5);
|
||
}, $bookedHm);
|
||
$bookedSet = array_fill_keys($bookedHm, true);
|
||
|
||
$totalAvailable = 0;
|
||
|
||
foreach ($rosters as $roster) {
|
||
$window = RosterSegmentService::resolveWindow($roster);
|
||
if ($window === null) {
|
||
continue;
|
||
}
|
||
[$startHm, $endHm] = $window;
|
||
$slotMinutes = RosterSegmentService::normalizeSlotMinutes($roster['slot_minutes'] ?? 15);
|
||
$times = RosterSegmentService::generateSlotTimes($startHm, $endHm, $slotMinutes);
|
||
$times = RosterSegmentService::applyQuotaCap($times, (int) ($roster['quota'] ?? 0));
|
||
foreach ($times as $t) {
|
||
if (empty($bookedSet[$t])) {
|
||
++$totalAvailable;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $totalAvailable;
|
||
} catch (\Exception $e) {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @notes 完成预约
|
||
* @param array $params
|
||
* @return bool
|
||
*/
|
||
public static function complete(array $params)
|
||
{
|
||
try {
|
||
$appointment = Appointment::findOrEmpty($params['id']);
|
||
if ($appointment->isEmpty()) {
|
||
self::setError('预约记录不存在');
|
||
return false;
|
||
}
|
||
|
||
|
||
if ($appointment->status == 3) {
|
||
self::setError('该预约已处理,无法完成');
|
||
return false;
|
||
}
|
||
|
||
$appointment->status = 3; // 已完成
|
||
$appointment->update_time = time();
|
||
$appointment->save();
|
||
|
||
// 面诊结束:通知对应医助(从诊单 assistant_id 获取)
|
||
try {
|
||
$diagnosis = Diagnosis::where('id', $appointment->patient_id)->find();
|
||
if ($diagnosis && !empty($diagnosis->assistant_id)) {
|
||
$assistantId = (int) $diagnosis->assistant_id;
|
||
$patientName = $diagnosis->patient_name ?? '患者';
|
||
$doctor = Admin::where('id', $appointment->doctor_id)->find();
|
||
$doctorName = $doctor ? $doctor->name : '医生';
|
||
ChatNotifyLogic::addConsultationCompleteNotify(
|
||
$assistantId,
|
||
$patientName,
|
||
$doctorName,
|
||
(int) $appointment->patient_id
|
||
);
|
||
}
|
||
} catch (\Throwable $e) {
|
||
\think\facade\Log::warning('面诊结束通知医助失败: ' . $e->getMessage());
|
||
}
|
||
|
||
// 完成就诊后系统代开处方(无药材、标记 is_system_auto;失败不影响「完成」成功)
|
||
try {
|
||
PrescriptionLogic::createSystemAutoFromCompletedAppointment((int) $appointment->id);
|
||
} catch (\Throwable $e) {
|
||
Log::warning('完成挂号后系统代开处方失败: ' . $e->getMessage());
|
||
}
|
||
|
||
return true;
|
||
} catch (\Exception $e) {
|
||
self::setError($e->getMessage());
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @notes 接诊台聚合:单次返回挂号信息 + 诊单病例 + 血糖血压记录 + 助理姓名
|
||
* 数据复用:detail() / DiagnosisLogic::detail() / BloodRecordLogic::getRecordsByPatient()
|
||
* 备注:appointment.patient_id 实际对应 tcm_diagnosis.id
|
||
* @param array $params
|
||
* @return array
|
||
*/
|
||
public static function reception(array $params): array
|
||
{
|
||
// 1) 挂号详情(已包含 patient_name / patient_phone / doctor_name / status_desc 等)
|
||
$appointment = self::detail($params);
|
||
if (empty($appointment)) {
|
||
return [];
|
||
}
|
||
|
||
// 2) 诊单(病例)详情:appointment.patient_id == tcm_diagnosis.id
|
||
$diagnosisId = (int) ($appointment['patient_id'] ?? 0);
|
||
$diagnosis = $diagnosisId > 0
|
||
? DiagnosisLogic::detail(['id' => $diagnosisId])
|
||
: [];
|
||
|
||
// 3) 跟踪信息(血糖血压 / 饮食 / 运动)改为前端按窗口 lazy load,
|
||
// 详见 DiagnosisController::trackingWindow。此处不再一次性返回。
|
||
|
||
// 4) 补全医助姓名(detail() 未关联 admin 助理表)
|
||
$assistantName = '';
|
||
if (!empty($diagnosis['assistant_id'])) {
|
||
$assistant = Admin::where('id', (int) $diagnosis['assistant_id'])->find();
|
||
if ($assistant) {
|
||
$assistantName = $assistant->name ?? '';
|
||
}
|
||
}
|
||
$appointment['assistant_name'] = $assistantName;
|
||
$appointment['assistant_id'] = $diagnosis['assistant_id'] ?? 0;
|
||
|
||
// 5) 是否已开方(与列表口径一致:未删除 & 未作废)
|
||
$hasPrescription = 0;
|
||
if ($diagnosisId > 0) {
|
||
$rxExists = Prescription::where('diagnosis_id', $diagnosisId)
|
||
->whereNull('delete_time')
|
||
->where('void_status', 0)
|
||
->find();
|
||
$hasPrescription = $rxExists ? 1 : 0;
|
||
}
|
||
$appointment['has_prescription'] = $hasPrescription;
|
||
|
||
// 6) 对诊单字段进行字典/枚举翻译(产出 *_text 附加字段,不覆盖原 value)
|
||
$diagnosis = self::enrichDiagnosisLabels($diagnosis);
|
||
|
||
// 7) 医生备注
|
||
$doctorNotes = $diagnosisId > 0
|
||
? DoctorNoteLogic::getByDiagnosis($diagnosisId)
|
||
: [];
|
||
|
||
// 7.1) 跟踪备注
|
||
$trackingNotes = $diagnosisId > 0
|
||
? TrackingNoteLogic::getByDiagnosis($diagnosisId)
|
||
: [];
|
||
|
||
return [
|
||
'appointment' => $appointment,
|
||
'diagnosis' => $diagnosis,
|
||
'doctor_notes' => $doctorNotes,
|
||
'tracking_notes' => $trackingNotes,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 字典翻译:把诊单中以 code 存储的字段(字典/枚举)补一份中文 label 字段,
|
||
* 便于前端(接诊台 / 只读病例页等只读场景)直接渲染,无需加载字典。
|
||
* 原始 code 字段保留不动,edit 场景仍可使用。
|
||
*
|
||
* @param array $diagnosis
|
||
* @return array
|
||
*/
|
||
public static function enrichDiagnosisLabels(array $diagnosis): array
|
||
{
|
||
if (empty($diagnosis)) {
|
||
return $diagnosis;
|
||
}
|
||
|
||
// 单值字段:value 为字典 value,翻译成字典 name
|
||
$singleDictFields = [
|
||
'diagnosis_type' => 'diagnosis_type',
|
||
'syndrome_type' => 'syndrome_type',
|
||
'diabetes_type' => 'diabetes_type',
|
||
'water_intake' => 'water_intake',
|
||
'weight_change' => 'weight_change',
|
||
'fatty_liver_degree' => 'fatty_liver_degree',
|
||
];
|
||
// 多值字段:value 为数组或逗号分隔字符串
|
||
$multiDictFields = [
|
||
'past_history' => 'past_history',
|
||
'appetite' => 'appetite',
|
||
'diet_condition' => 'diet_condition',
|
||
'body_feeling' => 'body_feeling',
|
||
'sleep_condition' => 'sleep_condition',
|
||
'eye_condition' => 'eye_condition',
|
||
'head_feeling' => 'head_feeling',
|
||
'sweat_condition' => 'sweat_condition',
|
||
'skin_condition' => 'skin_condition',
|
||
'urine_condition' => 'urine_condition',
|
||
'stool_condition' => 'stool_condition',
|
||
'kidney_condition' => 'kidney_condition',
|
||
];
|
||
|
||
// 一次性查出用到的所有字典
|
||
$allTypes = array_values(array_unique(array_merge(array_values($singleDictFields), array_values($multiDictFields))));
|
||
$dictMap = self::buildDictMap($allTypes);
|
||
|
||
foreach ($singleDictFields as $field => $type) {
|
||
if (!array_key_exists($field, $diagnosis)) {
|
||
continue;
|
||
}
|
||
$diagnosis[$field . '_text'] = self::dictLabel($dictMap, $type, $diagnosis[$field]);
|
||
}
|
||
foreach ($multiDictFields as $field => $type) {
|
||
if (!array_key_exists($field, $diagnosis)) {
|
||
continue;
|
||
}
|
||
$v = $diagnosis[$field];
|
||
$arr = is_array($v)
|
||
? $v
|
||
: (is_string($v) && $v !== '' ? preg_split('/[,,、]/', $v) : []);
|
||
$labels = [];
|
||
foreach ($arr as $item) {
|
||
$item = is_string($item) ? trim($item) : $item;
|
||
if ($item === '' || $item === null) {
|
||
continue;
|
||
}
|
||
$labels[] = self::dictLabel($dictMap, $type, $item);
|
||
}
|
||
$diagnosis[$field . '_text'] = implode('、', array_filter($labels));
|
||
}
|
||
|
||
// 枚举字段
|
||
$diagnosis['gender_text'] = isset($diagnosis['gender']) ? self::genderText((int) $diagnosis['gender']) : '';
|
||
$diagnosis['marital_status_text'] = array_key_exists('marital_status', $diagnosis) ? self::maritalText($diagnosis['marital_status']) : '';
|
||
foreach (['trauma_history', 'surgery_history', 'allergy_history', 'family_history', 'pregnancy_history'] as $f) {
|
||
if (array_key_exists($f, $diagnosis)) {
|
||
$diagnosis[$f . '_text'] = self::yesNoText($diagnosis[$f]);
|
||
}
|
||
}
|
||
|
||
return $diagnosis;
|
||
}
|
||
|
||
/**
|
||
* 构建 type_value => [value => name] 的两级查询映射
|
||
*
|
||
* @param array $types
|
||
* @return array
|
||
*/
|
||
private static function buildDictMap(array $types): array
|
||
{
|
||
if (empty($types)) {
|
||
return [];
|
||
}
|
||
$rows = DictData::whereIn('type_value', $types)
|
||
->field(['type_value', 'value', 'name'])
|
||
->select()
|
||
->toArray();
|
||
$map = [];
|
||
foreach ($rows as $r) {
|
||
$tv = (string) ($r['type_value'] ?? '');
|
||
$v = (string) ($r['value'] ?? '');
|
||
$n = (string) ($r['name'] ?? '');
|
||
if ($tv === '' || $v === '') {
|
||
continue;
|
||
}
|
||
$map[$tv][$v] = $n;
|
||
}
|
||
return $map;
|
||
}
|
||
|
||
/**
|
||
* 找不到字典项时回显原值(避免空白)
|
||
*/
|
||
private static function dictLabel(array $dictMap, string $type, $value): string
|
||
{
|
||
if ($value === null || $value === '') {
|
||
return '';
|
||
}
|
||
$key = (string) $value;
|
||
return $dictMap[$type][$key] ?? $key;
|
||
}
|
||
|
||
private static function genderText(int $gender): string
|
||
{
|
||
if ($gender === 1) return '男';
|
||
if ($gender === 0) return '女';
|
||
return '';
|
||
}
|
||
|
||
private static function maritalText($v): string
|
||
{
|
||
if ($v === null || $v === '') return '';
|
||
$n = (int) $v;
|
||
if ($n === 0) return '未婚';
|
||
if ($n === 1) return '已婚';
|
||
if ($n === 2) return '离异';
|
||
return '';
|
||
}
|
||
|
||
private static function yesNoText($v): string
|
||
{
|
||
if ($v === null || $v === '') return '';
|
||
return ((int) $v) === 1 ? '有' : '无';
|
||
}
|
||
|
||
/**
|
||
* @notes 通知接诊医助(按诊单 assistant_id 查企微 userid 后发应用消息)
|
||
* 文案固定:马上面诊到你对应的患者了,请联系患者进入诊室等待。
|
||
* @param int $appointmentId
|
||
* @return array{ok: bool, message?: string}
|
||
*/
|
||
public static function notifyAssistant(int $appointmentId): array
|
||
{
|
||
try {
|
||
$appointment = Appointment::findOrEmpty($appointmentId);
|
||
if ($appointment->isEmpty()) {
|
||
return ['ok' => false, 'message' => '挂号记录不存在'];
|
||
}
|
||
|
||
$diagnosis = Diagnosis::where('id', (int) $appointment->patient_id)->find();
|
||
if (!$diagnosis) {
|
||
return ['ok' => false, 'message' => '未找到对应的诊单'];
|
||
}
|
||
$assistantId = (int) ($diagnosis->assistant_id ?? 0);
|
||
if ($assistantId <= 0) {
|
||
return ['ok' => false, 'message' => '该患者尚未指派医助'];
|
||
}
|
||
|
||
$assistant = Admin::whereNull('delete_time')->find($assistantId);
|
||
if (!$assistant) {
|
||
return ['ok' => false, 'message' => '医助账号不存在'];
|
||
}
|
||
$wxId = trim((string) ($assistant->work_wechat_userid ?? ''));
|
||
if ($wxId === '') {
|
||
Log::warning("接诊台通知医助跳过:医助 admin_id={$assistantId} 未绑定 work_wechat_userid");
|
||
return [
|
||
'ok' => false,
|
||
'message' => '该医助未绑定企业微信账号,无法推送消息(请其在后台「扫码绑定企业微信」后再试)',
|
||
];
|
||
}
|
||
|
||
$patientName = (string) ($diagnosis->patient_name ?? '患者');
|
||
$text = "【接诊通知】马上面诊到你对应的患者了,请联系患者进入诊室等待。\n患者:{$patientName}\n时间:" . (string) $appointment->appointment_date . ' ' . (string) $appointment->appointment_time;
|
||
|
||
$result = WechatWorkAppMessageService::sendTextToUser($wxId, $text);
|
||
if (empty($result['ok'])) {
|
||
Log::warning('接诊台通知医助失败:' . ($result['message'] ?? ''));
|
||
}
|
||
return $result;
|
||
} catch (\Throwable $e) {
|
||
Log::error('接诊台通知医助异常:' . $e->getMessage());
|
||
return ['ok' => false, 'message' => '企业微信通知异常:' . $e->getMessage()];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 与 AppointmentLists 一致的可见性(不含 progress_board / diag_scope_relax)
|
||
*/
|
||
private static function appointmentRowManageableByAdmin(
|
||
Appointment $appointment,
|
||
?Diagnosis $diag,
|
||
int $adminId,
|
||
array $adminInfo
|
||
): bool {
|
||
$roleIds = array_map('intval', AdminRole::where('admin_id', $adminId)->column('role_id'));
|
||
|
||
if (in_array(1, $roleIds, true) && (int) $appointment->doctor_id !== $adminId) {
|
||
return false;
|
||
}
|
||
if (in_array(2, $roleIds, true)) {
|
||
$asst = $diag ? (int) $diag->assistant_id : 0;
|
||
if ($asst !== $adminId) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if (!DataScopeService::isEnabled()) {
|
||
return true;
|
||
}
|
||
$ids = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||
if ($ids === []) {
|
||
return false;
|
||
}
|
||
if ($ids === null) {
|
||
return true;
|
||
}
|
||
$docId = (int) $appointment->doctor_id;
|
||
$asstId = $diag ? (int) $diag->assistant_id : 0;
|
||
|
||
return in_array($docId, $ids, true)
|
||
|| ($asstId > 0 && in_array($asstId, $ids, true));
|
||
}
|
||
|
||
/**
|
||
* 后台编辑挂号(预约日期/时段/类型/状态/备注/医助)
|
||
*
|
||
* @param array<string, mixed> $params
|
||
*/
|
||
public static function adminEdit(array $params, int $adminId, array $adminInfo): bool
|
||
{
|
||
try {
|
||
$id = (int) ($params['id'] ?? 0);
|
||
if ($id <= 0) {
|
||
self::setError('参数错误');
|
||
|
||
return false;
|
||
}
|
||
|
||
$appointment = Appointment::findOrEmpty($id);
|
||
if ($appointment->isEmpty()) {
|
||
self::setError('预约记录不存在');
|
||
|
||
return false;
|
||
}
|
||
|
||
$diag = Diagnosis::where('id', (int) $appointment->patient_id)->whereNull('delete_time')->find();
|
||
|
||
if (!self::appointmentRowManageableByAdmin($appointment, $diag ?: null, $adminId, $adminInfo)) {
|
||
self::setError('无权限编辑');
|
||
|
||
return false;
|
||
}
|
||
|
||
$period = trim((string) ($params['period'] ?? ''));
|
||
if (!in_array($period, ['morning', 'afternoon', 'all'], true)) {
|
||
self::setError('时段无效');
|
||
|
||
return false;
|
||
}
|
||
|
||
$appointmentDate = trim((string) ($params['appointment_date'] ?? ''));
|
||
if ($appointmentDate === '') {
|
||
self::setError('预约日期不能为空');
|
||
|
||
return false;
|
||
}
|
||
|
||
$rawTime = trim((string) ($params['appointment_time'] ?? ''));
|
||
if ($rawTime === '') {
|
||
self::setError('预约时间不能为空');
|
||
|
||
return false;
|
||
}
|
||
$appointmentTime = strlen($rawTime) === 5 ? $rawTime . ':00' : $rawTime;
|
||
|
||
$status = (int) ($params['status'] ?? 0);
|
||
if ($status < 1 || $status > 4) {
|
||
self::setError('状态无效');
|
||
|
||
return false;
|
||
}
|
||
|
||
$appointmentType = trim((string) ($params['appointment_type'] ?? ''));
|
||
if (!in_array($appointmentType, ['video', 'text', 'phone'], true)) {
|
||
self::setError('预约类型无效');
|
||
|
||
return false;
|
||
}
|
||
|
||
$remark = isset($params['remark']) ? trim((string) $params['remark']) : '';
|
||
if (mb_strlen($remark) > 500) {
|
||
self::setError('备注过长');
|
||
|
||
return false;
|
||
}
|
||
|
||
$chSrc = trim((string) ($params['channel_source'] ?? ''));
|
||
if ($chSrc === '') {
|
||
self::setError('请选择渠道来源');
|
||
|
||
return false;
|
||
}
|
||
$chDetail = trim((string) ($params['channel_source_detail'] ?? ''));
|
||
$channelDictName = trim((string) (DictData::where('type_value', 'channels')
|
||
->where('value', $chSrc)
|
||
->value('name') ?? ''));
|
||
if ($channelDictName !== '' && self::channelDictNameRequiresSourceDetail($channelDictName)) {
|
||
if ($chDetail === '') {
|
||
self::setError('请填写自媒体渠道补充信息');
|
||
|
||
return false;
|
||
}
|
||
}
|
||
if (mb_strlen($chDetail) > 128) {
|
||
self::setError('渠道补充说明过长');
|
||
|
||
return false;
|
||
}
|
||
|
||
$postAssistant = array_key_exists('assistant_id', $params);
|
||
$assistantId = null;
|
||
if ($postAssistant) {
|
||
$assistantRaw = $params['assistant_id'];
|
||
if ($assistantRaw !== null && $assistantRaw !== '') {
|
||
$aid = (int) $assistantRaw;
|
||
$assistantId = $aid > 0 ? $aid : null;
|
||
}
|
||
}
|
||
|
||
$patientId = (int) $appointment->patient_id;
|
||
$doctorId = (int) $appointment->doctor_id;
|
||
|
||
if (in_array($status, [1, 4], true)) {
|
||
$patientDup = Appointment::where('patient_id', $patientId)
|
||
->where('appointment_date', $appointmentDate)
|
||
->whereIn('status', [1, 4])
|
||
->where('id', '<>', $id)
|
||
->find();
|
||
if ($patientDup) {
|
||
self::setError('该诊单在所选日期已有其他有效挂号(已预约/已过号)');
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
if ($status === 1) {
|
||
$slotDup = Appointment::where('doctor_id', $doctorId)
|
||
->where('appointment_date', $appointmentDate)
|
||
->where('appointment_time', $appointmentTime)
|
||
->where('status', 1)
|
||
->where('id', '<>', $id)
|
||
->find();
|
||
if ($slotDup) {
|
||
self::setError('该医生在所选时段已被占用');
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
$beforeDate = (string) ($appointment->appointment_date ?? '');
|
||
$beforeTimeRaw = (string) ($appointment->appointment_time ?? '');
|
||
$beforeHm = strlen($beforeTimeRaw) >= 5 ? substr($beforeTimeRaw, 0, 5) : $beforeTimeRaw;
|
||
$snap = $appointment->toArray();
|
||
$beforePeriod = (string) ($snap['period'] ?? $snap['type'] ?? '');
|
||
$beforeStatus = (int) $appointment->status;
|
||
|
||
$tblFields = Db::name('doctor_appointment')->getTableFields();
|
||
$cols = is_array($tblFields) ? $tblFields : [];
|
||
$channelErr = self::assertAppointmentChannelWritable($cols, $chSrc);
|
||
if ($channelErr !== null) {
|
||
self::setError($channelErr);
|
||
|
||
return false;
|
||
}
|
||
|
||
Db::startTrans();
|
||
|
||
$saveData = [
|
||
'appointment_date' => $appointmentDate,
|
||
'appointment_time' => $appointmentTime,
|
||
'appointment_type' => $appointmentType,
|
||
'status' => $status,
|
||
'remark' => $remark,
|
||
'update_time' => time(),
|
||
];
|
||
if (in_array('assistant_id', $cols, true) && $postAssistant) {
|
||
$saveData['assistant_id'] = $assistantId;
|
||
}
|
||
if (in_array('period', $cols, true)) {
|
||
$saveData['period'] = $period;
|
||
} elseif (in_array('type', $cols, true)) {
|
||
$saveData['type'] = $period;
|
||
}
|
||
self::mergeAppointmentChannelIntoRow($saveData, $cols, $chSrc, $chDetail);
|
||
|
||
Db::name('doctor_appointment')->where('id', $id)->update($saveData);
|
||
|
||
Db::commit();
|
||
|
||
$hm = substr($appointmentTime, 0, 5);
|
||
$summary = sprintf(
|
||
'后台修改挂号 #%d:日期 %s %s→%s %s,时段 %s→%s,状态 %d→%d',
|
||
$id,
|
||
$beforeDate,
|
||
$beforeHm,
|
||
$appointmentDate,
|
||
$hm,
|
||
$beforePeriod !== '' ? $beforePeriod : '—',
|
||
$period,
|
||
$beforeStatus,
|
||
$status
|
||
);
|
||
self::appendDiagnosisGuahaoLog($patientId, $id, $adminId, $adminInfo, 'admin_edit', $summary);
|
||
|
||
return true;
|
||
} catch (\Throwable $e) {
|
||
Db::rollback();
|
||
self::setError($e->getMessage());
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 后台批量修改挂号渠道(仅渠道号与可选补充)
|
||
*
|
||
* @param array<string, mixed> $params
|
||
* @return array{updated:int}|false
|
||
*/
|
||
public static function adminBatchEditChannel(array $params, int $adminId, array $adminInfo)
|
||
{
|
||
try {
|
||
$idsRaw = $params['ids'] ?? [];
|
||
if (!\is_array($idsRaw) || $idsRaw === []) {
|
||
self::setError('请选择要修改的挂号记录');
|
||
|
||
return false;
|
||
}
|
||
|
||
$ids = [];
|
||
foreach ($idsRaw as $raw) {
|
||
$id = (int) $raw;
|
||
if ($id > 0) {
|
||
$ids[$id] = $id;
|
||
}
|
||
}
|
||
$ids = array_values($ids);
|
||
$maxBatch = 300;
|
||
if (\count($ids) === 0) {
|
||
self::setError('预约ID无效');
|
||
|
||
return false;
|
||
}
|
||
if (\count($ids) > $maxBatch) {
|
||
self::setError('单次最多修改 ' . $maxBatch . ' 条挂号');
|
||
|
||
return false;
|
||
}
|
||
|
||
$chSrc = trim((string) ($params['channel_source'] ?? ''));
|
||
if ($chSrc === '') {
|
||
self::setError('请选择渠道来源');
|
||
|
||
return false;
|
||
}
|
||
$chDetail = isset($params['channel_source_detail']) ? trim((string) $params['channel_source_detail']) : '';
|
||
$channelDictName = trim((string) (DictData::where('type_value', 'channels')
|
||
->where('value', $chSrc)
|
||
->value('name') ?? ''));
|
||
if ($channelDictName !== '' && self::channelDictNameRequiresSourceDetail($channelDictName)) {
|
||
if ($chDetail === '') {
|
||
self::setError('请填写自媒体渠道补充信息');
|
||
|
||
return false;
|
||
}
|
||
}
|
||
if (mb_strlen($chDetail) > 128) {
|
||
self::setError('渠道补充说明过长');
|
||
|
||
return false;
|
||
}
|
||
|
||
$tblFields = Db::name('doctor_appointment')->getTableFields();
|
||
$cols = \is_array($tblFields) ? $tblFields : [];
|
||
$channelErr = self::assertAppointmentChannelWritable($cols, $chSrc);
|
||
if ($channelErr !== null) {
|
||
self::setError($channelErr);
|
||
|
||
return false;
|
||
}
|
||
|
||
Db::startTrans();
|
||
|
||
$updated = 0;
|
||
foreach ($ids as $id) {
|
||
$appointment = Appointment::findOrEmpty((int) $id);
|
||
if ($appointment->isEmpty()) {
|
||
Db::rollback();
|
||
self::setError('预约记录不存在(ID ' . $id . ')');
|
||
|
||
return false;
|
||
}
|
||
|
||
$diag = Diagnosis::where('id', (int) $appointment->patient_id)->whereNull('delete_time')->find();
|
||
|
||
if (!self::appointmentRowManageableByAdmin($appointment, $diag ?: null, $adminId, $adminInfo)) {
|
||
Db::rollback();
|
||
self::setError('无权限修改挂号 #' . $id);
|
||
|
||
return false;
|
||
}
|
||
|
||
$saveData = [
|
||
'update_time' => time(),
|
||
];
|
||
self::mergeAppointmentChannelIntoRow($saveData, $cols, $chSrc, $chDetail);
|
||
|
||
$saveData = self::filterAppointmentRowByExistingColumns($saveData, $cols);
|
||
|
||
Db::name('doctor_appointment')->where('id', $id)->update($saveData);
|
||
|
||
$patientId = (int) $appointment->patient_id;
|
||
self::appendDiagnosisGuahaoLog(
|
||
$patientId,
|
||
(int) $id,
|
||
$adminId,
|
||
$adminInfo,
|
||
'admin_batch_channel',
|
||
sprintf('批量修改挂号 #%d:渠道设为 %s', $id, $chSrc !== '' ? $chSrc : '—')
|
||
);
|
||
++$updated;
|
||
}
|
||
|
||
Db::commit();
|
||
|
||
return [
|
||
'updated' => $updated,
|
||
'msg' => sprintf('已更新 %d 条挂号渠道', $updated),
|
||
];
|
||
} catch (\Throwable $e) {
|
||
Db::rollback();
|
||
self::setError($e->getMessage());
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 诊单列表维度:记录谁在后台发起挂号 / 取消挂号(写入失败不影响主流程)
|
||
*/
|
||
private static function appendDiagnosisGuahaoLog(
|
||
int $diagnosisId,
|
||
int $appointmentId,
|
||
int $adminId,
|
||
array $adminInfo,
|
||
string $action,
|
||
string $summary
|
||
): void {
|
||
if ($diagnosisId <= 0) {
|
||
return;
|
||
}
|
||
$adminName = (string) ($adminInfo['name'] ?? '');
|
||
if ($adminName === '' && $adminId > 0) {
|
||
$adminName = (string) (Admin::where('id', $adminId)->value('name') ?? '');
|
||
}
|
||
try {
|
||
$log = new DiagnosisGuahaoLog();
|
||
$log->diagnosis_id = $diagnosisId;
|
||
$log->appointment_id = $appointmentId;
|
||
$log->admin_id = $adminId;
|
||
$log->admin_name = mb_substr($adminName, 0, 64);
|
||
$log->action = mb_substr($action, 0, 32);
|
||
$log->summary = mb_substr($summary, 0, 500);
|
||
$log->create_time = time();
|
||
$log->save();
|
||
} catch (\Throwable $e) {
|
||
Log::warning('诊单挂号日志写入失败: ' . $e->getMessage());
|
||
}
|
||
}
|
||
}
|