feat: 检查报告模块

medical
Rmiku 2 months ago
parent 4518acb3a8
commit 03e6e10048
  1. 68
      app/Http/Controllers/Report/InspectController.php
  2. 143
      app/Http/Logics/Report/InspectLogic.php
  3. 72
      app/Http/Resources/Report/Inspect/DetailsResource.php
  4. 51
      app/Http/Resources/Report/Inspect/ListsResource.php
  5. 31
      app/Services/HisHttp/Client.php
  6. 205
      app/Services/MedicalAuth/Client.php
  7. 22
      app/Utils/Traits/BuildCacheKeyName.php
  8. 14
      routes/api.php

@ -0,0 +1,68 @@
<?php
declare(strict_types = 1);
namespace App\Http\Controllers\Report;
use App\Exceptions\GeneralException;
use App\Http\Logics\Registration\RecordLogic;
use App\Http\Logics\Report\InspectLogic;
use App\Http\Resources\Report\Inspect\DetailsResource;
use App\Http\Resources\Report\Inspect\ListsResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class InspectController
{
protected InspectLogic $inspect_logic;
/**
* InspectController Construct.
*/
public function __construct()
{
$this->inspect_logic = new InspectLogic();
}
/**
* 获取检查报告列表
* @param Request $request
* @param string $patient_id
* @return JsonResponse
* @throws GeneralException
*/
public function lists(Request $request, string $patient_id): JsonResponse
{
$validated = $request->validate([
'start_date' => 'required|date_format:Y-m-d',
'end_date' => 'required|date_format:Y-m-d|after:start_date',
], [
'start_date.required' => '请选择查询开始日期',
'end_date.required' => '请选择查询结束日期',
'start_date.date_format' => '日期格式错误',
'end_date.date_format' => '日期格式错误',
'end_date.after' => '查询日期错误',
]);
// 日期必须在3个月之内
$response = $this->inspect_logic->lists($patient_id, $validated['start_date'], $validated['end_date']);
return jsonResponse(Response::HTTP_OK, 'success', ListsResource::make($response)->toArray());
}
/**
* 获取缴费记录详情
* @param Request $request
* @param string $patient_id
* @param string $serial_no
* @return JsonResponse
* @throws GeneralException
*/
public function details(Request $request, string $patient_id, string $serial_no): JsonResponse
{
$response = $this->inspect_logic->details($patient_id, $serial_no);
return jsonResponse(Response::HTTP_OK, 'success.', DetailsResource::make($response)->toArray());
}
}

