feat: 订阅消息推送,患者模块接入正式环境数据

master
Rmiku 1 month ago
parent ecf9758d64
commit bd580f4093
  1. 80
      app/Console/Commands/SendAppointmentReminders.php
  2. 51
      app/Dictionary/PushMessage/Type.php
  3. 2
      app/Dictionary/SendMessage/Status.php
  4. 52
      app/Dictionary/SendMessage/Type.php
  5. 3
      app/Dictionary/WeChat/MiniProgram/OpenApi.php
  6. 47
      app/Dictionary/WeChat/MiniProgram/SubscribeId.php
  7. 58
      app/Dictionary/WeChat/Official/SubscribeId.php
  8. 58
      app/Dictionary/WeChat/Official/TemplateId.php
  9. 5
      app/Http/Controllers/Auth/AuthController.php
  10. 8
      app/Http/Logics/Dictionary/ItemLogic.php
  11. 12
      app/Http/Logics/Notify/NotifyLogic.php
  12. 8
      app/Http/Logics/Outpatient/PaymentLogic.php
  13. 50
      app/Http/Logics/Patient/PatientLogic.php
  14. 10
      app/Http/Logics/Registration/RegisterLogic.php
  15. 8
      app/Http/Logics/Registration/ScheduleLogic.php
  16. 4
      app/Http/Resources/Patient/PatientDetailsResource.php
  17. 6
      app/Http/Resources/Registration/Schedule/DeptListsResource.php
  18. 48
      app/Jobs/Message/Message.php
  19. 20
      app/Jobs/Message/SubscriptionMessage.php
  20. 159
      app/Jobs/SendWeChatMessageJob.php
  21. 10
      app/Models/Order.php
  22. 2
      app/Models/RegistrationRecord.php
  23. 25
      app/Models/SendMessageJob.php
  24. 5
      app/Services/HisHttp/Client.php
  25. 260
      app/Utils/Traits/SendSubscribeMessage.php
  26. 13
      app/Utils/Transfer/HisHttpClient/ClientMockHttpTransfer.php
  27. 6
      bootstrap/app.php
  28. 4
      config/custom.php
  29. 9
      config/logging.php
  30. 4
      config/wechat.php
  31. 3
      database/migrations/2023_03_03_080312_create_orders_table.php
  32. 1
      database/migrations/2023_03_03_080715_create_registration_records_table.php
  33. 7
      database/migrations/2023_03_03_082229_create_send_message_jobs_table.php
  34. 33
      database/migrations/2024_12_24_110504_create_personal_access_tokens_table.php

@ -0,0 +1,80 @@
<?php
namespace App\Console\Commands;
use App\Dictionary\SendMessage\Type;
use App\Dictionary\WeChat\MiniProgram\SubscribeId;
use App\Jobs\SendWeChatMessageJob;
use App\Models\RegistrationRecord;
use App\Models\SendMessageJob;
use App\Utils\Traits\SendSubscribeMessage;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use \Symfony\Component\Console\Command\Command as BaseCommand;
use Throwable;
/**
* Appointment Visit Reminders 发送预约就诊提示
* @example
*/
class SendAppointmentReminders extends Command
{
use SendSubscribeMessage;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:send-appointment-reminders';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send appointment reminders to users via WeChat subscription messages';
/**
* Execute the console command.
*/
public function handle(): int
{
$this->info('Starting to send appointment reminders...');
// 查询即将到期的预约记录(8小时后的预约)
$appointments = RegistrationRecord::where('visit_date', now()->toDate())
->where('begin_date', now()->subHours(8))
->where('reminder_sent', 0)
->get();
if ($appointments->isEmpty()) {
$this->info('No appointments to send reminders for.');
return BaseCommand::SUCCESS;
}
$message = new SendMessageJob();
$success_records = [];
foreach ($appointments as $appointment) {
try {
$this->sendAppointmentReminderMessage($appointment);
// 标记提醒已发送
$appointment->update(['reminder_sent' => 1]);
$success_records[] = $appointment->id;
$this->info("Reminder sent for appointment ID: {$appointment->id}");
Log::channel('send_wechat_message')->info('Reminder sent for appointment ID: '. $appointment->id);
} catch (Exception|Throwable $e) {
// 记录错误日志
$err_msg = "{$e->getMessage()} ON {$e->getFile()}:{$e->getLine()}";
$this->error("Failed to send reminder for appointment ID: {$appointment->id}, Err msg: {$err_msg}");
Log::channel('send_wechat_message')->error('Failed to send reminder for appointment ID: '. $appointment->id, ['error' => $err_msg]);
}
}
$this->info( '['. date('Y-m-d H:i:s').'] Appointment reminders sent successfully. send Record:'. implode(',', $success_records));
return BaseCommand::SUCCESS;
}
}

@ -1,51 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Dictionary\PushMessage;
use App\Dictionary\WeChat\Official\OpenApi;
/**
* 推送微信消息类型
*/
enum Type: int
{
case OFFICIAL_TEMPLATE = 1;
case OFFICIAL_SINGLE_SUBSCRIBE = 2;
case OFFICIAL_SUBSCRIBE = 3;
case OFFICIAL_CUSTOM = 4;
/**
* Label string.
*
* @return string
*/
public function label(): string
{
return match ($this) {
self::OFFICIAL_TEMPLATE => '公众号模板消息',
self::OFFICIAL_SINGLE_SUBSCRIBE => '公众号一次性订阅消息',
self::OFFICIAL_SUBSCRIBE => '公众号订阅消息',
self::OFFICIAL_CUSTOM => '公众号客服消息',
};
}
/**
* Get Open Api.
*
* @return OpenApi
*/
public function api(): OpenApi
{
return match ($this) {
self::OFFICIAL_TEMPLATE => OpenApi::SEND_TEMPLATE_MESSAGE,
self::OFFICIAL_SINGLE_SUBSCRIBE => OpenApi::SEND_SINGLE_SUBSCRIBE_MESSAGE,
self::OFFICIAL_SUBSCRIBE => OpenApi::SEND_SUBSCRIBE_MESSAGE,
self::OFFICIAL_CUSTOM => OpenApi::SEND_CUSTOM_MESSAGE,
};
}
}

@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace App\Dictionary\PushMessage; namespace App\Dictionary\SendMessage;
use App\Dictionary\WeChat\Official\OpenApi; use App\Dictionary\WeChat\Official\OpenApi;

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace App\Dictionary\SendMessage;
use App\Dictionary\WeChat\MiniProgram\OpenApi as MiniOpenApi;
use App\Dictionary\WeChat\Official\OpenApi as OfficialOpenApi;
/**
* 推送微信消息类型
*/
enum Type: int
{
case TEMPLATE = 1;
case SINGLE_SUBSCRIBE = 2;
case SUBSCRIBE = 3;
case CUSTOM = 4;
/**
* Label string.
*
* @return string
*/
public function label(): string
{
return match ($this) {
self::TEMPLATE => '模板消息',
self::SINGLE_SUBSCRIBE => '一次性订阅消息',
self::SUBSCRIBE => '长期订阅消息',
self::CUSTOM => '客服消息',
};
}
/**
* Get Open Api.
*
* @return OfficialOpenApi|MiniOpenApi
*/
public function api(): OfficialOpenApi|MiniOpenApi
{
return match ($this) {
self::TEMPLATE => OfficialOpenApi::SEND_TEMPLATE_MESSAGE,
self::SINGLE_SUBSCRIBE,
self::SUBSCRIBE => MiniOpenApi::SEND_SUBSCRIBE_MESSAGE,
self::CUSTOM => OfficialOpenApi::SEND_CUSTOM_MESSAGE,
};
}
}

