You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

633 lines
26 KiB
PHP

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Services;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class HisApplicationService
{
public function createApplication(array $params): array
{
$requiredFields = [
'his_request_no', 'patient_id', 'patient_type', 'patient_name',
'patient_phone', 'patient_gender', 'patient_age',
'apply_department_code', 'apply_doctor_code', 'apply_doctor_name',
'examination_item_code', 'examination_department_code', 'apply_time'
];
foreach ($requiredFields as $field) {
if (!isset($params[$field]) || $params[$field] === '') {
return ['success' => false, 'message' => "缺少必填字段: {$field}"];
}
}
$validPatientTypes = [1, 2, 3, 4];
if (!in_array((int)$params['patient_type'], $validPatientTypes)) {
return ['success' => false, 'message' => 'patient_type 无效,应为 1(门诊) 2(住院) 3(急诊) 4(体检)'];
}
$validGenders = [0, 1, 2];
if (!in_array((int)$params['patient_gender'], $validGenders)) {
return ['success' => false, 'message' => 'patient_gender 无效,应为 0(未知) 1(男) 2(女)'];
}
return DB::transaction(function () use ($params) {
$existing = DB::table('exam_application')
->where('his_request_no', $params['his_request_no'])
->first();
if ($existing) {
if ($existing->status == 0) {
return [
'success' => true,
'message' => '申请单已存在(重复推送)',
'data' => ['application_id' => $existing->id, 'his_request_no' => $existing->his_request_no]
];
}
return ['success' => false, 'message' => '申请单已存在且状态不为申请中,无法重复入库'];
}
$insertData = [
'his_request_no' => $params['his_request_no'],
'his_order_id' => $params['his_order_id'] ?? null,
'patient_id' => (int)$params['patient_id'],
'patient_type' => (int)$params['patient_type'],
'patient_name' => $params['patient_name'],
'patient_id_card_no' => $params['patient_id_card_no'] ?? null,
'patient_phone' => $params['patient_phone'],
'patient_gender' => (int)$params['patient_gender'],
'patient_birthday' => $params['patient_birthday'] ?? null,
'patient_age' => (int)$params['patient_age'],
'inpatient_id' => $params['inpatient_id'] ?? null,
'outpatient_id' => $params['outpatient_id'] ?? null,
'ward_name' => $params['ward_name'] ?? null,
'ward_code' => $params['ward_code'] ?? null,
'bed_name' => $params['bed_name'] ?? null,
'bed_code' => $params['bed_code'] ?? null,
'apply_department_code' => $params['apply_department_code'],
'apply_department_name' => $params['apply_department_name'] ?? null,
'apply_doctor_code' => $params['apply_doctor_code'],
'apply_doctor_name' => $params['apply_doctor_name'],
'examination_item_name' => $params['examination_item_name'] ?? null,
'examination_item_code' => $params['examination_item_code'],
'examination_department_code' => $params['examination_department_code'],
'examination_department_name' => $params['examination_department_name'] ?? null,
'apply_time' => $params['apply_time'],
'payment_status' => isset($params['payment_status']) ? (int)$params['payment_status'] : null,
'clinical_diagnosis' => $params['clinical_diagnosis'] ?? null,
'notes' => $params['notes'] ?? null,
'status' => 0,
'created_at' => now(),
'updated_at' => now(),
];
$id = DB::table('exam_application')->insertGetId($insertData);
$this->logOperation($id, 1, 1, $id, 1, 'HIS', null, $insertData, 'HIS系统推送申请单');
return [
'success' => true,
'message' => '申请单入库成功',
'data' => [
'application_id' => $id,
'his_request_no' => $params['his_request_no'],
]
];
});
}
public function paymentNotify(string $hisRequestNo, int $paymentStatus): array
{
return DB::transaction(function () use ($hisRequestNo, $paymentStatus) {
$application = DB::table('exam_application')
->where('his_request_no', $hisRequestNo)
->lockForUpdate()
->first();
if (!$application) {
return ['success' => false, 'message' => '申请单不存在: ' . $hisRequestNo];
}
if ($application->status == 4) {
return ['success' => false, 'message' => '申请单已作废,无法更新付费状态'];
}
$oldValues = ['payment_status' => $application->payment_status];
DB::table('exam_application')
->where('id', $application->id)
->update([
'payment_status' => $paymentStatus,
'updated_at' => now(),
]);
$this->logOperation(
$application->id,
2,
1,
$application->id,
1,
'HIS',
$oldValues,
['payment_status' => $paymentStatus],
'HIS付费通知付费状态变更为: ' . $paymentStatus
);
return [
'success' => true,
'message' => '付费状态更新成功',
'data' => [
'application_id' => $application->id,
'his_request_no' => $hisRequestNo,
'payment_status' => $paymentStatus,
]
];
});
}
public function cancelNotify(string $hisRequestNo, ?string $cancelReason = null): array
{
return DB::transaction(function () use ($hisRequestNo, $cancelReason) {
$application = DB::table('exam_application')
->where('his_request_no', $hisRequestNo)
->lockForUpdate()
->first();
if (!$application) {
return ['success' => false, 'message' => '申请单不存在: ' . $hisRequestNo];
}
if ($application->status == 4) {
return [
'success' => true,
'message' => '申请单已作废(重复推送)',
'data' => ['application_id' => $application->id, 'his_request_no' => $hisRequestNo]
];
}
if ($application->status == 2) {
return ['success' => false, 'message' => '申请单已到检,请先取消报到后再作废'];
}
if ($application->status == 3) {
return ['success' => false, 'message' => '申请单已完成检查,无法作废'];
}
if ($application->status == 1) {
$appointments = DB::table('exam_appointment')
->where('exam_application_id', $application->id)
->where('status', 1)
->get();
foreach ($appointments as $appointment) {
$this->releaseQuotaForAppointment($appointment);
DB::table('exam_appointment')
->where('id', $appointment->id)
->update([
'status' => 4,
'cancel_time' => now(),
'cancel_reason' => 'HIS系统作废: ' . ($cancelReason ?? ''),
'updated_at' => now(),
]);
$this->logOperation(
$application->id,
3,
2,
$appointment->id,
1,
'HIS',
['status' => 1],
['status' => 4, 'cancel_reason' => 'HIS系统作废'],
'HIS作废通知取消预约'
);
}
}
$oldValues = ['status' => $application->status];
DB::table('exam_application')
->where('id', $application->id)
->update([
'status' => 4,
'his_cancel_time' => now(),
'his_cancel_reason' => $cancelReason,
'updated_at' => now(),
]);
$this->logOperation(
$application->id,
3,
1,
$application->id,
1,
'HIS',
$oldValues,
['status' => 4, 'his_cancel_time' => now(), 'his_cancel_reason' => $cancelReason],
'HIS作废通知' . ($cancelReason ? ': ' . $cancelReason : '')
);
return [
'success' => true,
'message' => '申请单作废成功',
'data' => [
'application_id' => $application->id,
'his_request_no' => $hisRequestNo,
]
];
});
}
private function releaseQuotaForAppointment($appointment): void
{
$plan = DB::table('plan')
->where('id', $appointment->time_slot_id)
->where('deleted', 0)
->lockForUpdate()
->first();
if (!$plan) {
Log::warning("HIS作废释放配额号源不存在 plan_id={$appointment->time_slot_id}");
return;
}
$batch = DB::table('plan_batch')
->where('id', $plan->batch_id)
->where('deleted', 0)
->lockForUpdate()
->first();
if (!$batch) {
Log::warning("HIS作废释放配额批次不存在 batch_id={$plan->batch_id}");
return;
}
$application = DB::table('exam_application')
->where('id', $appointment->exam_application_id)
->first();
$totalSeats = 1;
if ($application && $batch->slot_mode == 1) {
$itemSeat = DB::table('exam_item')
->where('code', $application->examination_item_code)
->where('deleted', 0)
->value('use_seats');
$totalSeats = $itemSeat ?? 1;
}
$channelQuotaUsed = $appointment->channel_quota_used ?? 0;
$publicQuotaUsed = $appointment->public_quota_used ?? 0;
$enableChannelQuota = ($batch->enable_channel_quota ?? 0) == 1;
$channelId = $appointment->channel_id;
if ($channelQuotaUsed > 0 && $enableChannelQuota) {
$channelQuota = DB::table('plan_batch_channel_quota')
->where('batch_id', $plan->batch_id)
->where('channel_id', $channelId)
->where('status', 1)
->where('deleted', 0)
->lockForUpdate()
->first();
if ($channelQuota) {
DB::table('plan_batch_channel_quota')
->where('id', $channelQuota->id)
->where('version', $channelQuota->version)
->update([
'used_quota' => DB::raw('GREATEST(0, used_quota - ' . $channelQuotaUsed . ')'),
'version' => $channelQuota->version + 1
]);
}
}
if ($publicQuotaUsed > 0) {
$publicQuota = DB::table('plan_batch_channel_quota')
->where('batch_id', $plan->batch_id)
->where('channel_id', -1)
->where('status', 1)
->where('deleted', 0)
->lockForUpdate()
->first();
if ($publicQuota) {
DB::table('plan_batch_channel_quota')
->where('id', $publicQuota->id)
->where('version', $publicQuota->version)
->update([
'used_quota' => DB::raw('GREATEST(0, used_quota - ' . $publicQuotaUsed . ')'),
'version' => $publicQuota->version + 1
]);
}
}
DB::table('plan_batch')
->where('id', $batch->id)
->where('version', $batch->version ?? 0)
->update([
'used_quota' => DB::raw('GREATEST(0, used_quota - ' . $totalSeats . ')'),
'version' => ($batch->version ?? 0) + 1
]);
DB::table('plan')
->where('id', $plan->id)
->where('version', $plan->version ?? 0)
->update([
'used_quota' => DB::raw('GREATEST(0, used_quota - ' . $totalSeats . ')'),
'version' => ($plan->version ?? 0) + 1
]);
}
public function queryAppointment(array $params): array
{
$query = DB::table('exam_appointment')
->leftJoin('exam_application', 'exam_appointment.exam_application_id', '=', 'exam_application.id')
->leftJoin('department_resource', 'exam_appointment.department_resource_id', '=', 'department_resource.id')
->leftJoin('department', 'department_resource.department_id', '=', 'department.id')
->leftJoin('channel', 'exam_appointment.channel_id', '=', 'channel.id')
->select(
'exam_appointment.id as appointment_id',
'exam_appointment.status as appointment_status',
'exam_appointment.appointment_time',
'exam_appointment.check_in_time',
'exam_appointment.finish_time',
'exam_appointment.cancel_time',
'exam_appointment.cancel_reason',
'exam_appointment.created_at as appointment_created_at',
'exam_application.id as application_id',
'exam_application.his_request_no',
'exam_application.his_order_id',
'exam_application.patient_id',
'exam_application.patient_name',
'exam_application.patient_id_card_no',
'exam_application.patient_phone',
'exam_application.patient_gender',
'exam_application.patient_age',
'exam_application.patient_type',
'exam_application.examination_item_code',
'exam_application.examination_item_name',
'exam_application.examination_department_code',
'exam_application.examination_department_name',
'exam_application.apply_department_code',
'exam_application.apply_department_name',
'exam_application.apply_doctor_code',
'exam_application.apply_doctor_name',
'exam_application.clinical_diagnosis',
'exam_application.status as application_status',
'department_resource.id as resource_id',
'department_resource.name as resource_name',
'department_resource.code as resource_code',
'department.name as department_name',
'department.code as department_code',
'channel.name as channel_name'
);
if (!empty($params['his_request_no'])) {
$query->where('exam_application.his_request_no', $params['his_request_no']);
}
if (!empty($params['his_order_id'])) {
$query->where('exam_application.his_order_id', $params['his_order_id']);
}
if (!empty($params['patient_id_card_no'])) {
$query->where('exam_application.patient_id_card_no', $params['patient_id_card_no']);
}
if (!empty($params['patient_id'])) {
$query->where('exam_application.patient_id', $params['patient_id']);
}
if (!empty($params['appointment_id'])) {
$query->where('exam_appointment.id', $params['appointment_id']);
}
if (isset($params['appointment_status']) && $params['appointment_status'] !== '') {
$query->where('exam_appointment.status', (int)$params['appointment_status']);
}
if (!empty($params['start_date'])) {
$query->where('exam_appointment.appointment_time', '>=', $params['start_date']);
}
if (!empty($params['end_date'])) {
$query->where('exam_appointment.appointment_time', '<=', $params['end_date'] . ' 23:59:59');
}
$results = $query->orderByDesc('exam_appointment.id')->limit(200)->get();
$statusMap = [1 => '已预约', 2 => '已到检', 3 => '已完成', 4 => '已取消', 5 => '爽约'];
$genderMap = [0 => '未知', 1 => '男', 2 => '女'];
$patientTypeMap = [1 => '门诊', 2 => '住院', 3 => '急诊', 4 => '体检'];
$list = $results->map(function ($row) use ($statusMap, $genderMap, $patientTypeMap) {
return [
'appointment_id' => $row->appointment_id,
'appointment_status' => $row->appointment_status,
'appointment_status_name' => $statusMap[$row->appointment_status] ?? '未知',
'appointment_time' => $row->appointment_time,
'check_in_time' => $row->check_in_time,
'finish_time' => $row->finish_time,
'cancel_time' => $row->cancel_time,
'cancel_reason' => $row->cancel_reason,
'his_request_no' => $row->his_request_no,
'his_order_id' => $row->his_order_id,
'patient_id' => $row->patient_id,
'patient_name' => $row->patient_name,
'patient_id_card_no' => $row->patient_id_card_no,
'patient_phone' => $row->patient_phone,
'patient_gender' => $row->patient_gender,
'patient_gender_name' => $genderMap[$row->patient_gender] ?? '未知',
'patient_age' => $row->patient_age,
'patient_type' => $row->patient_type,
'patient_type_name' => $patientTypeMap[$row->patient_type] ?? '未知',
'examination_item_code' => $row->examination_item_code,
'examination_item_name' => $row->examination_item_name,
'examination_department_code' => $row->examination_department_code,
'examination_department_name' => $row->examination_department_name,
'apply_department_code' => $row->apply_department_code,
'apply_department_name' => $row->apply_department_name,
'apply_doctor_code' => $row->apply_doctor_code,
'apply_doctor_name' => $row->apply_doctor_name,
'clinical_diagnosis' => $row->clinical_diagnosis,
'resource_id' => $row->resource_id,
'resource_name' => $row->resource_name,
'resource_code' => $row->resource_code,
'department_name' => $row->department_name,
'department_code' => $row->department_code,
'channel_name' => $row->channel_name,
];
});
return [
'success' => true,
'message' => '查询成功',
'data' => ['total' => $list->count(), 'list' => $list->values()->toArray()]
];
}
public function checkIn(string $hisRequestNo): array
{
return DB::transaction(function () use ($hisRequestNo) {
$application = DB::table('exam_application')
->where('his_request_no', $hisRequestNo)
->lockForUpdate()
->first();
if (!$application) {
return ['success' => false, 'message' => '申请单不存在: ' . $hisRequestNo];
}
if ($application->status == 4) {
return ['success' => false, 'message' => '申请单已作废,无法报到'];
}
$appointment = DB::table('exam_appointment')
->where('exam_application_id', $application->id)
->where('status', 1)
->lockForUpdate()
->first();
if (!$appointment) {
return ['success' => false, 'message' => '该申请单没有处于已预约状态的预约记录'];
}
DB::table('exam_appointment')
->where('id', $appointment->id)
->update([
'status' => 2,
'check_in_time' => now(),
'updated_at' => now(),
]);
DB::table('exam_application')
->where('id', $application->id)
->update([
'status' => 2,
'updated_at' => now(),
]);
$resource = DB::table('department_resource')
->where('id', $appointment->department_resource_id)
->first();
$this->logOperation(
$application->id,
2,
2,
$appointment->id,
1,
'HIS',
['status' => 1, 'check_in_time' => null],
['status' => 2, 'check_in_time' => now()->toDateTimeString()],
'三方系统通知报到'
);
return [
'success' => true,
'message' => '报到成功',
'data' => [
'appointment_id' => $appointment->id,
'his_request_no' => $hisRequestNo,
'patient_name' => $application->patient_name,
'examination_item_name' => $application->examination_item_name,
'resource_name' => $resource->name ?? null,
'check_in_time' => now()->toDateTimeString(),
]
];
});
}
public function cancelCheckIn(string $hisRequestNo, ?string $reason = null): array
{
return DB::transaction(function () use ($hisRequestNo, $reason) {
$application = DB::table('exam_application')
->where('his_request_no', $hisRequestNo)
->lockForUpdate()
->first();
if (!$application) {
return ['success' => false, 'message' => '申请单不存在: ' . $hisRequestNo];
}
$appointment = DB::table('exam_appointment')
->where('exam_application_id', $application->id)
->where('status', 2)
->lockForUpdate()
->first();
if (!$appointment) {
return ['success' => false, 'message' => '该申请单没有处于已到检状态的预约记录'];
}
DB::table('exam_appointment')
->where('id', $appointment->id)
->update([
'status' => 1,
'check_in_time' => null,
'updated_at' => now(),
]);
DB::table('exam_application')
->where('id', $application->id)
->update([
'status' => 1,
'updated_at' => now(),
]);
$resource = DB::table('department_resource')
->where('id', $appointment->department_resource_id)
->first();
$this->logOperation(
$application->id,
2,
2,
$appointment->id,
1,
'HIS',
['status' => 2, 'check_in_time' => $appointment->check_in_time],
['status' => 1, 'check_in_time' => null],
'三方系统通知取消报到' . ($reason ? ': ' . $reason : '')
);
return [
'success' => true,
'message' => '取消报到成功',
'data' => [
'appointment_id' => $appointment->id,
'his_request_no' => $hisRequestNo,
'patient_name' => $application->patient_name,
'examination_item_name' => $application->examination_item_name,
'resource_name' => $resource->name ?? null,
]
];
});
}
private function logOperation(
int $applicationId,
int $operationType,
int $entityType,
int $entityId,
int $operatorType,
string $operatorId,
?array $oldValues = null,
?array $newValues = null,
?string $operationReason = null
): void {
try {
DB::table('operation_log')->insert([
'application_id' => $applicationId,
'operator_type' => $operatorType,
'operator_id' => $operatorId,
'operation_type' => $operationType,
'entity_type' => $entityType,
'entity_id' => $entityId,
'old_values' => $oldValues ? json_encode($oldValues, JSON_UNESCAPED_UNICODE) : null,
'new_values' => $newValues ? json_encode($newValues, JSON_UNESCAPED_UNICODE) : null,
'operation_time' => now(),
'operation_reason' => $operationReason,
'client_ip' => request()->ip(),
]);
} catch (\Exception $e) {
Log::error('记录操作日志失败:' . $e->getMessage());
}
}
}