@ -0,0 +1,143 @@
<?php
declare(strict_types = 1);
namespace App\Http\Logics\Report;
use App\Dictionary\Order\PayType;
use App\Dictionary\Order\SourceId;
use App\Dictionary\Order\Status;
use App\Dictionary\Order\Type;
use App\Dictionary\Patient\CardType;
use App\Exceptions\GeneralException;
use App\Models\Order;
use App\Models\Patient;
use App\Models\RegistrationRecord;
use App\Services\HisHttp\Client;
use App\Utils\Traits\BuildCacheKeyName;
use App\Utils\Traits\Logger;
use App\Utils\Traits\MiniProgramAuth;
use App\Utils\Traits\SendSubscribeMessage;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Redis;
use ReflectionException;
use Symfony\Component\HttpFoundation\Response;
use UnifyPayment\Cores\Struct\RefundOrder;
use UnifyPayment\Unify;
class InspectLogic
{
use Logger;
use MiniProgramAuth;
use BuildCacheKeyName;
private Client $his_client;
private Patient $patient_model;
private Order $order_model;
/**
* RecordLogic Construct
* @throws AuthenticationException
*/
public function __construct()
{
$this->authInitialize();
$this->setChannel('refund');
$this->his_client = app('HisHttpService');
$this->patient_model = new Patient();
}
/**
* 获取报告列表
* @param string $patient_number
* @param string $start_date
* @param string $end_date
* @return mixed
* @throws GeneralException
*/
public function lists(string $patient_number, string $start_date, string $end_date): mixed
{
$patient_info = $this->getHisPatientInfo($this->open_id, $patient_number);
$response = $this->his_client->getInspectReportLists($patient_info['name'], $patient_info['idCardNo'], $start_date, $end_date);
if (!isset($response['success']) || !$response['success']) {
throw new GeneralException($response['msg'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE);
}
Redis::setex($this->getInspectReportListsKey($this->open_id, $patient_info['patient_id']), 3600, json_encode($response['data'], JSON_UNESCAPED_UNICODE));
return $response['data'];
}
/**
* 获取报告详情
* @param string $patient_number
* @param string $serial_no
* @return array
* @throws GeneralException
*/
public function details(string $patient_number, string $serial_no)
{
$patient_info = $this->getPatientInfo($this->open_id, $patient_number);
$report_lists_cache_key = $this->getInspectReportListsKey($this->open_id, $patient_info['patient_id']);
if (!Redis::exists($report_lists_cache_key)) {
throw new GeneralException('找不到报告详情,请重新再试!', Response::HTTP_BAD_REQUEST);
}
$lists = Redis::get($report_lists_cache_key);
$lists = json_decode($lists, true);
$info = [];
foreach ($lists as $k => $v) {
if ($serial_no === $v['sourceId']) {
$info = [];
break;
}
}
if (empty($info)) {
throw new GeneralException('找不到报告详情,请重新再试!', Response::HTTP_BAD_REQUEST);
}
return $info;
}
/**
* 获取数据库里的患者信息
* @param string $open_id
* @param string $patient_number
* @return mixed
* @throws GeneralException
*/
protected function getPatientInfo(string $open_id, string $patient_number): mixed
{
$info = $this->patient_model->getBindPatientInfoByPatientNumber($open_id, $patient_number);
if (empty($info)) {
throw new GeneralException('找不到患者信息,请重新再试!', Response::HTTP_BAD_REQUEST);
}
return $info;
}
/**
* 获取HIS里的患者信息
* @param string $open_id
* @param string $patient_number
* @return mixed
* @throws GeneralException
*/
protected function getHisPatientInfo(string $open_id, string $patient_number): mixed
{
$info = $this->getPatientInfo($open_id, $patient_number);
$response = $this->his_client->getPatientInfo($info['patient_number'], CardType::OUTPATIENT_NO, $info['name']);
if (!isset($response['success']) || !$response['success']) {
throw new GeneralException($response['msg'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE);
}
return $response['data'];
}
}

@ -0,0 +1,72 @@
<?php
namespace App\Http\Resources\Report\Inspect;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class DetailsResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request = null): array
{
if (empty($this->resource)) {
return [];
}
$lists = [];
foreach ($this->resource['response']['body'] as $k => $v) {
// 过滤掉诊断为接口调用正常的第一条数据
if ($v['diagnose'] === '医院接口调用正常') {
continue;
}
$lists[] = [
'clinic_no' => $v['clinicNo'],
'serial_no' => $v['sourceId'],
'source_form' => $v['sourceFrom'],
'dept_id' => $v['dept']['localCode'],
'dept_name' => $v['dept']['localText'],
'doctor_id' => $v['doctor']['localText'],
'doctor_name' => $v['doctor']['localText'],
'exam_type' => $v['examMethod'],
'exam_name' => $v['examName'],
'exam_purpose' => $v['examPurpose'],
'diagnose' => $v['diagnose'],
'exam_view' => $v['examView'],
'diagnose_opinion' =>$v['diagnose_opinion'],
'created_time' => $v['createDt'],
'check_time' => $v['checkDt'],
'report_time' => $v['reportDt'],
'comment' => $v['comment'],
// 患者信息
'patient_name' => $v['patient']['name'],
'patient_gender' => $v['patient']['gender'],
'patient_birth' => $v['patient']['birthday'],
// 冗余信息
'org_id' => $v['org']['localCode'],
'org_name' => $v['org']['localText'],
'equipment_name' => $v['equipmentName'],
'equipment_no' => $v['equipmentNameNo'],
'report_doctor_id' => $v['rpDoctor']['localCode'],
'report_doctor_name' => $v['rpDoctor']['localText'],
'report_org_id' => $v['rpOrg']['localCode'],
'report_org_name' => $v['rpOrg']['localText'],
'crisis_flag' => $v['crisisFlag'],
'crisis_desc' => $v['crisisDesc'],
'crisis_content' => $v['crisisContent'],
'unusual_flag' => $v['unusualFlag'],
'deal_flag' => $v['dealFlag'],
'need_deal_flag' => $v['needDealFlag'],
// pacs 图片数组一般为空
'pacs_images' => (array) ($v['pacs'] ?? [])
];
}
return $lists;
}
}