@ -30,4 +30,7 @@ enum OpenApi: string
case GET_PAID_UNION_ID = '/wxa/getpaidunionid'; case GET_PAID_UNION_ID = '/wxa/getpaidunionid';
case GET_PHONE_NUMBER = '/wxa/business/getuserphonenumber'; case GET_PHONE_NUMBER = '/wxa/business/getuserphonenumber';
// 推送消息
case SEND_SUBSCRIBE_MESSAGE = 'cgi-bin/message/subscribe/send';
} }

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\Dictionary\WeChat\MiniProgram;
/**
* 推送模板消息ID
*/
enum SubscribeId: string
{
case PATIENT_BIND_SUCCESS = 'C1JKomPq-Bes7fOs4vv2Jcy5SFUVvnr_qcl9oNaz7eQ';
case PATIENT_UNBIND_SUCCESS = 'oDOqp-Smyve0Se8ued4592HC82fBxRLodD1CCb2FWBU';
case REGISTRATION_SUCCESS = '8TEAjsZsuK8atvLeLqzEH067Mnn5qGmyFnshEy6YsiU';
case REGISTRATION_FAILURE = '4I_LqdovtP1gwkQjH78Y7gwuPde_rY0qgPqNl4aPBWk';
case REGISTRATION_CANCEL = 'fVaoPnY6MOpKXbpnrGcPqcLl8dv-aRBNfCGeOlgL8bA';
case OUTPATIENT_PAYMENT_SUCCESS = 'IlYVy2NNv7v6sg-s_Y6OYS4bRlulosztG0fn7LW4qcQ';
case OUTPATIENT_PAYMENT_FAILURE = 'ayUD7DXCxZAlhWZ7YiWnHvUXf3AqS_6xkWlbCMC40ek';
case VISIT_REMIND = 'PidheHDjaf--SIu4-wsDT2uFA_0gjDhq_BU0fABa_w0';
/**
* Label string
*
* @return string
*/
public function label(): string
{
return match ($this) {
self::PATIENT_BIND_SUCCESS => '就诊人解绑成功提醒',
self::PATIENT_UNBIND_SUCCESS => '就诊人绑定成功提醒',
self::REGISTRATION_SUCCESS => '挂号成功通知',
self::REGISTRATION_FAILURE => '挂号失败通知',
self::REGISTRATION_CANCEL => '取消预约挂号通知',
self::OUTPATIENT_PAYMENT_SUCCESS => '门诊缴费成功通知',
self::OUTPATIENT_PAYMENT_FAILURE => '门诊缴费失败通知',
self::VISIT_REMIND => '就诊提醒',
};
}
}

@ -1,58 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Dictionary\WeChat\Official;
/**
* 推送模板消息ID
*/
enum SubscribeId: string
{
case REGISTRATION_SUCCESS = 'ASUyXyVFONT6RNwbAs49_rHKQUkc_7iXs31kt1AyACU';
case REGISTRATION_FAILURE = '36fosQYJU-dN8AsNZERrXY9bfEhP1N9zoqVkORnVr_o';
case REGISTRATION_CANCEL = '8sWThvcSxg0uS6AFy0LWOeGjbRTUI_n2QHDDMYffjGo';
case OUTPATIENT_PENDING = 'qieeDt7t2XnT0xB8Fc5ec_peuQDN4TQFPl3ivsILKF0';
case OUTPATIENT_PAYMENT = 'Aeww94Ahcr0q8bGaAQTSW3s6CB2RXdN9qxZV65bYhhc';
case INPATIENT_RECHARGE_SUCCESS = 'V30A9ZZ6dqJe_DrYhB5YTUmDbj_F99oJ5k3XP4LK7qk';
case INPATIENT_RECHARGE_FAILURE = '02HnYY5HFP9LKrWDX_UsQNq2vw3ttfGBvZ4H7SfPZyM';
case REFUND = '3C9Zw6gBgESwciGF3y2ZeoelYXg1fiZeHAnOUXRf2nM';
case VISIT = 'IrpIcVU20c5GzBPNUqkp_lvuO-t8gxjiiGx7S88GvIA';
case SUSPEND_VISIT = 'fTUc-MhhqpBA9WzqQQtfceAmgRr8EFuJNDSdvRDG5K8';
case TAKE_MEDICINE = '-NX91rCEWPejiKVgbkkGf1QF4zNyH7tadHnkTDqyK_c';
case TODO_LIST = 'pLdQ5HfXP12C8bHM8EiUP2dqOVFqv-2m0YGCpqC-54Y';
/**
* Label string
*
* @return string
*/
public function label(): string
{
return match ($this) {
self::REGISTRATION_SUCCESS => '挂号成功通知',
self::REGISTRATION_FAILURE => '挂号失败通知',
self::REGISTRATION_CANCEL => '挂号取消通知',
self::OUTPATIENT_PENDING => '门诊待缴费通知',
self::OUTPATIENT_PAYMENT => '门诊缴费通知',
self::INPATIENT_RECHARGE_SUCCESS => '住院预交金支付成功通知',
self::INPATIENT_RECHARGE_FAILURE => '住院预交失败提醒',
self::REFUND => '退费通知',
self::VISIT => '就诊提醒',
self::SUSPEND_VISIT => '医生停诊通知',
self::TAKE_MEDICINE => '取药通知',
self::TODO_LIST => '待办事项通知'
};
}
}

@ -1,58 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Dictionary\WeChat\Official;
/**
* 推送模板消息ID
*/
enum TemplateId: string
{
case REGISTRATION_SUCCESS = 'ASUyXyVFONT6RNwbAs49_rHKQUkc_7iXs31kt1AyACU';
case REGISTRATION_FAILURE = '36fosQYJU-dN8AsNZERrXY9bfEhP1N9zoqVkORnVr_o';
case REGISTRATION_CANCEL = '8sWThvcSxg0uS6AFy0LWOeGjbRTUI_n2QHDDMYffjGo';
case OUTPATIENT_PENDING = 'qieeDt7t2XnT0xB8Fc5ec_peuQDN4TQFPl3ivsILKF0';
case OUTPATIENT_PAYMENT = 'Aeww94Ahcr0q8bGaAQTSW3s6CB2RXdN9qxZV65bYhhc';
case INPATIENT_RECHARGE_SUCCESS = 'V30A9ZZ6dqJe_DrYhB5YTUmDbj_F99oJ5k3XP4LK7qk';
case INPATIENT_RECHARGE_FAILURE = '02HnYY5HFP9LKrWDX_UsQNq2vw3ttfGBvZ4H7SfPZyM';
case REFUND = '3C9Zw6gBgESwciGF3y2ZeoelYXg1fiZeHAnOUXRf2nM';
case VISIT = 'IrpIcVU20c5GzBPNUqkp_lvuO-t8gxjiiGx7S88GvIA';
case SUSPEND_VISIT = 'fTUc-MhhqpBA9WzqQQtfceAmgRr8EFuJNDSdvRDG5K8';
case TAKE_MEDICINE = '-NX91rCEWPejiKVgbkkGf1QF4zNyH7tadHnkTDqyK_c';
case TODO_LIST = 'pLdQ5HfXP12C8bHM8EiUP2dqOVFqv-2m0YGCpqC-54Y';
/**
* Label string
*
* @return string
*/
public function label(): string
{
return match ($this) {
self::REGISTRATION_SUCCESS => '挂号成功通知',
self::REGISTRATION_FAILURE => '挂号失败通知',
self::REGISTRATION_CANCEL => '挂号取消通知',
self::OUTPATIENT_PENDING => '门诊待缴费通知',
self::OUTPATIENT_PAYMENT => '门诊缴费通知',
self::INPATIENT_RECHARGE_SUCCESS => '住院预交金支付成功通知',
self::INPATIENT_RECHARGE_FAILURE => '住院预交失败提醒',
self::REFUND => '退费通知',
self::VISIT => '就诊提醒',
self::SUSPEND_VISIT => '医生停诊通知',
self::TAKE_MEDICINE => '取药通知',
self::TODO_LIST => '待办事项通知'
};
}
}