@ -0,0 +1,51 @@
<?php
namespace App\Http\Resources\Report\Inspect;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ListsResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request = null): array
{
if (empty($this->resource)) {
return [];
}
$lists = [];
foreach ($this->resource['response']['body'] as $k => $v) {
// 过滤掉诊断为接口调用正常的第一条数据
if ($v['diagnose'] === '医院接口调用正常') {
continue;
}
$lists[] = [
'clinic_no' => $v['clinicNo'],
'serial_no' => $v['sourceId'],
'source_form' => $v['sourceFrom'],
'dept_id' => $v['dept']['localCode'],
'dept_name' => $v['dept']['localText'],
'doctor_id' => $v['doctor']['localText'],
'doctor_name' => $v['doctor']['localText'],
'exam_type' => $v['examMethod'],
'exam_name' => $v['examName'],
'exam_purpose' => $v['examPurpose'],
'diagnose' => $v['diagnose'],
'exam_view' => $v['examView'],
'diagnose_opinion' =>$v['diagnose_opinion'],
'created_time' => $v['createDt'],
'check_time' => $v['checkDt'],
'report_time' => $v['reportDt'],
'comment' => $v['comment'],
];
}
return $lists;
}
}

@ -645,4 +645,35 @@ class Client
} }
// Dictionary Module End // Dictionary Module End
// Inspect/Check Module Start
/**
* 获取检查报告列表
* @param string $patient_name 患者姓名
* @param string $card_no 证件号,居民注册时的证件号,不区分证件类型,一般为身份证
* @param string $start_date 查询起始日期,查询时间跨度必须小于3个月
* @param string $end_date 查询截止日期
* @param string $bar_code 条码,由患者直接录入或者扫描线下单据(可能是发票,处方等)获得
* @param string $source_id 报告标识,机构内部唯一标识
* @return mixed
* @throws GeneralException
*/
public function getInspectReportLists(string $patient_name, string $card_no, string $start_date, string $end_date, string $bar_code = '', string $source_id = ''): mixed
{
return $this->requestHandle('POST', '0rientQueryExaReport', [
'json' => [
'patientName' => $patient_name,
'orgId' => '45592623X44040211A1001', // 机构ID 珠海市香洲区第二人民医院
'barCode' => $bar_code,
'SourceId' => $source_id,
'certificateNo' => $card_no,
'startDt' => $start_date,
'endDt' => $end_date,
... $this->commonRequestData()
]
]);
}
// Inspect/Check Module End
} }