@ -10,11 +10,6 @@ use App\Http\Requests\Auth\LoginRequest;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
class AuthController extends Controller class AuthController extends Controller
{ {

@ -36,8 +36,8 @@ class ItemLogic
public function getLists() public function getLists()
{ {
$response = $this->his_client->getDictionaryLists(); $response = $this->his_client->getDictionaryLists();
if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { if (!isset($response['success']) || !$response['success']) {
throw new GeneralException($response['ERRORMSG'] ?? '找不到缴费项目分类列表!', Response::HTTP_SERVICE_UNAVAILABLE); throw new GeneralException($response['msg'] ?? '找不到缴费项目分类列表!', Response::HTTP_SERVICE_UNAVAILABLE);
} }
return $response; return $response;
@ -52,8 +52,8 @@ class ItemLogic
public function getDetails(int $type_id): array public function getDetails(int $type_id): array
{ {
$response = $this->his_client->getDictionaryDetails($type_id); $response = $this->his_client->getDictionaryDetails($type_id);
if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { if (!isset($response['success']) || !$response['success']) {
throw new GeneralException($response['ERRORMSG'] ?? '找不到缴费项目分类详情!', Response::HTTP_SERVICE_UNAVAILABLE); throw new GeneralException($response['msg'] ?? '找不到缴费项目分类详情!', Response::HTTP_SERVICE_UNAVAILABLE);
} }
return $response; return $response;

@ -15,6 +15,7 @@ use App\Models\Patient as PatientModel;
use App\Services\HisHttp\Client; use App\Services\HisHttp\Client;
use App\Utils\Traits\Logger; use App\Utils\Traits\Logger;
use App\Utils\Traits\RedisLockUtil; use App\Utils\Traits\RedisLockUtil;
use App\Utils\Traits\SendSubscribeMessage;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\Kernel\Exceptions\InvalidConfigException; use EasyWeChat\Kernel\Exceptions\InvalidConfigException;
use EasyWeChat\Pay\Application as PaymentApplication; use EasyWeChat\Pay\Application as PaymentApplication;
@ -32,6 +33,7 @@ class NotifyLogic
{ {
use Logger; use Logger;
use RedisLockUtil; use RedisLockUtil;
use SendSubscribeMessage;
protected Client $his_client; protected Client $his_client;
@ -151,16 +153,19 @@ class NotifyLogic
$this->unifyConfirm($notify['out_trade_no'], $response['VISITNO'], $notify['openid'], $notify['transaction_id']); $this->unifyConfirm($notify['out_trade_no'], $response['VISITNO'], $notify['openid'], $notify['transaction_id']);
// 推送成功 // 推送成功
$this->sendRegistrationSuccessMessage($order_info);
} else if (isset($response['RESULTCODE'])) { } else if (isset($response['RESULTCODE'])) {
// 失败流程 // 失败流程
$this->handleOrderReverse($order_info, $response['ERRORMSG'] ?? ''); $this->handleOrderReverse($order_info, $response['ERRORMSG'] ?? '');
// 推送失败 // 推送失败
$this->sendRegistrationFailureMessage($order_info);
} else { } else {
// 异常流程 // 异常流程
$order_info->abnormalOrderOpera($order_info->id); $order_info->abnormalOrderOpera($order_info->id);
// 推送异常 // 推送异常
$this->sendRegistrationFailureMessage($order_info);
} }
} }
@ -202,19 +207,19 @@ class NotifyLogic
$this->unifyConfirm($notify['out_trade_no'], $response['HOSTRANNO'], $notify['openid'], $notify['transaction_id']); $this->unifyConfirm($notify['out_trade_no'], $response['HOSTRANNO'], $notify['openid'], $notify['transaction_id']);
// 推送成功 // 推送成功
$this->sendOutpatientPaymentSuccessMessage($order_info);
} else if (isset($response['ResultCode'])) { } else if (isset($response['ResultCode'])) {
// 失败流程 // 失败流程
$this->handleOrderReverse($order_info, $response['ERRORMSG']); $this->handleOrderReverse($order_info, $response['ERRORMSG']);
// 推送失败 // 推送失败
$this->sendOutpatientPaymentFailureMessage($order_info);
} else { } else {
// 异常流程 // 异常流程
$order_info->abnormalOrderOpera($order_info->id); $order_info->abnormalOrderOpera($order_info->id);
// 推送异常 // 推送异常
$this->sendOutpatientPaymentFailureMessage($order_info);
} }
} }
@ -257,6 +262,7 @@ class NotifyLogic
$refund_order_id = $order_info->getRefundOrderId($order_info->order_id); $refund_order_id = $order_info->getRefundOrderId($order_info->order_id);
$order_info->createRefundOReverseOrder( $order_info->createRefundOReverseOrder(
$order_info->id, $order_info->id,
$order_info->relate_patient_id,
$refund_order_id, $refund_order_id,
PayType::from($order_info->pay_type), PayType::from($order_info->pay_type),
$order_info->fee, $order_info->fee,

@ -68,7 +68,7 @@ class PaymentLogic
$order = $this->createOrder($order_id, $pay_type, $total_fee, $order_type, $patient_info, $pending_info); $order = $this->createOrder($order_id, $pay_type, $total_fee, $order_type, $patient_info, $pending_info);
// 申请支付 // 申请支付
$pay_data = $this->applyPayment($order_type, $order_id, $total_fee, $patient_info['PATIENTID'], $patient_info['NAME']); $pay_data = $this->applyPayment($order_type, $order_id, $total_fee, $patient_info['patientNumber'], $patient_info['name']);
// 去除无用数据 // 去除无用数据
unset($pay_data['merchantId'], $pay_data['merchantName'], $pay_data['channelId'], $pay_data['channelName']); unset($pay_data['merchantId'], $pay_data['merchantName'], $pay_data['channelId'], $pay_data['channelName']);
@ -91,11 +91,12 @@ class PaymentLogic
} }
$patient_info = $this->his_client->getPatientInfo($info['patient_id'], CardType::OUTPATIENT_NO, $info['name']); $patient_info = $this->his_client->getPatientInfo($info['patient_id'], CardType::OUTPATIENT_NO, $info['name']);
if (!isset($patient_info['RESULTCODE']) || $patient_info['RESULTCODE'] !== '0') { if (!isset($patient_info['success']) || !$patient_info['success']) {
throw new GeneralException($patient_info['ERRORMSG'] ?? '找不到患者信息,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); throw new GeneralException($patient_info['msg'] ?? '找不到患者信息,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE);
} }
// 添加Patient 表ID // 添加Patient 表ID
$patient_info = &$response['response'];
$patient_info['id'] = $info['id']; $patient_info['id'] = $info['id'];
$this->info('缴费患者信息', $patient_info); $this->info('缴费患者信息', $patient_info);
@ -174,6 +175,7 @@ class PaymentLogic
} }
$order = $this->order_model->createOrder( $order = $this->order_model->createOrder(
$patient_info['id'],
$order_id, $order_id,
$pay_type, $pay_type,
$total_fee * 100, $total_fee * 100,

@ -5,11 +5,16 @@ namespace App\Http\Logics\Patient;
use App\Dictionary\Patient\CardType; use App\Dictionary\Patient\CardType;
use App\Dictionary\Patient\Sex; use App\Dictionary\Patient\Sex;
use App\Dictionary\SendMessage\Type;
use App\Dictionary\WeChat\MiniProgram\SubscribeId;
use App\Exceptions\GeneralException; use App\Exceptions\GeneralException;
use App\Jobs\SendWeChatMessageJob;
use App\Models\Patient; use App\Models\Patient;
use App\Models\SendMessageJob;
use App\Services\HisHttp\Client; use App\Services\HisHttp\Client;
use App\Utils\Traits\Logger; use App\Utils\Traits\Logger;
use App\Utils\Traits\MiniProgramAuth; use App\Utils\Traits\MiniProgramAuth;
use App\Utils\Traits\SendSubscribeMessage;
use App\Utils\Traits\UniversalEncryption; use App\Utils\Traits\UniversalEncryption;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
@ -19,6 +24,7 @@ class PatientLogic
use Logger; use Logger;
use MiniProgramAuth; use MiniProgramAuth;
use UniversalEncryption; use UniversalEncryption;
use SendSubscribeMessage;
private Client $his_client; private Client $his_client;
@ -57,13 +63,12 @@ class PatientLogic
} }
// 获取患者信息 // 获取患者信息
$response = $this->his_client->getPatientInfo($info['patient_id'], CardType::MEDICAL_CARD, $info['name']); $response = $this->his_client->getPatientInfo($info['patient_id'], CardType::OUTPATIENT_NO, $info['name']);
if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { if (!isset($response['success']) || !$response['success']) {
throw new GeneralException($response['ERRORMSG'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE); throw new GeneralException($response['msg'] ?? '找不到该就诊卡!', Response::HTTP_SERVICE_UNAVAILABLE);
} }
$info->patient_card_id = $response['MZHM'] ?? ''; $info->birthday = $response['response']['birthday'] ?? '';
$info->card_no = substr($response['CARDNO'], 0, 3). '**********'. substr($response['CARDNO'], -3);
return $info; return $info;
} }
@ -115,8 +120,8 @@ class PatientLogic
$response = $this->his_client->getPatientInfo($data['card_no'], $card_type, $data['name']); $response = $this->his_client->getPatientInfo($data['card_no'], $card_type, $data['name']);
$this->info('查询患者信息:', $response); $this->info('查询患者信息:', $response);
if (isset($response['RESULTCODE']) && $response['RESULTCODE'] === '0') { if (!isset($response['success']) || !$response['success']) {
$patient_id = &$response['PATIENTID']; $patient_id = &$response['response']['patientNumber'];
// 查询是否已绑定 // 查询是否已绑定
$result = $this->patient_model->getPatientInfoByPatientId($patient_id); $result = $this->patient_model->getPatientInfoByPatientId($patient_id);
@ -141,13 +146,19 @@ class PatientLogic
); );
$this->info('建档患者:'. $data['name']. '建档结果', $response); $this->info('建档患者:'. $data['name']. '建档结果', $response);
if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { if (!isset($response['success']) || !$response['success']) {
throw new GeneralException('建档失败,失败原因:'. $response['ERRORMSG'] ?? '未知错误', Response::HTTP_SERVICE_UNAVAILABLE); throw new GeneralException('建档失败,失败原因:'. $response['msg'] ?? '未知错误', Response::HTTP_SERVICE_UNAVAILABLE);
} }
}
$patient_id = &$response['PATIENTID']; // 再查一遍接口 获取患者信息
$response = $this->his_client->getPatientInfo($data['card_no'], $card_type, $data['name']);
if (!isset($response['success']) || !$response['success']) {
throw new GeneralException('建档失败,失败原因:'. $response['msg'] ?? '未知错误', Response::HTTP_SERVICE_UNAVAILABLE);
} }
$patient_id = $response['response']['patientNumber'];
// 写入数据库 // 写入数据库
$result = $this->patient_model->createPatient($this->union_id, $this->open_id, $patient_id, $data['name'], $sex); $result = $this->patient_model->createPatient($this->union_id, $this->open_id, $patient_id, $data['name'], $sex);
@ -155,6 +166,7 @@ class PatientLogic
throw new GeneralException('数据保存失败,请重试!', Response::HTTP_INTERNAL_SERVER_ERROR); throw new GeneralException('数据保存失败,请重试!', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
$this->sendBindPatientSubscribeMessage($result->id, $data['name']);
return $patient_id; return $patient_id;
} }
@ -180,22 +192,22 @@ class PatientLogic
$response = $this->his_client->getPatientInfo($data['card_no'], $card_type, $data['name']); $response = $this->his_client->getPatientInfo($data['card_no'], $card_type, $data['name']);
$this->info('查询患者信息:', $response); $this->info('查询患者信息:', $response);
if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] != '0') { if (!isset($response['success']) || !$response['success']) {
throw new GeneralException($response['ResultContent'] ?? '未知错误'); throw new GeneralException($response['msg'] ?? '未知错误');
} }
$patient_info = &$response; $patient_info = &$response['response'];
$sex = Sex::from((int) $patient_info['SEX']); $sex = Sex::from((int) $patient_info['SEX']);
if ($patient_info['PATIENTID'] != $data['patient_id']) { if ($patient_info['patientNumber'] != $data['patient_id']) {
throw new GeneralException('该证件号已建档,但就诊卡号不匹配!'); throw new GeneralException('该证件号已建档,但就诊卡号不匹配!');
} }
if ($patient_info['NAME'] != $data['name']) { if ($patient_info['name'] != $data['name']) {
throw new GeneralException('该证件号已建档,但姓名不匹配!'); throw new GeneralException('该证件号已建档,但姓名不匹配!');
} }
if ($patient_info['CARDNO'] != $data['card_no']) { if ($patient_info['cardNo'] != $data['card_no']) {
throw new GeneralException('该就诊号已建档,但证件号码不匹配!'); throw new GeneralException('该就诊号已建档,但证件号码不匹配!');
} }
@ -210,13 +222,14 @@ class PatientLogic
} }
// 写入数据库 // 写入数据库
$result = $this->patient_model->createPatient($this->union_id, $this->open_id, $patient_info['PATIENTID'], $data['name'], $sex); $result = $this->patient_model->createPatient($this->union_id, $this->open_id, $patient_info['patientNumber'], $data['name'], $sex);
if (!$result) { if (!$result) {
throw new GeneralException('数据保存失败,请重试!', Response::HTTP_INTERNAL_SERVER_ERROR); throw new GeneralException('数据保存失败,请重试!', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
return $patient_info['PATIENTID']; $this->sendBindPatientSubscribeMessage($this->open_id, $result->id, $data['name']);
return $patient_info['patientNumber'];
} }
/** /**
@ -258,6 +271,7 @@ class PatientLogic
throw new GeneralException('解绑失败,请稍后再试!', Response::HTTP_INTERNAL_SERVER_ERROR); throw new GeneralException('解绑失败,请稍后再试!', Response::HTTP_INTERNAL_SERVER_ERROR);
} }
$this->sendUnbindPatientSubscribeMessage($this->open_id, $info->id, $info['name'], '', $info['patient_id']);
return true; return true;
} }
} }

@ -72,7 +72,7 @@ class RegisterLogic
$order = $this->createOrder($order_id, $pay_type, $reg_fee, $order_type, $patient_info, $schedule_info); $order = $this->createOrder($order_id, $pay_type, $reg_fee, $order_type, $patient_info, $schedule_info);
// 申请支付 // 申请支付
$pay_data = $this->applyPayment($order_type, $order_id, $reg_fee, $patient_info['PATIENTID'], $patient_info['NAME']); $pay_data = $this->applyPayment($order_type, $order_id, $reg_fee, $patient_info['patientNumber'], $patient_info['name']);
// 去除无用数据 // 去除无用数据
unset($pay_data['merchantId'], $pay_data['merchantName'], $pay_data['channelId'], $pay_data['channelName']); unset($pay_data['merchantId'], $pay_data['merchantName'], $pay_data['channelId'], $pay_data['channelName']);
@ -94,12 +94,13 @@ class RegisterLogic
throw new GeneralException('找不到患者信息,请重新再试!', Response::HTTP_BAD_REQUEST); throw new GeneralException('找不到患者信息,请重新再试!', Response::HTTP_BAD_REQUEST);
} }
$patient_info = $this->his_client->getPatientInfo($info['patient_id'], CardType::OUTPATIENT_NO, $info['name']); $response = $this->his_client->getPatientInfo($info['patient_id'], CardType::OUTPATIENT_NO, $info['name']);
if (!isset($patient_info['RESULTCODE']) || $patient_info['RESULTCODE'] !== '0') { if (!isset($response['success']) || !$response['success']) {
throw new GeneralException($patient_info['ERRORMSG'] ?? '找不到患者信息,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE); throw new GeneralException($response['msg'] ?? '找不到患者信息,请重新再试!', Response::HTTP_SERVICE_UNAVAILABLE);
} }
// 添加Patient 表ID // 添加Patient 表ID
$patient_info = &$response['response'];
$patient_info['id'] = $info['id']; $patient_info['id'] = $info['id'];
$this->info('挂号患者信息', $patient_info); $this->info('挂号患者信息', $patient_info);
@ -194,6 +195,7 @@ class RegisterLogic
]; ];
$order = $this->order_model->createOrder( $order = $this->order_model->createOrder(
$patient_info['id'],
$order_id, $order_id,
$pay_type, $pay_type,
$reg_fee * 100, $reg_fee * 100,

@ -32,8 +32,8 @@ class ScheduleLogic
{ {
$response = $this->his_client->getDepType('', '','01', $date); $response = $this->his_client->getDepType('', '','01', $date);
if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { if (!isset($response['success']) || !$response['success']) {
throw new GeneralException($response['ERRORMSG'] ?? '暂无科室排班!', Response::HTTP_SERVICE_UNAVAILABLE); throw new GeneralException($response['msg'] ?? '暂无科室排班!', Response::HTTP_SERVICE_UNAVAILABLE);
} }
return $response; return $response;
@ -51,8 +51,8 @@ class ScheduleLogic
$type = $date === date('Y-m-d') ? '3' : '1'; $type = $date === date('Y-m-d') ? '3' : '1';
$response = $this->his_client->getDoctorLists($dept_id, $type, '', $date); $response = $this->his_client->getDoctorLists($dept_id, $type, '', $date);
if (!isset($response['RESULTCODE']) || $response['RESULTCODE'] !== '0') { if (!isset($response['success']) || !$response['success']) {
throw new GeneralException($response['ERRORMSG'] ?? '该科室暂无医生排班!', Response::HTTP_SERVICE_UNAVAILABLE); throw new GeneralException($response['msg'] ?? '该科室暂无医生排班!', Response::HTTP_SERVICE_UNAVAILABLE);
} }
return $response; return $response;

@ -16,10 +16,10 @@ class PatientDetailsResource extends JsonResource
{ {
return [ return [
'patient_id' => $this->resource['patient_id'], 'patient_id' => $this->resource['patient_id'],
'patient_card_id' => $this->resource['patient_card_id'],
'patient_name' => $this->resource['name'], 'patient_name' => $this->resource['name'],
'card_no' => $this->resource['card_no'], // 'card_no' => $this->resource['card_no'],
'sex' => $this->resource['sex'], 'sex' => $this->resource['sex'],
'birthday' => $this->resource['birthday'],
'is_default' => $this->resource['def_status'] 'is_default' => $this->resource['def_status']
]; ];
} }

@ -19,9 +19,9 @@ class DeptListsResource extends JsonResource
$lists = []; $lists = [];
foreach ($this->resource['ITEM'] as $v) { foreach ($this->resource['ITEM'] as $v) {
$lists[] = [ $lists[] = [
'dept_id' => $v['DEPID'], 'dept_id' => $v['typeId'],
'dept_name' => $v['DEPNAME'], 'dept_name' => $v['typeName'],
'dept_intro' => $v['INTRODUCE'] // 'dept_intro' => $v['INTRODUCE']
]; ];
} }

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\Jobs\Message;
use App\Dictionary\SendMessage\Type as MessageType;
use EasyWeChat\Kernel\HttpClient\Response;
use EasyWeChat\OfficialAccount\Application as OfficialApplication;
use EasyWeChat\MiniApp\Application as MiniApplication;
use EasyWeChat\Work\Application as WorkApplication;
use Illuminate\Support\Facades\Log;
use ReflectionException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
abstract class Message
{
/**
* @return OfficialApplication|MiniApplication|WorkApplication
*/
abstract public function getApp(): OfficialApplication|MiniApplication|WorkApplication;
/**
* Send Message.
*
* @param MessageType $type 消息类型
* @param array $message 消息数据 json 包
*
* @return Response
*
* @throws TransportExceptionInterface
* @throws ClientExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ServerExceptionInterface
*/
public function send(MessageType $type, array $message): Response
{
/** @var Response $response */
$response = $this->getApp()->getClient()->postJson($type->api()->value, $message);
Log::channel('SendWeChatMessage')->info('Push WeChat Message', [$type->label(), $message, $response->getContent(false)]);
return $response;
}
}

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace App\Jobs\Message;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\MiniApp\Application as MiniApplication;
class SubscriptionMessage extends Message
{
/**
* @return MiniApplication
* @throws InvalidArgumentException
*/
public function getApp(): MiniApplication
{
/** @var MiniApplication */
return getWeChatMiniProgramApp();
}
}

@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace App\Jobs;
use App\Dictionary\SendMessage\Type as MessageType;
use App\Dictionary\SendMessage\Status as MessageStatus;
use App\Jobs\Message\SubscriptionMessage;
use App\Models\SendMessageJob;
use Exception;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Throwable;
class SendWeChatMessageJob implements ShouldQueue, ShouldBeUnique
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;
/**
* PushWechatMessage Model.
*
* @var SendMessageJob
*/
protected SendMessageJob $message;
/**
* Message Type.
*
* @var MessageType
*/
protected MessageType $message_type;
/**
* 任务可尝试次数.
*
* @var int
*/
public int $tries = 3;
/**
* 任务的唯一 ID。
*
* @return string
*/
public function uniqueId(): string
{
return 'Queue:SendWechatMessage:'. $this->message->id;
}
/**
* 获取唯一任务锁的缓存驱动程序。
*
* @return Repository
*/
public function uniqueVia(): Repository
{
return Cache::driver('redis');
}
/**
* Send WeChat Message Job Construct.
*
* @param SendMessageJob $message
*/
public function __construct(SendMessageJob $message)
{
$this->message = $message->withoutRelations();
$this->message_type = MessageType::from($this->message->type);
}
/**
* Execute the job.
*/
public function handle(): void
{
$content = json_decode($this->message->content, true);
try {
$response = match ($this->message_type) {
MessageType::TEMPLATE,
MessageType::SINGLE_SUBSCRIBE,
MessageType::SUBSCRIBE,
MessageType::CUSTOM => (new SubscriptionMessage())->send($this->message_type, $content),
};
$data = $response->toArray(false);
if ($response->isSuccessful()) {
$this->successful((string)$data['errcode']);
} else {
$this->retry((string)$data['errmsg']);
}
} catch (Exception|Throwable $e) {
$message = $e->getMessage().' in '.$e->getFile().':'.$e->getLine();
Log::channel('SendWeChatMessage')->info('Push WeChat Message Error', [$this->message->id, $message]);
$this->retry($e->getMessage());
}
}
/**
* Handle a job successful.
*
* @param string $msg_id
*
* @return void
*/
public function successful(string $msg_id): void
{
$this->message->status = MessageStatus::SUCCESS->value;
$this->message->msg_id = $msg_id;
$this->message->sent_at = date('Y-m-d H:i:s');
$this->message->save();
}
/**
* Handle a job retry.
*
* @param string $fail_reason
* @return void
*/
public function retry(string $fail_reason): void
{
if ($this->message->number < 3) {
++$this->message->number;
$this->message->save();
$this->release(10);
} else {
$this->failed($fail_reason);
}
}
/**
* Handle a job failure.
*
* @param string $fail_reason
*
* @return void
*/
public function failed(string $fail_reason): void
{
$this->message->status = MessageStatus::FAILURE->value;
$this->message->fail_reason = $fail_reason;
$this->message->save();
}
}