@ -0,0 +1,205 @@
<?php
declare(strict_types=1);
namespace App\Services\MedicalAuth;
use App\Utils\Traits\HttpRequest;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use JsonException;
use Psr\Http\Message\ResponseInterface;
// use JetBrains\PhpStorm\ArrayShape;
/**
* 医保移动支付免密授权 - 微信端
*
* @link https://yb.qq.com/yibao-payment/doc?nodeId=83679977515675648
*/
class Authorization
{
use HttpRequest;
/**
* 配置数组
* @var string[]
*/
/*#[ArrayShape([
'app_id' => "string",
'partner_id' => "string",
'partner_secret' => "string",
'channel' => "string",
'org_chnl_crtf_codg' => "string",
'org_codg' => "string",
'org_app_id' => "string",
'auth_return_url' => "string",
])]*/
private array $config;
/**
* 城市编码 441721 香洲区
* @var string
*/
private string $city_code = '441721';
/**
* 授权查询host地址
* @var string
*/
private string $host = 'https://test-receiver.wecity.qq.com';
// private string $host = 'https://mip-receiver.tengmed.com';
/**
* 免密授权host地址
* @var string
*/
private string $auth_host = 'https://mitest.wecity.qq.com';
// private string $auth_host = 'https://card.wecity.qq.com';
/**
* Authorization Class Construct.
*/
public function __construct()
{
$this->config = config('wechat.medical.auth');
}
/**
* 获取签名
* @param string $timestamp
* @return string
*/
private function getSign(string $timestamp): string
{
return hash_hmac("sha256", $this->config['partner_id'] . $timestamp, $this->config['partner_secret']);
}
/**
* 获取RequestId
* @return string
*/
private function getRequestId(): string
{
$char_id = strtoupper(md5(uniqid((string)mt_rand(), true)));
return substr($char_id, 0, 8)
. substr($char_id, 8, 4)
. substr($char_id, 12, 4)
. substr($char_id, 16, 4)
. substr($char_id, 20, 12);
}
/**
* 获取毫秒
* @return int
*/
private function getMillisecond(): int
{
[$t1, $t2] = explode(' ', microtime());
return (int)sprintf('%.0f', ((float)$t1 + (float)$t2) * 1000);
}
/**
* @param string $endpoint
* @param array $params
* @param string $method
* @param array $options
* @return ResponseInterface|string[]
*/
protected function requestHanle(string $endpoint, array $params = [], string $method = 'post', array $options = [])
{
$timestamp = $this->getMillisecond();
$params = array_merge([
'base_uri' => $this->host,
'headers' => [
'god-portal-timestamp' => $timestamp,
'god-portal-request-id' => $this->getRequestId(),
'god-portal-signature' => $this->getSign((string)$timestamp),
],
'json' => $params
], $options);
try {
$response = $this->request($endpoint, $method, $params);
$this->writerLog($endpoint, $params, $response);
return $response;
} catch (Exception $e) {
return [
'code' => -1,
//'message' => "{$e->getMessage()} ON {$e->getFile()}:{$e->getLine()}"
'message' => $e->getMessage()
];
}
}
/**
* 记录日志
* @param string $request_url
* @param mixed $request_data
* @param mixed $response
* @return void
*/
private function writerLog(string $request_url, mixed $request_data, mixed $response): void
{
//写入日志
$log_content = [
'[REQUEST TIME] ' . date('Y-m-d H:i:s'),
'[REQUEST URL] ' . $request_url,
'[REQUEST PARAM] ' . json_encode($request_data, JSON_UNESCAPED_UNICODE),
'[RESPONSE PARAM] ' . json_encode($response, JSON_UNESCAPED_UNICODE),
"\r\n",
];
recordLog('authorization', implode("\r\n", $log_content));
}
/**
* 获取授权跳转地址
* @param string $return_url 回调地址
* @return string
*/
public function getAuthRedirectUrl(string $return_url): string
{
$url = '/oauth/code';
$data = [
'authType' => 2,
'isDepart' => 2,
'bizType' => '04107',
'appid' => $this->config['app_id'],
'cityCode' => $this->config['city_code'],
'channel' => $this->config['channel'],
'orgChnlCrtfCodg' => $this->config['org_chnl_crtf_codg'],
'orgCodg' => $this->config['org_codg'],
'orgAppId' => $this->config['org_app_id'],
'redirectUrl' => $return_url
];
$redirect_url = $this->auth_host . $url . '?' . http_build_query($data);
$this->writerLog($url, $data, '');
return $redirect_url;
}
/**
* 查询用户授权
* @param string $auth_code
* @param string $open_id
* @return ResponseInterface|string[]
* @throws GuzzleException
*/
public function userQuery(string $auth_code, string $open_id): array|ResponseInterface
{
$uri = '/api/mipuserquery/userQuery/' . $this->config['partner_id'];
$param = [
'qrcode' => $auth_code,
'openid' => $open_id,
];
return $this->requestHanle($uri, $param);
}
}