@ -79,7 +79,7 @@ class Order extends Model
*/ */
public function patient(): belongsTo public function patient(): belongsTo
{ {
return $this->belongsTo(Patient::class, 'patient_id', 'patient_id'); return $this->belongsTo(Patient::class, 'id', 'relate_patient_id');
} }
/** /**
@ -162,6 +162,7 @@ class Order extends Model
/** /**
* 创建订单 * 创建订单
* @param int $relate_patient_id
* @param string $order_id * @param string $order_id
* @param PayType $pay_type * @param PayType $pay_type
* @param float $fee * @param float $fee
@ -174,11 +175,12 @@ class Order extends Model
* @param array $record_info * @param array $record_info
* @return mixed * @return mixed
*/ */
public function createOrder(string $order_id, PayType $pay_type, float $fee, float $reduce_fee, string $open_id, string $patient_id, string $patient_name, Type $order_type, SourceId $source_id, array $record_info = []): mixed public function createOrder(int $relate_patient_id, string $order_id, PayType $pay_type, float $fee, float $reduce_fee, string $open_id, string $patient_id, string $patient_name, Type $order_type, SourceId $source_id, array $record_info = []): mixed
{ {
$order_info = [ $order_info = [
'relate_id' => 0, 'relate_id' => 0,
'relate_patient_id' => $relate_patient_id,
'order_id' => $order_id, 'order_id' => $order_id,
'his_order_id' => '', 'his_order_id' => '',
'transaction_id' => '', 'transaction_id' => '',
@ -228,6 +230,7 @@ class Order extends Model
/** /**
* 创建退费/冲正订单 * 创建退费/冲正订单
* @param int $relate_order_id * @param int $relate_order_id
* @param int $relate_patient_id
* @param string $order_id * @param string $order_id
* @param PayType $pay_type * @param PayType $pay_type
* @param float $fee * @param float $fee
@ -238,10 +241,11 @@ class Order extends Model
* @param SourceId $source_id * @param SourceId $source_id
* @return mixed * @return mixed
*/ */
public function createRefundOReverseOrder(int $relate_order_id, string $order_id, PayType $pay_type, float $fee, string $open_id, string $patient_id, string $patient_name, Type $order_type, SourceId $source_id): mixed public function createRefundOReverseOrder(int $relate_order_id, int $relate_patient_id, string $order_id, PayType $pay_type, float $fee, string $open_id, string $patient_id, string $patient_name, Type $order_type, SourceId $source_id): mixed
{ {
$order_info = [ $order_info = [
'relate_id' => $relate_order_id, 'relate_id' => $relate_order_id,
'relate_patient_id' => $relate_patient_id,
'order_id' => $order_id, 'order_id' => $order_id,
'his_order_id' => '', 'his_order_id' => '',
'transaction_id' => '', 'transaction_id' => '',

@ -32,6 +32,7 @@ class RegistrationRecord extends Model
'lock_status', 'lock_status',
'lock_at', 'lock_at',
'extra_info', 'extra_info',
'reminder_sent'
]; ];
/** /**
@ -45,6 +46,7 @@ class RegistrationRecord extends Model
'relate_patient_id' => 'integer', 'relate_patient_id' => 'integer',
'lock_status' => 'integer', 'lock_status' => 'integer',
'lock_at' => 'datetime', 'lock_at' => 'datetime',
'reminder_sent' => 'integer'
]; ];
/** /**

@ -4,19 +4,13 @@ declare(strict_types=1);
namespace App\Models; namespace App\Models;
use App\Dictionary\PushMessage\Type; use App\Dictionary\SendMessage\Type;
use App\Dictionary\WeChat\Official\SubscribeId; use App\Dictionary\WeChat\MiniProgram\SubscribeId;
use App\Dictionary\WeChat\Official\TemplateId;
use App\Jobs\SendWeChatMessage;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Modules\Official\Structs\Message\CustomMessage;
use Modules\Official\Structs\Message\SubscribeMessage;
use Modules\Official\Structs\Message\TemplateData\Common;
use Modules\Official\Structs\Message\TemplateMessage;
class PushWechatMessage extends Model class SendMessageJob extends Model
{ {
use HasFactory; use HasFactory;
@ -34,6 +28,7 @@ class PushWechatMessage extends Model
'content', 'content',
'number', 'number',
'status', 'status',
'fail_reason',
'sent_at', 'sent_at',
]; ];
@ -71,19 +66,21 @@ class PushWechatMessage extends Model
* 插入推送消息队列 * 插入推送消息队列
* @param int $relate_order_id * @param int $relate_order_id
* @param int $relate_patient_id * @param int $relate_patient_id
* @param TemplateId|SubscribeId|null $template_id * @param Type $type
* @param TemplateMessage|SubscribeMessage|CustomMessage $message * @param SubscribeId|null $template_id
* @param array $message
* @param string $scene
* @return mixed * @return mixed
*/ */
public function insertMessageJobs(int $relate_order_id, int $relate_patient_id, TemplateId|SubscribeId|null $template_id, TemplateMessage|SubscribeMessage|CustomMessage $message): mixed public function insertMessageJobs(int $relate_order_id, int $relate_patient_id, Type $type, SubscribeId|null $template_id, array $message, string $scene = ''): mixed
{ {
$data = [ $data = [
'relate_order_id' => $relate_order_id, 'relate_order_id' => $relate_order_id,
'relate_patient_id' => $relate_patient_id, 'relate_patient_id' => $relate_patient_id,
'type' => Type::OFFICIAL_TEMPLATE->value, 'type' => $type->value,
'template_id' => $template_id, 'template_id' => $template_id,
'scene' => '', 'scene' => '',
'content' => serialize($message), 'content' => json_encode($message, JSON_UNESCAPED_UNICODE),
]; ];
return $this->create($data); return $this->create($data);

@ -35,7 +35,7 @@ class Client
*/ */
protected function commonRequestData(): array protected function commonRequestData(): array
{ {
return [ /*return [
'mock' => 0, 'mock' => 0,
'appKey' => '', 'appKey' => '',
'atmName' => '', 'atmName' => '',
@ -44,7 +44,8 @@ class Client
'f1' => '', 'f1' => '',
'f2' => '', 'f2' => '',
'f3' => '' 'f3' => ''
]; ];*/
return [];
} }
/** /**

@ -0,0 +1,260 @@
<?php
declare(strict_types=1);
namespace App\Utils\Traits;
use App\Dictionary\SendMessage\Type;
use App\Dictionary\WeChat\MiniProgram\SubscribeId;
use App\Jobs\SendWeChatMessageJob;
use App\Models\Order as OrderModel;
use App\Models\RegistrationRecord;
use App\Models\SendMessageJob;
trait SendSubscribeMessage
{
protected static SendMessageJob $message_model;
/**
* 单例获取 SendMessageJob
* @return SendMessageJob
*/
public static function getSendMessageJob(): SendMessageJob
{
if (!self::$message_model) {
self::$message_model = new SendMessageJob();
}
return self::$message_model;
}
/**
* 发送绑定患者订阅消息
* @param string $open_id
* @param int $relate_patient_id
* @param string $patient_name
* @return void
*/
public function sendBindPatientSubscribeMessage(string $open_id, int $relate_patient_id, string $patient_name): void
{
$subscribe_id = SubscribeId::PATIENT_BIND_SUCCESS;
$data = [
'touser' => $open_id,
'template_id' => $subscribe_id->value,
'page' => 'pagesA/card/index',
'data' => [
'thing1' => ['value' => $patient_name],
'time2' => ['value' => date('Y-m-d H:i')],
'thing3' => ['value' => '您已绑定成功,可以进行线上线下就医服务。'],
],
'miniprogram_state' => env('custom.mini_program_message_state')
];
$message = self::getSendMessageJob()->insertMessageJobs(0, $relate_patient_id, Type::SINGLE_SUBSCRIBE, $subscribe_id, $data, '');
SendWeChatMessageJob::dispatch($message);
}
/**
* 发送绑定患者订阅消息
* @param string $open_id
* @param int $relate_patient_id
* @param string $patient_name
* @param string $inpatient_id 住院号
* @param string $patient_id 门诊号
* @return void
*/
public function sendUnbindPatientSubscribeMessage(string $open_id, int $relate_patient_id, string $patient_name, string $inpatient_id, string $patient_id): void
{
$subscribe_id = SubscribeId::PATIENT_UNBIND_SUCCESS;
$data = [
'touser' => $open_id,
'template_id' => $subscribe_id->value,
'page' => '',
'data' => [
'thing1' => ['value' => $patient_name],
'time2' => ['value' => date('Y-m-d H:i')],
'thing3' => ['value' => '您已解除绑定关系,无法使用线上线下就医服务。'],
'character_string5' => ['value' => $inpatient_id],
'character_string6' => ['value' => $patient_id],
],
'miniprogram_state' => env('custom.mini_program_message_state')
];
$message = self::getSendMessageJob()->insertMessageJobs(0, $relate_patient_id, Type::SINGLE_SUBSCRIBE, $subscribe_id, $data);
SendWeChatMessageJob::dispatch($message);
}
/**
* 发送就诊提醒消息
* @param RegistrationRecord $record
* @return void
*/
public function sendAppointmentReminderMessage(RegistrationRecord $record): void
{
$order = &$record->order;
$subscribe_id = SubscribeId::VISIT_REMIND;
$visit_time = $record->visit_date . ' '. $record->begin_time . '~'. $record->end_time;
$data = [
'touser' => $order->open_id,
'template_id' => $subscribe_id->value,
'page' => 'pagesA/register/regRecord',
'data' => [
'name1' => ['value' => $order->patient_name],
'time7' => ['value' => $visit_time],
'thing10' => ['value' => $record->dept_name. '('. $record->doctor_name. ')'],
'thing6' => ['value' => $record->dept_location],
'thing5' => ['value' => '请准时前往医院就诊。'],
],
'miniprogram_state' => env('custom.mini_program_message_state')
];
$message = self::getSendMessageJob()->insertMessageJobs(0, $order->relate_patient_id, Type::SINGLE_SUBSCRIBE, $subscribe_id, $data, '');
SendWeChatMessageJob::dispatch($message);
}
/**
* 发送挂号成功订阅消息
* @param OrderModel $order
* @return void
*/
public function sendRegistrationSuccessMessage(OrderModel $order): void
{
$record = &$order->registrationRecord;
$subscribe_id = SubscribeId::REGISTRATION_SUCCESS;
$visit_time = $record->visit_date . ' '. $record->begin_time . '~'. $record->end_time;
$data = [
'touser' => $order->open_id,
'template_id' => $subscribe_id->value,
'page' => 'pagesA/register/regRecord',
'data' => [
'thing5' => ['value' => $record->dept_name],
'thing17' => ['value' => $record->dept_location],
'character_string15' => ['value' => $visit_time],
'thing19' => ['value' => $record->doctor_name],
'amount13' => ['value' => $order->fee / 100],
],
'miniprogram_state' => env('custom.mini_program_message_state')
];
$message = self::getSendMessageJob()->insertMessageJobs(0, $order->relate_patient_id, Type::SINGLE_SUBSCRIBE, $subscribe_id, $data, '');
SendWeChatMessageJob::dispatch($message);
}
/**
* 发送挂号失败订阅消息
* @param OrderModel $order
* @return void
*/
public function sendRegistrationFailureMessage(OrderModel $order): void
{
$record = &$order->registrationRecord;
$subscribe_id = SubscribeId::REGISTRATION_FAILURE;
$visit_time = $record->visit_date . ' '. $record->begin_time . '~'. $record->end_time;
$data = [
'touser' => $order->open_id,
'template_id' => $subscribe_id->value,
'page' => 'pagesA/register/regRecord',
'data' => [
'thing2' => ['value' => $record->dept_name],
'thing3' => ['value' => $record->doctor_name],
'name1' => ['value' => $order->patient_name],
'time4' => ['value' => $visit_time],
'amount13' => ['value' => $order->fee / 100],
],
'miniprogram_state' => env('custom.mini_program_message_state')
];
$message = self::getSendMessageJob()->insertMessageJobs(0, $order->relate_patient_id, Type::SINGLE_SUBSCRIBE, $subscribe_id, $data, '');
SendWeChatMessageJob::dispatch($message);
}
/**
* 发送取消预约挂号消息
* @param OrderModel $order
* @return void
*/
public function sendRegistrationCancelMessage(OrderModel $order): void
{
$record = &$order->registrationRecord;
$subscribe_id = SubscribeId::REGISTRATION_CANCEL;
$visit_time = $record->visit_date . ' '. $record->begin_time . '~'. $record->end_time;
$data = [
'touser' => $order->open_id,
'template_id' => $subscribe_id->value,
'page' => 'pagesA/register/regRecord',
'data' => [
'thing1' => ['value' => $order->patient_name],
'thing2' => ['value' => $record->dept_name],
'thing3' => ['value' => $record->doctor_name],
'time4' => ['value' => $visit_time],
'thing5' => ['value' => '已成功取消预约,如有需要请重新预约。'],
],
'miniprogram_state' => env('custom.mini_program_message_state')
];
$message = self::getSendMessageJob()->insertMessageJobs(0, $order->relate_patient_id, Type::SINGLE_SUBSCRIBE, $subscribe_id, $data, '');
SendWeChatMessageJob::dispatch($message);
}
/**
* 发送门诊缴费成功消息
* @param OrderModel $order
* @return void
*/
public function sendOutpatientPaymentSuccessMessage(OrderModel $order): void
{
$record = &$order->outpatientPaymentRecord;
$subscribe_id = SubscribeId::OUTPATIENT_PAYMENT_SUCCESS;
$data = [
'touser' => $order->open_id,
'template_id' => $subscribe_id->value,
'page' => 'pagesA/outpatient/outPayList',
'data' => [
'date5' => ['value' => date('Y-m-d')],
'amount6' => ['value' => $order->fee / 100],
'character_string7' => ['value' => $order->order_id],
'character_string14' => ['value' => $order->patient_id],
'thing9' => ['value' => '门诊缴费'],
],
'miniprogram_state' => env('custom.mini_program_message_state')
];
$message = self::getSendMessageJob()->insertMessageJobs(0, $order->relate_patient_id, Type::SINGLE_SUBSCRIBE, $subscribe_id, $data, '');
SendWeChatMessageJob::dispatch($message);
}
/**
* 发送门诊缴费失败消息
* @param OrderModel $order
* @return void
*/
public function sendOutpatientPaymentFailureMessage(OrderModel $order): void
{
$record = &$order->outpatientPaymentRecord;
$subscribe_id = SubscribeId::OUTPATIENT_PAYMENT_FAILURE;
$data = [
'touser' => $order->open_id,
'template_id' => $subscribe_id->value,
'page' => '',
'data' => [
'character_string1' => ['value' => $order->order_id],
'thing2' => ['value' => '门诊缴费'],
'name3' => ['value' => $order->patient_name],
'amount4' => ['value' => $order->fee / 100],
'date6' => ['value' => date('Y-m-d')],
],
'miniprogram_state' => env('custom.mini_program_message_state')
];
$message = self::getSendMessageJob()->insertMessageJobs(0, $order->relate_patient_id, Type::SINGLE_SUBSCRIBE, $subscribe_id, $data, '');
SendWeChatMessageJob::dispatch($message);
}
}

@ -72,9 +72,8 @@ class ClientMockHttpTransfer extends HttpTransferAbstract
public function responseFormat(mixed $data): mixed public function responseFormat(mixed $data): mixed
{ {
try { try {
// 此处为xml格式 // 此处为json格式
$obj = simplexml_load_string((string)$data, 'SimpleXMLElement', LIBXML_NOCDATA); return json_decode((string)$data, true);
return json_decode((string)json_encode($obj), true);
} catch (Exception $e) { } catch (Exception $e) {
throw new Exception($e->getMessage()); throw new Exception($e->getMessage());
} }
@ -98,7 +97,7 @@ class ClientMockHttpTransfer extends HttpTransferAbstract
*/ */
private function mockRegisterCard(array $params): self private function mockRegisterCard(array $params): self
{ {
$this->transfer_response = '<RESPONSE><RESULTCODE>0</RESULTCODE><ERRORMSG>建卡成功</ERRORMSG><PATIENTID></PATIENTID></RESPONSE>'; $this->transfer_response = '{"status":200,"success":true,"msg":"成功","msgDev":null,"response":{"patientId":"2235711"}}';
return $this; return $this;
} }
@ -110,7 +109,7 @@ class ClientMockHttpTransfer extends HttpTransferAbstract
*/ */
private function mockGetPatientInfo(array $params): self private function mockGetPatientInfo(array $params): self
{ {
$this->transfer_response = '<RESPONSE><RESULTCODE>0</RESULTCODE><ERRORMSG></ERRORMSG><PATIENTID>1104468</PATIENTID><CARDNO>452323193712153735</CARDNO><NAME>唐超积</NAME><SEX>1</SEX><BIRTHDAY>1937-12-15</BIRTHDAY><CARDSTATUS>0</CARDSTATUS><BRXZ>123</BRXZ><MZHM>299811204468</MZHM></RESPONSE>'; $this->transfer_response = '{"status":200,"success":true,"msg":"成功","msgDev":null,"response":{"patientId":"2235574","cardNo":"230403199903245493","name":"谭玉山","sex":"0","birthday":"1999-03-24","cardStatus":"0","naturePatients":"123","patientNumber":"288712335574"}}';
return $this; return $this;
} }
@ -122,7 +121,7 @@ class ClientMockHttpTransfer extends HttpTransferAbstract
*/ */
private function mockGetDepLists(array $params): self private function mockGetDepLists(array $params): self
{ {
$this->transfer_response = '<RESPONSE><RESULTCODE>0</RESULTCODE><ERRORMSG>Success</ERRORMSG><ITEM><DEPID>12345</DEPID><YBDEPID>67890</YBDEPID><DEPNAME>内科</DEPNAME><FEE>50.00</FEE><FEECODE>001</FEECODE><YSCOUNT>5</YSCOUNT><JZCOUNT>3</JZCOUNT><REGISTERAREA>1</REGISTERAREA><INTRODUCE>内科专注于诊治呼吸、消化、心血管等系统疾病。</INTRODUCE><KSGHXE>5</KSGHXE><KSYGRS>20</KSYGRS><KSKGRS>10</KSKGRS><YSGHXE>10</YSGHXE><YSYGRS>15</YSYGRS><YSKGRS>25</YSKGRS><KYYYSCOUNT>2</KYYYSCOUNT></ITEM><ITEM><DEPID>23456</DEPID><YBDEPID>78901</YBDEPID><DEPNAME>外科</DEPNAME><FEE>60.00</FEE><FEECODE>002</FEECODE><YSCOUNT>8</YSCOUNT><JZCOUNT>4</JZCOUNT><REGISTERAREA>2</REGISTERAREA><INTRODUCE>外科提供专业的手术治疗服务,包括创伤、整形及器官移植。</INTRODUCE><KSGHXE>6</KSGHXE><KSYGRS>30</KSYGRS><KSKGRS>15</KSKGRS><YSGHXE>12</YSGHXE><YSYGRS>20</YSYGRS><YSKGRS>30</YSKGRS><KYYYSCOUNT>3</KYYYSCOUNT></ITEM><ITEM><DEPID>34567</DEPID><YBDEPID>89012</YBDEPID><DEPNAME>儿科</DEPNAME><FEE>40.00</FEE><FEECODE>003</FEECODE><YSCOUNT>6</YSCOUNT><JZCOUNT>2</JZCOUNT><REGISTERAREA>1</REGISTERAREA><INTRODUCE>儿科为0-18岁儿童提供专业的诊疗与健康管理服务。</INTRODUCE><KSGHXE>4</KSGHXE><KSYGRS>25</KSYGRS><KSKGRS>8</KSKGRS><YSGHXE>8</YSGHXE><YSYGRS>10</YSYGRS><YSKGRS>18</YSKGRS><KYYYSCOUNT>4</KYYYSCOUNT></ITEM><ITEM><DEPID>45678</DEPID><YBDEPID>90123</YBDEPID><DEPNAME>皮肤科</DEPNAME><FEE>30.00</FEE><FEECODE>004</FEECODE><YSCOUNT>4</YSCOUNT><JZCOUNT>1</JZCOUNT><REGISTERAREA>1</REGISTERAREA><INTRODUCE>皮肤科诊治常见皮肤病、性病及皮肤美容问题。</INTRODUCE><KSGHXE>3</KSGHXE><KSYGRS>15</KSYGRS><KSKGRS>7</KSKGRS><YSGHXE>6</YSGHXE><YSYGRS>5</YSYGRS><YSKGRS>11</YSKGRS><KYYYSCOUNT>1</KYYYSCOUNT></ITEM></RESPONSE>'; $this->transfer_response = '{"status":200,"success":true,"msg":"成功","msgDev":null,"response":[{"typeId":"17","typeName":"泌尿外科"},{"typeId":"20","typeName":"体检办证"},{"typeId":"31","typeName":"内分泌科"},{"typeId":"01","typeName":"内科"},{"typeId":"05","typeName":"口腔科"},{"typeId":"19","typeName":"消化内科"},{"typeId":"29","typeName":"呼吸内科门诊"},{"typeId":"33","typeName":"中医减重门诊"},{"typeId":"03","typeName":"中医康复"},{"typeId":"18","typeName":"产前门诊"},{"typeId":"04","typeName":"五官科"},{"typeId":"10","typeName":"妇产科"},{"typeId":"16","typeName":"全科医生门诊"},{"typeId":"28","typeName":"儿童青少年心理门诊"},{"typeId":"02","typeName":"普外科"},{"typeId":"25","typeName":"儿科"},{"typeId":"27","typeName":"精神心理科"},{"typeId":"07","typeName":"急诊内科"},{"typeId":"09","typeName":"皮肤科"},{"typeId":"14","typeName":"急诊外科"},{"typeId":"15","typeName":"骨科"},{"typeId":"26","typeName":"治未病科"},{"typeId":"06","typeName":"专家门诊"},{"typeId":"11","typeName":"妇保门诊"},{"typeId":"12","typeName":"儿保门诊"},{"typeId":"32","typeName":"血液透析门诊"},{"typeId":"08","typeName":"天灸门诊"},{"typeId":"24","typeName":"神经内科"},{"typeId":"30","typeName":"助产士门诊"}]}';
return $this; return $this;
} }
@ -239,7 +238,7 @@ class ClientMockHttpTransfer extends HttpTransferAbstract
*/ */
private function mockGetDictionaryLists(array $params): self private function mockGetDictionaryLists(array $params): self
{ {
$this->transfer_response = '<RESPONSE><RESULTCODE>0</RESULTCODE><ERRORMSG></ERRORMSG><ITEM><TYPEID>1</TYPEID><TYPENAME>手术费</TYPENAME></ITEM><ITEM><TYPEID>2</TYPEID><TYPENAME>治疗费</TYPENAME></ITEM><ITEM><TYPEID>3</TYPEID><TYPENAME>中药费</TYPENAME></ITEM><ITEM><TYPEID>4</TYPEID><TYPENAME>西药费</TYPENAME></ITEM><ITEM><TYPEID>5</TYPEID><TYPENAME>检查费</TYPENAME></ITEM><ITEM><TYPEID>6</TYPEID><TYPENAME>诊查费</TYPENAME></ITEM><ITEM><TYPEID>7</TYPEID><TYPENAME>护理费</TYPENAME></ITEM></RESPONSE>'; $this->transfer_response = '{"status":200,"success":true,"msg":"成功","msgDev":null,"response":[{"typeId":"4","typeName":"草药费"},{"typeId":"20","typeName":"防疫药品"},{"typeId":"2","typeName":"西药费"},{"typeId":"3","typeName":"中成药"},{"typeId":"22","typeName":"B超"},{"typeId":"25","typeName":"CT检查"},{"typeId":"19","typeName":"X光费"},{"typeId":"1","typeName":"床位费"},{"typeId":"27","typeName":"高值耗材"},{"typeId":"12","typeName":"挂号费"},{"typeId":"11","typeName":"护理费"},{"typeId":"17","typeName":"急诊留观床位费"},{"typeId":"5","typeName":"检查费"},{"typeId":"9","typeName":"检验费"},{"typeId":"15","typeName":"救护车"},{"typeId":"26","typeName":"内镜检查"},{"typeId":"10","typeName":"其它"},{"typeId":"8","typeName":"手术费"},{"typeId":"13","typeName":"输血费"},{"typeId":"14","typeName":"输氧费"},{"typeId":"23","typeName":"碎石"},{"typeId":"21","typeName":"心电图"},{"typeId":"24","typeName":"一般诊疗费"},{"typeId":"16","typeName":"医材费"},{"typeId":"7","typeName":"诊查费"},{"typeId":"6","typeName":"治疗费"},{"typeId":"18","typeName":"自负床位费"}]}';
return $this; return $this;
} }

@ -1,5 +1,6 @@
<?php <?php
use App\Console\Commands\SendAppointmentReminders;
use App\Exceptions\GeneralException; use App\Exceptions\GeneralException;
use App\Http\Middleware\RecordApiLog; use App\Http\Middleware\RecordApiLog;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
@ -88,4 +89,7 @@ return Application::configure(basePath: dirname(__DIR__))
recordLog('AppError', $record_msg); recordLog('AppError', $record_msg);
return jsonResponse(Response::HTTP_INTERNAL_SERVER_ERROR, $record_msg); return jsonResponse(Response::HTTP_INTERNAL_SERVER_ERROR, $record_msg);
}); });
})->create(); })->withCommands([
SendAppointmentReminders::class,
])
->create();

@ -6,5 +6,7 @@
return [ return [
// 绑定就诊卡数上限 // 绑定就诊卡数上限
'max_bind_patient_count' => 5 'max_bind_patient_count' => 5,
// 跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
'mini_program_message_state' => 'developer'
]; ];

@ -200,6 +200,15 @@ return [
'level' => Level::Info, 'level' => Level::Info,
'max_files' => 0, 'max_files' => 0,
], ],
// 发送微信消息
'send_wechat_message' => [
'driver' => 'custom',
'via' => GeneralDailyLogger::class,
'service_type' => 'SendWeChatMessage',
'level' => Level::Info,
'max_files' => 0,
]
], ],
]; ];

@ -70,8 +70,8 @@ return [
| 详见:https://easywechat.com/6.x/mini-app/index.html | 详见:https://easywechat.com/6.x/mini-app/index.html
| |
*/ */
'app_id' => env('WECHAT_MINI_APP_ID', 'wxccd0c4f642673a6d'), 'app_id' => env('WECHAT_MINI_APP_ID', 'wx84863fc5fadc3f8c'),
'secret' => env('WECHAT_MINI_APP_SECRET', '73f66753cf45336186f8640b6673d5fb'), 'secret' => env('WECHAT_MINI_APP_SECRET', 'feff14552b5507329e45bd14eec1de72'),
'token' => env('WECHAT_MINI_TOKEN', ''), 'token' => env('WECHAT_MINI_TOKEN', ''),
'aes_key' => env('WECHAT_MINI_AES_KEY', ''), 'aes_key' => env('WECHAT_MINI_AES_KEY', ''),

@ -13,7 +13,8 @@ return new class () extends Migration {
{ {
Schema::create('orders', static function (Blueprint $table) { Schema::create('orders', static function (Blueprint $table) {
$table->id(); $table->id();
$table->integer('relate_id')->default(0)->comment('关联订单号'); $table->integer('relate_id')->default(0)->comment('关联订单表ID');
$table->integer('relate_patient_id')->default(0)->comment('关联患者表ID');
$table->string('order_id', 64)->unique()->comment('订单ID'); $table->string('order_id', 64)->unique()->comment('订单ID');
$table->string('his_order_id', 64)->comment('his订单ID'); $table->string('his_order_id', 64)->comment('his订单ID');
$table->string('transaction_id', 64)->comment('交易平台单号'); $table->string('transaction_id', 64)->comment('交易平台单号');

@ -28,6 +28,7 @@ return new class () extends Migration {
$table->timestamp('lock_at')->nullable()->comment('锁号时间'); $table->timestamp('lock_at')->nullable()->comment('锁号时间');
$table->timestamp('unlock_at')->nullable()->comment('解锁时间'); $table->timestamp('unlock_at')->nullable()->comment('解锁时间');
$table->text('extra_info')->comment('额外的挂号信息'); $table->text('extra_info')->comment('额外的挂号信息');
$table->tinyInteger('reminder_sent')->default(0)->comment('是否已推送就诊提醒');
$table->timestamps(); $table->timestamps();
$table->engine = 'InnoDB'; $table->engine = 'InnoDB';
}); });

@ -11,17 +11,18 @@ return new class () extends Migration {
*/ */
public function up(): void public function up(): void
{ {
Schema::create('push_wechat_messages', static function (Blueprint $table) { Schema::create('send_message_jobs', static function (Blueprint $table) {
$table->id(); $table->id();
$table->integer('relate_order_id')->default(0)->index()->comment('关联订单表ID'); $table->integer('relate_order_id')->default(0)->index()->comment('关联订单表ID');
$table->integer('relate_patient_id')->index()->comment('关联患者表ID'); $table->integer('relate_patient_id')->index()->comment('关联患者表ID');
$table->tinyInteger('type')->index()->comment('消息类型 1公众号模板消息 2公众号订阅消息 3公众号一次性订阅消息 4公众号客服消息'); $table->tinyInteger('type')->index()->comment('消息类型 消息类型 1模板消息 2长期订阅消息 3一次性订阅消息 4客服消息');
$table->string('template_id', 64)->comment('推送消息ID'); $table->string('template_id', 64)->comment('推送消息ID');
$table->string('scene', 32)->default('')->comment('订阅场景'); $table->string('scene', 32)->default('')->comment('订阅场景');
$table->text('content')->comment('序列化推送消息内容'); $table->text('content')->comment('序列化推送消息内容');
$table->unsignedTinyInteger('number')->default(0)->index()->comment('已尝试发送次数'); $table->unsignedTinyInteger('number')->default(0)->index()->comment('已尝试发送次数');
$table->unsignedTinyInteger('status')->default(0)->index()->comment('状态 0尚未推送 1已推送 2推送异常'); $table->unsignedTinyInteger('status')->default(0)->index()->comment('状态 0尚未推送 1已推送 2推送异常');
$table->string('msg_id', 32)->default('')->comment('推送后返回的msg_id'); $table->string('msg_id', 32)->default('')->comment('推送后返回的msg_id');
$table->string('fail_reason')->default('')->comment('失败原因');
$table->timestamp('sent_at')->nullable()->comment('推送时间'); $table->timestamp('sent_at')->nullable()->comment('推送时间');
$table->timestamps(); $table->timestamps();
$table->engine = 'InnoDB'; $table->engine = 'InnoDB';
@ -33,6 +34,6 @@ return new class () extends Migration {
*/ */
public function down(): void public function down(): void
{ {
Schema::dropIfExists('push_wechat_messages'); Schema::dropIfExists('send_message_jobs');
} }
}; };

@ -1,33 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->string('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};
Loading…
Cancel
Save