@ -5,6 +5,17 @@ namespace App\Utils\Traits;
trait BuildCacheKeyName trait BuildCacheKeyName
{ {
/**
* 获取 检验报告列表 缓存建名
* @param string $open_id
* @param string $patient_id
* @return string
*/
public function getInspectReportListsKey(string $open_id, string $patient_id): string
{
return 'report.inspect.'. $open_id. '.'. $patient_id;
}
/** /**
* 获取 门诊待缴费列表 缓存键名 * 获取 门诊待缴费列表 缓存键名
* @param string $open_id * @param string $open_id
@ -27,4 +38,15 @@ trait BuildCacheKeyName
{ {
return 'outpatient.pending.'. $open_id.'.'. $patient_id. '.'. $serial_no; return 'outpatient.pending.'. $open_id.'.'. $patient_id. '.'. $serial_no;
} }
/**
* 获取用户 免密授权 缓存键名
* @param string $open_id
* @param string $patient_id
* @return string
*/
public function getUserMedicalInsuranceAuthKey(string $open_id, string $patient_id): string
{
return 'medical.user.info.'. $open_id.'.'. $patient_id;
}
} }

@ -4,6 +4,7 @@ use App\Http\Controllers\Auth\AuthController;
use App\Http\Controllers\Hospital\IntroduceController; use App\Http\Controllers\Hospital\IntroduceController;
use App\Http\Controllers\Message\TriggerController; use App\Http\Controllers\Message\TriggerController;
use App\Http\Controllers\Notify\NotifyController; use App\Http\Controllers\Notify\NotifyController;
use App\Http\Controllers\Outpatient\MedicalController;
use App\Http\Controllers\Outpatient\PaymentController; use App\Http\Controllers\Outpatient\PaymentController;
use App\Http\Controllers\Outpatient\PendingController; use App\Http\Controllers\Outpatient\PendingController;
use App\Http\Controllers\Outpatient\RecordController as OutpatientRecordController; use App\Http\Controllers\Outpatient\RecordController as OutpatientRecordController;
@ -12,6 +13,7 @@ use App\Http\Controllers\Registration\RecordController as RegistrationRecordCont
use App\Http\Controllers\Registration\RegisterController; use App\Http\Controllers\Registration\RegisterController;
use App\Http\Controllers\Registration\ScheduleController; use App\Http\Controllers\Registration\ScheduleController;
use App\Http\Controllers\Dictionary\ItemController; use App\Http\Controllers\Dictionary\ItemController;
use App\Http\Controllers\Report\InspectController;
use App\Http\Controllers\Test\TestController; use App\Http\Controllers\Test\TestController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
@ -51,6 +53,12 @@ Route::middleware(['apiLog'])->group(function() {
Route::post('/{patient_id}/record/{serial_no}/refund', [RegistrationRecordController::class, 'refund']); Route::post('/{patient_id}/record/{serial_no}/refund', [RegistrationRecordController::class, 'refund']);
}); });
// 医保模块
Route::prefix('Medical')->group(function () {
Route::get('/auth/path', [MedicalController::class, 'redirectAuth']);
Route::post('/auth/{patient_id}/info', [MedicalController::class, 'userAuthInfo']);
});
// 缴费模块 // 缴费模块
Route::prefix('Outpatient')->group(function () { Route::prefix('Outpatient')->group(function () {
Route::get('/{patient_id}/pending', [PendingController::class, 'lists']); Route::get('/{patient_id}/pending', [PendingController::class, 'lists']);
@ -66,6 +74,12 @@ Route::middleware(['apiLog'])->group(function() {
Route::get('/', [ItemController::class, 'lists']); Route::get('/', [ItemController::class, 'lists']);
Route::get('/{type_id}', [ItemController::class, 'details'])->where('type_id', '[0-9]+'); Route::get('/{type_id}', [ItemController::class, 'details'])->where('type_id', '[0-9]+');
}); });
// 报告相关模块
Route::prefix('Report')->group(function () {
Route::get('/{patient_id}/inspect', [InspectController::class, 'lists']);
Route::get('/{patient_id}/inspect/{serial_no}/', [InspectController::class, 'details']);
});
}); });
// 医院详情相关项目 // 医院详情相关项目

Loading…
Cancel
Save