新增
This commit is contained in:
@@ -50,13 +50,23 @@ class LoginController extends BaseAdminController
|
||||
$agentId = env('work_wechat.agent_id', '');
|
||||
|
||||
if (empty($corpId) || empty($agentId)) {
|
||||
return $this->data(['enabled' => false]);
|
||||
return $this->data([
|
||||
'enabled' => false,
|
||||
'require_bind_nonroot' => false,
|
||||
'force_bind_login' => LoginLogic::isForceBindWorkWechatFromEnv(),
|
||||
]);
|
||||
}
|
||||
|
||||
$oauthOk = LoginLogic::isWorkWechatOAuthConfigured();
|
||||
$forceBind = LoginLogic::isForceBindWorkWechatFromEnv();
|
||||
|
||||
return $this->data([
|
||||
'enabled' => true,
|
||||
'corp_id' => $corpId,
|
||||
'agent_id' => $agentId,
|
||||
/** 与 .env FORCE_BIND_LOGIN 一致:为 true 且 OAuth 配全时非 root 须先绑定 */
|
||||
'require_bind_nonroot' => $forceBind && $oauthOk,
|
||||
'force_bind_login' => $forceBind,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ use app\adminapi\validate\auth\AdminValidate;
|
||||
use app\adminapi\logic\auth\AdminLogic;
|
||||
use app\adminapi\logic\LoginLogic;
|
||||
use app\adminapi\validate\auth\editSelfValidate;
|
||||
use app\common\cache\AdminTokenCache;
|
||||
use app\common\model\auth\Admin;
|
||||
|
||||
/**
|
||||
@@ -169,9 +170,14 @@ class AdminController extends BaseAdminController
|
||||
return $this->fail('企业微信授权失败: ' . ($response['errmsg'] ?? '未知错误'));
|
||||
}
|
||||
|
||||
$wxUserId = $response['userid'] ?? '';
|
||||
if (empty($wxUserId)) {
|
||||
return $this->fail('未获取到企业微信用户身份');
|
||||
$wxUserId = LoginLogic::workWechatUserIdFromAuthResponse($response);
|
||||
if ($wxUserId === '') {
|
||||
$hasOpenId = trim((string) ($response['openid'] ?? $response['OpenId'] ?? '')) !== '';
|
||||
return $this->fail(
|
||||
$hasOpenId
|
||||
? '当前扫码账号非企业通讯录成员(或尚未同步到通讯录),无法绑定。请使用已在企业微信通讯录中的成员扫码,或联系管理员将你加入企业后再试'
|
||||
: '未获取到企业微信成员 userid。请确认 .env 中 work_wechat.secret 为「该自建应用」的 Secret(与 agent_id 对应),且绑定页完整 URL 已加入应用可信域名'
|
||||
);
|
||||
}
|
||||
|
||||
// 检查是否已被其他管理员绑定
|
||||
@@ -182,10 +188,15 @@ class AdminController extends BaseAdminController
|
||||
return $this->fail('该企业微信账号已被其他管理员绑定');
|
||||
}
|
||||
|
||||
Admin::update([
|
||||
'id' => $this->adminId,
|
||||
'work_wechat_userid' => $wxUserId,
|
||||
]);
|
||||
$affected = Admin::where('id', $this->adminId)->update(['work_wechat_userid' => $wxUserId]);
|
||||
if ($affected === 0) {
|
||||
return $this->fail('保存绑定失败,请确认账号有效后重试');
|
||||
}
|
||||
|
||||
$token = $this->request->header('token');
|
||||
if ($token) {
|
||||
(new AdminTokenCache())->deleteAdminInfo($token);
|
||||
}
|
||||
|
||||
return $this->success('绑定成功', ['work_wechat_userid' => $wxUserId]);
|
||||
}
|
||||
@@ -195,10 +206,7 @@ class AdminController extends BaseAdminController
|
||||
*/
|
||||
public function unbindWorkWechat()
|
||||
{
|
||||
Admin::update([
|
||||
'id' => $this->adminId,
|
||||
'work_wechat_userid' => '',
|
||||
]);
|
||||
Admin::where('id', $this->adminId)->update(['work_wechat_userid' => '']);
|
||||
|
||||
return $this->success('解绑成功');
|
||||
}
|
||||
|
||||
@@ -25,6 +25,27 @@ class OrderController extends BaseAdminController
|
||||
return $this->dataLists(new OrderLists());
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 指定诊单下已支付支付单列表(创建/编辑处方业务订单时多选关联)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function paidOrdersForDiagnosis()
|
||||
{
|
||||
$diagnosisId = (int) $this->request->get('diagnosis_id', 0);
|
||||
if ($diagnosisId <= 0) {
|
||||
return $this->fail('诊单ID必填');
|
||||
}
|
||||
$exceptPo = (int) $this->request->get('prescription_order_id', 0);
|
||||
$lists = OrderLogic::listPaidOrdersForDiagnosis(
|
||||
$diagnosisId,
|
||||
$this->adminId,
|
||||
$this->adminInfo,
|
||||
$exceptPo > 0 ? $exceptPo : null
|
||||
);
|
||||
|
||||
return $this->success('', ['lists' => $lists]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 同步企业微信对外收款账单到订单
|
||||
* 员工直接在企业微信发起收款(未经过后台创建)时,通过此接口拉取并创建订单
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\controller\tcm;
|
||||
|
||||
use app\adminapi\controller\BaseAdminController;
|
||||
use app\adminapi\lists\tcm\PrescriptionOrderLists;
|
||||
use app\adminapi\logic\order\OrderLogic;
|
||||
use app\adminapi\logic\tcm\PrescriptionOrderLogic;
|
||||
use app\adminapi\validate\tcm\PrescriptionOrderValidate;
|
||||
|
||||
/**
|
||||
* 处方业务订单(非支付单)
|
||||
*/
|
||||
class PrescriptionOrderController extends BaseAdminController
|
||||
{
|
||||
public function lists()
|
||||
{
|
||||
return $this->dataLists(new PrescriptionOrderLists());
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定诊单下已支付支付单(创建/编辑业务订单时多选关联)
|
||||
*/
|
||||
public function paidPayOrders()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->get()->goCheck('paidPayOrders');
|
||||
$exceptPo = (int) $this->request->get('prescription_order_id', 0);
|
||||
$lists = OrderLogic::listPaidOrdersForDiagnosis(
|
||||
(int) $params['diagnosis_id'],
|
||||
$this->adminId,
|
||||
$this->adminInfo,
|
||||
$exceptPo > 0 ? $exceptPo : null
|
||||
);
|
||||
|
||||
return $this->success('', ['lists' => $lists]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->post()->goCheck('create');
|
||||
$result = PrescriptionOrderLogic::create($params, $this->adminId, $this->adminInfo);
|
||||
if ($result === false) {
|
||||
return $this->fail(PrescriptionOrderLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('创建成功', $result);
|
||||
}
|
||||
|
||||
public function detail()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->get()->goCheck('detail');
|
||||
$detail = PrescriptionOrderLogic::detail((int) $params['id'], $this->adminId, $this->adminInfo);
|
||||
if ($detail === null) {
|
||||
return $this->fail(PrescriptionOrderLogic::getError());
|
||||
}
|
||||
|
||||
return $this->data($detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* 快递轨迹查询(对接快递100;未配置时仍可跳转顺丰/京东官网)
|
||||
*/
|
||||
public function logisticsTrace()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->get()->goCheck('logisticsTrace');
|
||||
$expressOverride = trim((string) $this->request->get('express_company', ''));
|
||||
$data = PrescriptionOrderLogic::logisticsTrace(
|
||||
(int) $params['id'],
|
||||
$expressOverride,
|
||||
$this->adminId,
|
||||
$this->adminInfo
|
||||
);
|
||||
if ($data === null) {
|
||||
return $this->fail(PrescriptionOrderLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('', $data);
|
||||
}
|
||||
|
||||
public function edit()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->post()->goCheck('edit');
|
||||
$result = PrescriptionOrderLogic::edit($params, $this->adminId, $this->adminInfo);
|
||||
if ($result === false) {
|
||||
return $this->fail(PrescriptionOrderLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('保存成功', $result);
|
||||
}
|
||||
|
||||
public function auditPrescription()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->post()->goCheck('auditPrescription');
|
||||
$result = PrescriptionOrderLogic::auditPrescription(
|
||||
(int) $params['id'],
|
||||
(string) $params['action'],
|
||||
(string) ($params['remark'] ?? ''),
|
||||
$this->adminId,
|
||||
$this->adminInfo
|
||||
);
|
||||
if ($result === false) {
|
||||
return $this->fail(PrescriptionOrderLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('操作成功', $result);
|
||||
}
|
||||
|
||||
public function auditPayment()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->post()->goCheck('auditPayment');
|
||||
$result = PrescriptionOrderLogic::auditPaymentSlip(
|
||||
(int) $params['id'],
|
||||
(string) $params['action'],
|
||||
(string) ($params['remark'] ?? ''),
|
||||
$this->adminId,
|
||||
$this->adminInfo
|
||||
);
|
||||
if ($result === false) {
|
||||
return $this->fail(PrescriptionOrderLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('操作成功', $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 医助/创建人撤回:仅「待双审通过」可撤(未通过或双审均驳回等仍为该状态)
|
||||
*/
|
||||
public function withdraw()
|
||||
{
|
||||
$params = (new PrescriptionOrderValidate())->post()->goCheck('withdraw');
|
||||
$result = PrescriptionOrderLogic::withdraw((int) $params['id'], $this->adminId, $this->adminInfo);
|
||||
if ($result === false) {
|
||||
return $this->fail(PrescriptionOrderLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('已撤回', $result);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ declare (strict_types=1);
|
||||
|
||||
namespace app\adminapi\http\middleware;
|
||||
|
||||
use app\adminapi\logic\LoginLogic;
|
||||
use app\common\{
|
||||
cache\AdminAuthCache,
|
||||
service\JsonService
|
||||
@@ -48,11 +49,23 @@ class AuthMiddleware
|
||||
return JsonService::fail('ip地址发生变化,请重新登录', [], -1);
|
||||
}
|
||||
|
||||
// 非 root 待绑企微:放行绑定 / 解绑 / 个人信息 / 退出(避免无菜单权限;action 与路由大小写一致)
|
||||
if (LoginLogic::adminMustBindWorkWechat($request->adminInfo)) {
|
||||
if (LoginLogic::isWorkWechatBindExemptActionName((string) $request->action())) {
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
//系统默认超级管理员,无需权限验证
|
||||
if (1 === $request->adminInfo['root']) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
// 面诊进度看板:仅登录即可拉医生列表 + 预约列表(须带 progress_board=1,见 AdminLists / AppointmentLists 内限制)
|
||||
if ($this->isFaceProgressBoardPublicLists($request)) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
$adminAuthCache = new AdminAuthCache($request->adminInfo['admin_id']);
|
||||
|
||||
// 当前访问路径
|
||||
@@ -90,4 +103,19 @@ class AuthMiddleware
|
||||
}, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 面诊进度专用:auth.admin/lists、doctor.appointment/lists + progress_board=1,不校验菜单权限
|
||||
*/
|
||||
private function isFaceProgressBoardPublicLists($request): bool
|
||||
{
|
||||
if ((int) $request->param('progress_board', 0) !== 1) {
|
||||
return false;
|
||||
}
|
||||
if (strtolower((string) $request->action()) !== 'lists') {
|
||||
return false;
|
||||
}
|
||||
$c = strtolower((string) $request->controller());
|
||||
|
||||
return in_array($c, ['auth.admin', 'doctor.appointment'], true);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ declare (strict_types=1);
|
||||
|
||||
namespace app\adminapi\http\middleware;
|
||||
|
||||
use app\adminapi\logic\LoginLogic;
|
||||
use app\common\cache\AdminTokenCache;
|
||||
use app\adminapi\service\AdminTokenService;
|
||||
use app\common\service\JsonService;
|
||||
@@ -72,7 +73,34 @@ class LoginMiddleware
|
||||
$request->adminInfo = $adminInfo;
|
||||
$request->adminId = $adminInfo['admin_id'] ?? 0;
|
||||
|
||||
// 非 root 须绑定企微:免登录接口不拦截(如 getConfig),避免 init 死循环
|
||||
if (!empty($adminInfo) && LoginLogic::adminMustBindWorkWechat($adminInfo) && !$isNotNeedLogin) {
|
||||
if (!self::isWorkWechatBindExemptAction($request)) {
|
||||
return JsonService::fail(
|
||||
'请先绑定企业微信后再使用系统',
|
||||
[],
|
||||
LoginLogic::CODE_NEED_BIND_WORK_WECHAT,
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
private static function isWorkWechatBindExemptAction($request): bool
|
||||
{
|
||||
if (LoginLogic::isWorkWechatBindExemptActionName((string) $request->action())) {
|
||||
return true;
|
||||
}
|
||||
// 与 AuthMiddleware 一致:未绑企微时仍允许打开面诊进度所需只读列表
|
||||
if ((int) $request->param('progress_board', 0) === 1 && strtolower((string) $request->action()) === 'lists') {
|
||||
$c = strtolower((string) $request->controller());
|
||||
|
||||
return in_array($c, ['auth.admin', 'doctor.appointment'], true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -71,7 +71,39 @@ class OperationLog
|
||||
$systemLog->type = $request->isGet() ? 'GET' : 'POST';
|
||||
$systemLog->params = json_encode($params, true);
|
||||
$systemLog->ip = $request->ip();
|
||||
$systemLog->result = $response->getContent();
|
||||
$systemLog->result = $this->shortenOperationLogResult((string) $response->getContent());
|
||||
return $systemLog->save();
|
||||
}
|
||||
|
||||
/** MySQL TEXT 上限约 64KB,列表接口常含 base64 签名导致超长 */
|
||||
private function shortenOperationLogResult(string $content): string
|
||||
{
|
||||
$maxBytes = 62000;
|
||||
$decoded = json_decode($content, true);
|
||||
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
|
||||
$this->stripLargeLogFields($decoded);
|
||||
$content = json_encode($decoded, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
if (strlen($content) > $maxBytes) {
|
||||
$content = substr($content, 0, $maxBytes) . '...[truncated]';
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function stripLargeLogFields(array &$data): void
|
||||
{
|
||||
foreach ($data as $key => &$value) {
|
||||
if (is_array($value)) {
|
||||
$this->stripLargeLogFields($value);
|
||||
continue;
|
||||
}
|
||||
if (! is_string($key)) {
|
||||
continue;
|
||||
}
|
||||
if (in_array($key, ['doctor_signature', 'tongue_image'], true) && is_string($value) && strlen($value) > 200) {
|
||||
$value = '[omitted:' . strlen($value) . ' bytes]';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,12 +112,28 @@ class AdminLists extends BaseAdminDataLists implements ListsExtendInterface, Lis
|
||||
public function queryWhere()
|
||||
{
|
||||
$where = [];
|
||||
if (isset($this->params['role_id']) && $this->params['role_id'] != '') {
|
||||
$adminIds = AdminRole::where('role_id', $this->params['role_id'])->column('admin_id');
|
||||
$progressBoard = (int) ($this->params['progress_board'] ?? 0) === 1;
|
||||
|
||||
if ($progressBoard) {
|
||||
// 面诊进度:固定只拉「医生」角色且未禁用,忽略客户端篡改的 role_id
|
||||
$adminIds = AdminRole::where('role_id', 1)->column('admin_id');
|
||||
if (!empty($adminIds)) {
|
||||
$where[] = ['id', 'in', $adminIds];
|
||||
}
|
||||
$where[] = ['disable', '=', 0];
|
||||
} else {
|
||||
if (isset($this->params['role_id']) && $this->params['role_id'] != '') {
|
||||
$adminIds = AdminRole::where('role_id', $this->params['role_id'])->column('admin_id');
|
||||
if (!empty($adminIds)) {
|
||||
$where[] = ['id', 'in', $adminIds];
|
||||
}
|
||||
}
|
||||
// 排除禁止登录(disable=1),医生选择器等场景
|
||||
if ((int) ($this->params['exclude_disabled'] ?? 0) === 1) {
|
||||
$where[] = ['disable', '=', 0];
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,14 +64,29 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
$this->searchWhere[] = ['a.patient_id', '=', (int) $this->params['patient_id']];
|
||||
}
|
||||
|
||||
// 构建查询
|
||||
// 按接诊医生筛选(管理端医生进度看板等)
|
||||
if (!empty($this->params['doctor_id'])) {
|
||||
$this->searchWhere[] = ['a.doctor_id', '=', (int) $this->params['doctor_id']];
|
||||
}
|
||||
|
||||
// 排除已取消挂号 status=2(医生进度看板等;未显式筛选「已取消」时生效)
|
||||
if ((int) ($this->params['exclude_cancelled'] ?? 0) === 1) {
|
||||
$sf = $this->params['status'] ?? '';
|
||||
if ($sf === '' || (int) $sf !== 2) {
|
||||
$this->searchWhere[] = ['a.status', '<>', 2];
|
||||
}
|
||||
}
|
||||
|
||||
// 构建查询(searchWhere 为空时避免部分环境下 where([]) 异常)
|
||||
$query = Appointment::alias('a')
|
||||
->with('diagnosis')
|
||||
->leftJoin('tcm_diagnosis u', 'a.patient_id = u.id')
|
||||
->leftJoin('admin ad', 'a.doctor_id = ad.id')
|
||||
->leftJoin('admin asst', 'a.assistant_id = asst.id')
|
||||
->field('a.*, u.patient_name as patient_name, u.phone as patient_phone, u.gender as gender, u.age as age, u.weight as weight, u.height as height, ad.name as doctor_name, asst.name as assistant_name, u.id as diagnosis_id')
|
||||
->where($this->searchWhere);
|
||||
->leftJoin('admin asst', 'u.assistant_id = asst.id')
|
||||
->field('a.*, u.patient_name as patient_name, u.phone as patient_phone, u.gender as gender, u.age as age, u.weight as weight, u.height as height, u.assistant_id as assistant_id, ad.name as doctor_name, asst.name as assistant_name, u.id as diagnosis_id');
|
||||
if ($this->searchWhere !== []) {
|
||||
$query->where($this->searchWhere);
|
||||
}
|
||||
|
||||
// 是否确认诊单:1=已确认 0=未确认
|
||||
if (isset($this->params['diagnosis_confirmed']) && $this->params['diagnosis_confirmed'] !== '') {
|
||||
@@ -85,14 +100,18 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
}
|
||||
}
|
||||
|
||||
// 如果是医生角色(role_id=1),只显示挂自己号的预约
|
||||
if (in_array(1, $roleIds)) {
|
||||
$query->where('a.doctor_id', $this->adminId);
|
||||
}
|
||||
|
||||
// 如果是医助角色(role_id=2),只显示自己添加的患者的预约
|
||||
if (in_array(2, $roleIds)) {
|
||||
$query->where('u.assistant_id', $this->adminId);
|
||||
// 面诊进度看板:可切换查看各医生挂号,不按当前账号角色收窄
|
||||
$progressBoard = (int) ($this->params['progress_board'] ?? 0) === 1;
|
||||
if (!$progressBoard) {
|
||||
// 如果是医生角色(role_id=1),只显示挂自己号的预约
|
||||
if (in_array(1, $roleIds)) {
|
||||
$query->where('a.doctor_id', $this->adminId);
|
||||
}
|
||||
|
||||
// 如果是医助角色(role_id=2),只显示自己添加的患者的预约
|
||||
if (in_array(2, $roleIds)) {
|
||||
$query->where('u.assistant_id', $this->adminId);
|
||||
}
|
||||
}
|
||||
|
||||
// 诊单软删除后不再展示对应挂号(leftJoin 时无诊单或诊单未删)
|
||||
@@ -118,11 +137,19 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
$confirmedMap = array_flip($confirmedIds ?: []);
|
||||
}
|
||||
// 开方状态:诊单是否有处方(按 diagnosis_id 查)
|
||||
// prescription_today_only=1:仅统计「今日创建」的处方(医生进度看板用,与历史处方区分)
|
||||
$rxTodayOnly = (int) ($this->params['prescription_today_only'] ?? 0) === 1;
|
||||
$prescribedDiagnosisIds = [];
|
||||
if (!empty($diagnosisIds)) {
|
||||
$prescribedDiagnosisIds = Prescription::whereIn('diagnosis_id', $diagnosisIds)
|
||||
$rxQ = Prescription::whereIn('diagnosis_id', $diagnosisIds)
|
||||
->whereNull('delete_time')
|
||||
->column('diagnosis_id');
|
||||
->where('void_status', 0);
|
||||
if ($rxTodayOnly) {
|
||||
$dayStart = strtotime(date('Y-m-d 00:00:00'));
|
||||
$dayEnd = strtotime(date('Y-m-d 23:59:59'));
|
||||
$rxQ->whereBetween('create_time', [$dayStart, $dayEnd]);
|
||||
}
|
||||
$prescribedDiagnosisIds = $rxQ->column('diagnosis_id');
|
||||
$prescribedDiagnosisIds = array_flip($prescribedDiagnosisIds ?: []);
|
||||
}
|
||||
// 当前页预约关联的处方(按 appointment_id,取最新一条):用于「开方/查看」与审核状态
|
||||
@@ -131,6 +158,7 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
if (!empty($appointmentIds)) {
|
||||
$rxRows = Prescription::whereIn('appointment_id', $appointmentIds)
|
||||
->whereNull('delete_time')
|
||||
->where('void_status', 0)
|
||||
->order('id', 'desc')
|
||||
->field(['id', 'appointment_id', 'audit_status', 'void_status'])
|
||||
->select()
|
||||
@@ -208,6 +236,17 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
$query->where('a.patient_id', '=', (int) $this->params['patient_id']);
|
||||
}
|
||||
|
||||
if (!empty($this->params['doctor_id'])) {
|
||||
$query->where('a.doctor_id', '=', (int) $this->params['doctor_id']);
|
||||
}
|
||||
|
||||
if ((int) ($this->params['exclude_cancelled'] ?? 0) === 1) {
|
||||
$sf = $this->params['status'] ?? '';
|
||||
if ($sf === '' || (int) $sf !== 2) {
|
||||
$query->where('a.status', '<>', 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($this->params['diagnosis_confirmed']) && $this->params['diagnosis_confirmed'] !== '') {
|
||||
$confirmed = (int)$this->params['diagnosis_confirmed'];
|
||||
$tbl = (new DiagnosisViewRecord())->getTable();
|
||||
@@ -219,11 +258,14 @@ class AppointmentLists extends BaseAdminDataLists implements ListsSearchInterfac
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array(1, $roleIds)) {
|
||||
$query->where('a.doctor_id', $this->adminId);
|
||||
}
|
||||
if (in_array(2, $roleIds)) {
|
||||
$query->where('u.assistant_id', $this->adminId);
|
||||
$progressBoard = (int) ($this->params['progress_board'] ?? 0) === 1;
|
||||
if (!$progressBoard) {
|
||||
if (in_array(1, $roleIds)) {
|
||||
$query->where('a.doctor_id', $this->adminId);
|
||||
}
|
||||
if (in_array(2, $roleIds)) {
|
||||
$query->where('u.assistant_id', $this->adminId);
|
||||
}
|
||||
}
|
||||
|
||||
$query->whereRaw('(u.id IS NULL OR u.delete_time IS NULL)');
|
||||
|
||||
@@ -21,6 +21,7 @@ use app\common\model\doctor\Appointment;
|
||||
use app\common\model\tcm\Prescription;
|
||||
use app\common\model\tcm\CallRecord;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\auth\AdminDept;
|
||||
use app\common\model\auth\AdminRole;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
|
||||
@@ -96,6 +97,21 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
}
|
||||
}
|
||||
|
||||
// 仅已开方(有处方)的诊单:随访列表传 only_has_prescription=1;问诊等页面不传则不过滤
|
||||
if (isset($this->params['only_has_prescription']) && (string) $this->params['only_has_prescription'] === '1') {
|
||||
$rxTbl = (new Prescription())->getTable();
|
||||
$diagTbl = (new Diagnosis())->getTable();
|
||||
$query->whereExists("SELECT 1 FROM {$rxTbl} rx WHERE rx.diagnosis_id = {$diagTbl}.id AND rx.delete_time IS NULL");
|
||||
}
|
||||
|
||||
// 医助所属部门(随访等页面传 assistant_dept_id):只看待定部门下医助负责的诊单
|
||||
if (isset($this->params['assistant_dept_id']) && $this->params['assistant_dept_id'] !== '' && (int) $this->params['assistant_dept_id'] > 0) {
|
||||
$deptId = (int) $this->params['assistant_dept_id'];
|
||||
$adTbl = (new AdminDept())->getTable();
|
||||
$diagTbl = (new Diagnosis())->getTable();
|
||||
$query->whereExists("SELECT 1 FROM {$adTbl} ad WHERE ad.admin_id = {$diagTbl}.assistant_id AND ad.dept_id = {$deptId}");
|
||||
}
|
||||
|
||||
// 按挂号日期+时间升序。若传了 appointment_date(当天/明天等筛选),只按「该日」的挂号排序与展示,避免仍按历史最早一天把昨天号顶到最前
|
||||
$diagTbl = (new Diagnosis())->getTable();
|
||||
$aptTbl = (new Appointment())->getTable();
|
||||
@@ -114,9 +130,10 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
|
||||
// 关联挂号:doctor_appointment.patient_id 存诊单主键 id(与 Appointment 列表 join 一致)
|
||||
$diagnosisIds = array_filter(array_unique(array_column($lists, 'id')));
|
||||
|
||||
if (!empty($diagnosisIds)) {
|
||||
$appointmentMap = [];
|
||||
$subQuery = Appointment::where('patient_id', 'in', $diagnosisIds)
|
||||
@@ -124,20 +141,36 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
if (!empty($this->params['appointment_date'])) {
|
||||
$subQuery->where('appointment_date', (string) $this->params['appointment_date']);
|
||||
}
|
||||
|
||||
$subQuery
|
||||
->field('id, patient_id, doctor_id, appointment_date, appointment_time, status, create_time')
|
||||
->order('appointment_date', 'asc')
|
||||
->order('appointment_time', 'asc')
|
||||
->order('id', 'asc');
|
||||
$appointments = $subQuery->select()->toArray();
|
||||
|
||||
foreach ($appointments as $apt) {
|
||||
$did = $apt['patient_id'];
|
||||
$did = (int) ($apt['patient_id'] ?? 0);
|
||||
if ($did <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($appointmentMap[$did])) {
|
||||
$appointmentMap[$did] = $apt;
|
||||
$appointmentMap[$did] = [];
|
||||
}
|
||||
$appointmentMap[$did][] = $apt;
|
||||
}
|
||||
|
||||
// 获取医生名称
|
||||
$doctorIds = [];
|
||||
foreach ($appointmentMap as $aptList) {
|
||||
foreach ($aptList as $apt) {
|
||||
$docId = (int) ($apt['doctor_id'] ?? 0);
|
||||
if ($docId > 0) {
|
||||
$doctorIds[] = $docId;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 获取医生名称
|
||||
$doctorIds = array_unique(array_column($appointmentMap, 'doctor_id'));
|
||||
$doctorIds = array_values(array_unique($doctorIds));
|
||||
$doctorNames = [];
|
||||
if (!empty($doctorIds)) {
|
||||
$doctors = Admin::whereIn('id', $doctorIds)->column('name', 'id');
|
||||
@@ -145,8 +178,9 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
}
|
||||
// 合并到诊单列表
|
||||
foreach ($lists as &$item) {
|
||||
$apt = $appointmentMap[$item['id']] ?? null;
|
||||
if ($apt) {
|
||||
$aptList = $appointmentMap[(int) $item['id']] ?? [];
|
||||
if (!empty($aptList)) {
|
||||
$apt = $aptList[0];
|
||||
$item['has_appointment'] = 1;
|
||||
$item['appointment_id'] = $apt['id'];
|
||||
$item['appointment_status'] = (int) ($apt['status'] ?? 0);
|
||||
@@ -157,6 +191,21 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
$timePart = substr($timePart, 0, 5); // 09:00:00 -> 09:00
|
||||
}
|
||||
$item['appointment_time_text'] = trim(($apt['appointment_date'] ?? '') . ' ' . $timePart);
|
||||
$item['appointments'] = array_map(function ($a) use ($doctorNames) {
|
||||
$timePart = $a['appointment_time'] ?? '';
|
||||
if (strlen((string) $timePart) > 5) {
|
||||
$timePart = substr((string) $timePart, 0, 5);
|
||||
}
|
||||
$doctorId = (int) ($a['doctor_id'] ?? 0);
|
||||
|
||||
return [
|
||||
'id' => (int) ($a['id'] ?? 0),
|
||||
'status' => (int) ($a['status'] ?? 0),
|
||||
'doctor_id' => $doctorId,
|
||||
'doctor_name' => (string) ($doctorNames[$doctorId] ?? '-'),
|
||||
'time_text' => trim((string) ($a['appointment_date'] ?? '') . ' ' . (string) $timePart),
|
||||
];
|
||||
}, $aptList);
|
||||
} else {
|
||||
$item['has_appointment'] = 0;
|
||||
$item['appointment_id'] = null;
|
||||
@@ -164,6 +213,7 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
$item['appointment_doctor_id'] = null;
|
||||
$item['appointment_doctor_name'] = '';
|
||||
$item['appointment_time_text'] = '';
|
||||
$item['appointments'] = [];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -174,6 +224,7 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
$item['appointment_doctor_id'] = null;
|
||||
$item['appointment_doctor_name'] = '';
|
||||
$item['appointment_time_text'] = '';
|
||||
$item['appointments'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +242,66 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
$item['has_prescription'] = isset($prescriptionMap[$item['id']]) ? 1 : 0;
|
||||
}
|
||||
|
||||
// 复诊展示:业务上「已开方」即算有复诊,取该诊单下最新一条处方的时间与医师(优先未作废)
|
||||
$followupByDiag = [];
|
||||
$rxCountByDiag = [];
|
||||
if ($diagnosisIds !== []) {
|
||||
$rxAll = Prescription::whereIn('diagnosis_id', $diagnosisIds)
|
||||
->whereNull('delete_time')
|
||||
->field(['id', 'diagnosis_id', 'doctor_name', 'creator_id', 'prescription_date', 'create_time', 'void_status'])
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($rxAll as $rx) {
|
||||
$dCount = (int) ($rx['diagnosis_id'] ?? 0);
|
||||
if ($dCount > 0) {
|
||||
$rxCountByDiag[$dCount] = ($rxCountByDiag[$dCount] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
foreach ($rxAll as $rx) {
|
||||
$d = (int) ($rx['diagnosis_id'] ?? 0);
|
||||
if ($d <= 0 || isset($followupByDiag[$d])) {
|
||||
continue;
|
||||
}
|
||||
if ((int) ($rx['void_status'] ?? 0) === 0) {
|
||||
$followupByDiag[$d] = $rx;
|
||||
}
|
||||
}
|
||||
foreach ($rxAll as $rx) {
|
||||
$d = (int) ($rx['diagnosis_id'] ?? 0);
|
||||
if ($d <= 0 || isset($followupByDiag[$d])) {
|
||||
continue;
|
||||
}
|
||||
$followupByDiag[$d] = $rx;
|
||||
}
|
||||
$creatorIds = array_values(array_unique(array_filter(array_column($followupByDiag, 'creator_id'))));
|
||||
$creatorNames = [];
|
||||
if ($creatorIds !== []) {
|
||||
$creatorNames = Admin::whereIn('id', $creatorIds)->whereNull('delete_time')->column('name', 'id');
|
||||
}
|
||||
foreach ($followupByDiag as $d => $rx) {
|
||||
$doctorName = trim((string) ($rx['doctor_name'] ?? ''));
|
||||
$cid = (int) ($rx['creator_id'] ?? 0);
|
||||
if ($doctorName === '' && $cid > 0) {
|
||||
$doctorName = (string) ($creatorNames[$cid] ?? '');
|
||||
}
|
||||
$followupByDiag[$d] = [
|
||||
'time_text' => $this->formatPrescriptionFollowupTime($rx),
|
||||
'doctor_name' => $doctorName !== '' ? $doctorName : '—',
|
||||
'voided' => (int) ($rx['void_status'] ?? 0) !== 0,
|
||||
];
|
||||
}
|
||||
}
|
||||
foreach ($lists as &$item) {
|
||||
$did = (int) $item['id'];
|
||||
$fu = $followupByDiag[$did] ?? null;
|
||||
$item['followup_time_text'] = ($item['has_prescription'] && $fu) ? ($fu['time_text'] ?? '') : '';
|
||||
$item['followup_doctor_name'] = ($item['has_prescription'] && $fu) ? ($fu['doctor_name'] ?? '—') : '';
|
||||
$item['followup_rx_voided'] = ($item['has_prescription'] && $fu && !empty($fu['voided'])) ? 1 : 0;
|
||||
// 开方次数即复诊次数:1 张处方 = 第 1 次复诊,以此类推
|
||||
$item['followup_prescription_count'] = (int) ($rxCountByDiag[$did] ?? 0);
|
||||
}
|
||||
|
||||
// 最近一条通话记录状态(列表行展示:通话中 / 已结束等)
|
||||
$latestCallByDiag = [];
|
||||
if (!empty($diagnosisIds)) {
|
||||
@@ -214,6 +325,20 @@ class DiagnosisLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
return $lists;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $rx 处方一行
|
||||
*/
|
||||
private function formatPrescriptionFollowupTime(array $rx): string
|
||||
{
|
||||
$ct = (int) ($rx['create_time'] ?? 0);
|
||||
$pd = trim((string) ($rx['prescription_date'] ?? ''));
|
||||
if ($pd !== '') {
|
||||
return $pd . ($ct > 0 ? ' ' . date('H:i', $ct) : '');
|
||||
}
|
||||
|
||||
return $ct > 0 ? date('Y-m-d H:i', $ct) : '—';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed>|null $rec tcm_call_record 一行
|
||||
* @return array{state:string,label:string,end_time?:int,start_time?:int}
|
||||
|
||||
@@ -8,6 +8,7 @@ use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\adminapi\logic\tcm\PrescriptionLogic;
|
||||
use app\common\model\tcm\Prescription;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
|
||||
/**
|
||||
* 处方列表
|
||||
@@ -80,6 +81,7 @@ class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterfa
|
||||
$query->where(function ($q) use ($adminId, $roleIds) {
|
||||
$q->whereOr('is_shared', '=', 1);
|
||||
$q->whereOr('creator_id', '=', $adminId);
|
||||
$q->whereOr('assistant_id', '=', $adminId);
|
||||
foreach ($roleIds as $rid) {
|
||||
if ($rid > 0) {
|
||||
$q->whereOrRaw('FIND_IN_SET(?, `visible_role_ids`)', [(string) $rid]);
|
||||
@@ -105,7 +107,42 @@ class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterfa
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
|
||||
$rxIds = array_column($lists, 'id');
|
||||
$rxIds = array_map('intval', $rxIds);
|
||||
$rejectedRx = [];
|
||||
$bizRejectRemark = [];
|
||||
$hasBizOrderRx = [];
|
||||
if ($rxIds !== []) {
|
||||
$bizRejectRows = PrescriptionOrder::whereIn('prescription_id', $rxIds)
|
||||
->where('prescription_audit_status', 2)
|
||||
->whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->field(['prescription_id', 'prescription_audit_remark', 'id'])
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($bizRejectRows as $br) {
|
||||
$pid = (int) ($br['prescription_id'] ?? 0);
|
||||
if ($pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$rejectedRx[$pid] = true;
|
||||
if (!isset($bizRejectRemark[$pid])) {
|
||||
$bizRejectRemark[$pid] = (string) ($br['prescription_audit_remark'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
$orderRxIds = array_unique(array_map(
|
||||
'intval',
|
||||
PrescriptionOrder::whereIn('prescription_id', $rxIds)
|
||||
->whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->column('prescription_id')
|
||||
));
|
||||
$hasBizOrderRx = array_fill_keys($orderRxIds, true);
|
||||
}
|
||||
|
||||
// 处理字段格式
|
||||
foreach ($lists as &$item) {
|
||||
// 设置默认值
|
||||
@@ -117,6 +154,10 @@ class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterfa
|
||||
$item['audit_status'] = (int) ($item['audit_status'] ?? 1);
|
||||
$item['void_status'] = (int) ($item['void_status'] ?? 0);
|
||||
$item['visible_role_ids'] = PrescriptionLogic::visibleRoleIdsToArray((string) ($item['visible_role_ids'] ?? ''));
|
||||
$rid = (int) ($item['id'] ?? 0);
|
||||
$item['business_prescription_audit_rejected'] = !empty($rejectedRx[$rid]) ? 1 : 0;
|
||||
$item['business_prescription_audit_remark'] = (string) ($bizRejectRemark[$rid] ?? '');
|
||||
$item['has_prescription_order'] = !empty($hasBizOrderRx[$rid]) ? 1 : 0;
|
||||
|
||||
// 将 dietary_taboo 从逗号分隔的字符串转换为数组(前端需要数组格式)
|
||||
if (!empty($item['dietary_taboo']) && is_string($item['dietary_taboo'])) {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\tcm;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\tcm\PrescriptionOrderLogic;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
use app\common\model\tcm\PrescriptionOrderPayOrder;
|
||||
|
||||
class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'=' => ['prescription_id', 'fulfillment_status'],
|
||||
'%like%' => ['order_no'],
|
||||
];
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$query = PrescriptionOrder::where($this->searchWhere)->whereNull('delete_time');
|
||||
if (!PrescriptionOrderLogic::canSeeAllPrescriptionOrders($this->adminInfo)) {
|
||||
$diagIds = Diagnosis::where('assistant_id', $this->adminId)->whereNull('delete_time')->column('id');
|
||||
$query->where(function ($q) use ($diagIds) {
|
||||
$q->where('creator_id', $this->adminId);
|
||||
if ($diagIds !== []) {
|
||||
$q->whereOr('diagnosis_id', 'in', $diagIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
$lists = $query
|
||||
->order('id', 'desc')
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
foreach ($lists as &$item) {
|
||||
PrescriptionOrderLogic::maskInternalCostIfNeeded($item, $this->adminInfo);
|
||||
$item['linked_pay_order_count'] = (int) PrescriptionOrderPayOrder::where(
|
||||
'prescription_order_id',
|
||||
(int) ($item['id'] ?? 0)
|
||||
)->count();
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return $lists;
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
$query = PrescriptionOrder::where($this->searchWhere)->whereNull('delete_time');
|
||||
if (!PrescriptionOrderLogic::canSeeAllPrescriptionOrders($this->adminInfo)) {
|
||||
$diagIds = Diagnosis::where('assistant_id', $this->adminId)->whereNull('delete_time')->column('id');
|
||||
$query->where(function ($q) use ($diagIds) {
|
||||
$q->where('creator_id', $this->adminId);
|
||||
if ($diagIds !== []) {
|
||||
$q->whereOr('diagnosis_id', 'in', $diagIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (int) $query->count();
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,83 @@ use think\facade\Log;
|
||||
*/
|
||||
class LoginLogic extends BaseLogic
|
||||
{
|
||||
/** 非 root 未绑定企微时接口返回,与 axios 约定一致 */
|
||||
public const CODE_NEED_BIND_WORK_WECHAT = 10;
|
||||
|
||||
/**
|
||||
* 企业微信网页授权 / 扫码绑定所需配置是否齐全
|
||||
*/
|
||||
public static function isWorkWechatOAuthConfigured(): bool
|
||||
{
|
||||
$corpId = (string) env('work_wechat.corp_id', '');
|
||||
$secret = (string) env('work_wechat.secret', '');
|
||||
$agentId = (string) env('work_wechat.agent_id', '');
|
||||
|
||||
return $corpId !== '' && $secret !== '' && $agentId !== '';
|
||||
}
|
||||
|
||||
/**
|
||||
* .env 是否开启「非 root 须绑定企微」。
|
||||
* 推荐在 [work_wechat] 下写 FORCE_BIND_LOGIN=true;勿在节内写 WORK_WECHAT_FORCE_BIND_LOGIN(会变成双前缀键读不到)。
|
||||
*/
|
||||
public static function isForceBindWorkWechatFromEnv(): bool
|
||||
{
|
||||
$candidates = [
|
||||
env('WORK_WECHAT_FORCE_BIND_LOGIN', false),
|
||||
env('work_wechat.force_bind_login', false),
|
||||
env('work_wechat.work_wechat_force_bind_login', false),
|
||||
];
|
||||
$v = false;
|
||||
foreach ($candidates as $c) {
|
||||
if ($c !== false && $c !== null && $c !== '') {
|
||||
$v = $c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_bool($v)) {
|
||||
return $v;
|
||||
}
|
||||
$v = strtolower(trim((string) $v));
|
||||
|
||||
return in_array($v, ['1', 'true', 'yes', 'on'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* .env 开启强制绑定 + 企微 OAuth 已配置 + 非 root + 未绑定 work_wechat_userid
|
||||
*
|
||||
* @param array{root?:int|string,work_wechat_userid?:string} $adminInfo
|
||||
*/
|
||||
public static function adminMustBindWorkWechat(array $adminInfo): bool
|
||||
{
|
||||
if (!self::isForceBindWorkWechatFromEnv()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::isWorkWechatOAuthConfigured()) {
|
||||
return false;
|
||||
}
|
||||
if ((int) ($adminInfo['root'] ?? 0) === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return trim((string) ($adminInfo['work_wechat_userid'] ?? '')) === '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启「须绑定企微」时,这些 action 仍须放行(与 $request->action() 比较,不区分大小写)。
|
||||
* 路由/网关若把 action 变成全小写,严格 in_array('bindWorkWechat') 会失败,导致绑定接口被误判为 code=10,扫码页反复刷新。
|
||||
*/
|
||||
public static function isWorkWechatBindExemptActionName(string $action): bool
|
||||
{
|
||||
$a = strtolower(trim($action));
|
||||
|
||||
return in_array($a, [
|
||||
'bindworkwechat',
|
||||
'unbindworkwechat',
|
||||
'myself',
|
||||
'logout',
|
||||
], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 管理员账号登录
|
||||
* @param $params
|
||||
@@ -55,15 +132,29 @@ class LoginLogic extends BaseLogic
|
||||
//返回登录信息
|
||||
$avatar = $admin->avatar ? $admin->avatar : Config::get('project.default_image.admin_avatar');
|
||||
$avatar = FileService::getFileUrl($avatar);
|
||||
return [
|
||||
$row = [
|
||||
'name' => $adminInfo['name'],
|
||||
'avatar' => $avatar,
|
||||
'role_name' => $adminInfo['role_name'],
|
||||
'token' => $adminInfo['token'],
|
||||
'is_paw' => $admin->is_paw ?? 1, // 0=需要修改密码,1=正常
|
||||
];
|
||||
$row['need_bind_work_wechat'] = self::adminMustBindWorkWechat([
|
||||
'root' => (int) ($admin->root ?? 0),
|
||||
'work_wechat_userid' => (string) ($admin->work_wechat_userid ?? ''),
|
||||
]);
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 auth/getuserinfo 响应解析企业成员 userid。
|
||||
* 非通讯录成员时接口可能只返回 openid / external_userid,无 userid。
|
||||
*/
|
||||
public static function workWechatUserIdFromAuthResponse(array $response): string
|
||||
{
|
||||
return trim((string) ($response['userid'] ?? $response['UserId'] ?? ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notes 企业微信授权登录
|
||||
@@ -98,9 +189,14 @@ class LoginLogic extends BaseLogic
|
||||
return false;
|
||||
}
|
||||
|
||||
$wxUserId = $response['userid'] ?? '';
|
||||
if (empty($wxUserId)) {
|
||||
self::setError('未获取到企业微信用户身份,可能是外部联系人');
|
||||
$wxUserId = self::workWechatUserIdFromAuthResponse($response);
|
||||
if ($wxUserId === '') {
|
||||
$hasOpenId = trim((string) ($response['openid'] ?? $response['OpenId'] ?? '')) !== '';
|
||||
self::setError(
|
||||
$hasOpenId
|
||||
? '当前身份非企业通讯录成员(或未同步到通讯录),无法用此账号登录后台,请使用企业内成员账号或联系管理员将你加入通讯录'
|
||||
: '未获取到企业微信成员 userid,请检查自建应用 Secret、可信域名是否与当前扫码应用一致'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -135,6 +231,8 @@ class LoginLogic extends BaseLogic
|
||||
'role_name' => $adminInfo['role_name'],
|
||||
'token' => $adminInfo['token'],
|
||||
'is_paw' => $admin->is_paw ?? 1, // 0=需要修改密码,1=正常
|
||||
// 企微扫码登录即已绑定 userid
|
||||
'need_bind_work_wechat' => false,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
namespace app\adminapi\logic\auth;
|
||||
|
||||
use app\adminapi\logic\LoginLogic;
|
||||
use app\common\cache\AdminAuthCache;
|
||||
use app\common\enum\YesNoEnum;
|
||||
use app\common\logic\BaseLogic;
|
||||
@@ -314,6 +315,11 @@ class AdminLogic extends BaseLogic
|
||||
$authRoleIds = AdminRole::where('admin_id', $params['id'])->column('role_id');
|
||||
$admin['role_ids'] = array_values(array_map('intval', $authRoleIds));
|
||||
|
||||
$admin['need_bind_work_wechat'] = LoginLogic::adminMustBindWorkWechat([
|
||||
'root' => (int) ($admin['root'] ?? 0),
|
||||
'work_wechat_userid' => (string) ($admin['work_wechat_userid'] ?? ''),
|
||||
]);
|
||||
|
||||
$result['user'] = $admin;
|
||||
// 当前管理员角色拥有的菜单
|
||||
$result['menu'] = MenuLogic::getMenuByAdminId($params['id']);
|
||||
|
||||
@@ -7,6 +7,9 @@ namespace app\adminapi\logic\order;
|
||||
use app\common\model\Order;
|
||||
use app\common\model\OrderDetail;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
use app\common\model\tcm\PrescriptionOrderPayOrder;
|
||||
use app\common\service\wechat\WechatWorkExternalPayService;
|
||||
use think\facade\Db;
|
||||
use think\facade\Log;
|
||||
@@ -73,7 +76,7 @@ class OrderLogic
|
||||
|
||||
/**
|
||||
* @notes 从企业微信对外收款账单同步订单(员工直接在企业微信发起收款,未经过后台创建)
|
||||
* 拉取 get_bill_list 接口的已支付记录,创建或更新本地订单
|
||||
* 拉取 get_bill_list:trade_state 1=已完成(已支付) 3=已完成有退款;会创建订单,并在状态变化时更新(如已支付→已退款)
|
||||
* @param int $beginTime 开始时间戳(秒)
|
||||
* @param int $endTime 结束时间戳(秒)
|
||||
* @return array ['created' => int, 'updated' => int, 'skipped' => int, 'errors' => string[]]
|
||||
@@ -108,10 +111,13 @@ class OrderLogic
|
||||
|
||||
foreach ($billList as $bill) {
|
||||
$tradeState = (int)($bill['trade_state'] ?? 0);
|
||||
// 1:已完成(已支付) 3:已完成有退款 — 见企业微信 get_bill_list 文档
|
||||
if (!in_array($tradeState, [1, 3], true)) {
|
||||
$result['skipped']++;
|
||||
continue;
|
||||
}
|
||||
$targetStatus = $tradeState === 3 ? 4 : 2; // 4=已退款 2=已支付
|
||||
|
||||
$orderNo = (string)($bill['out_trade_no'] ?? $bill['trade_no'] ?? '');
|
||||
$totalFee = (int)($bill['total_fee'] ?? $bill['total_amount'] ?? 0);
|
||||
$tradeNo = (string)($bill['transaction_id'] ?? $bill['trade_no'] ?? '');
|
||||
@@ -134,34 +140,76 @@ class OrderLogic
|
||||
}
|
||||
|
||||
$payerExternalUserid = (string)($bill['external_userid'] ?? '');
|
||||
$paymentTimeStr = $payTime
|
||||
? (is_numeric($payTime) ? date('Y-m-d H:i:s', (int)$payTime) : (string)$payTime)
|
||||
: date('Y-m-d H:i:s');
|
||||
|
||||
$order = Order::where('order_no', $orderNo)->find();
|
||||
if ($order) {
|
||||
if ($order->status == 1) {
|
||||
$order->status = 2;
|
||||
$order->payment_method = 'wechat';
|
||||
$order->payment_time = $payTime ? (is_numeric($payTime) ? date('Y-m-d H:i:s', (int)$payTime) : $payTime) : date('Y-m-d H:i:s');
|
||||
$needSave = false;
|
||||
$currentStatus = (int)$order->status;
|
||||
|
||||
if ($currentStatus !== $targetStatus) {
|
||||
// 待支付:随账单变为已支付或已退款(含未同步到「已支付」即发生退款)
|
||||
if ($currentStatus === 1) {
|
||||
$order->status = $targetStatus;
|
||||
$needSave = true;
|
||||
} elseif ($currentStatus === 2 && $targetStatus === 4) {
|
||||
$order->status = 4;
|
||||
$needSave = true;
|
||||
} elseif ($currentStatus === 4 && $targetStatus === 2) {
|
||||
// 账单从「有退款」变回「已完成」等异常纠偏
|
||||
$order->status = 2;
|
||||
$needSave = true;
|
||||
}
|
||||
// 已取消(3) 不自动改支付状态,避免覆盖后台取消
|
||||
}
|
||||
|
||||
if ($currentStatus === 1 || $targetStatus === 2) {
|
||||
if ($order->payment_method !== 'wechat') {
|
||||
$order->payment_method = 'wechat';
|
||||
$needSave = true;
|
||||
}
|
||||
if ($paymentTimeStr !== '' && (string)$order->payment_time !== $paymentTimeStr) {
|
||||
$order->payment_time = $paymentTimeStr;
|
||||
$needSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tradeNo !== '' && (string)$order->trade_no !== $tradeNo) {
|
||||
$order->trade_no = $tradeNo;
|
||||
$needSave = true;
|
||||
}
|
||||
|
||||
if (abs((float)$order->amount - $amount) > 0.000001) {
|
||||
$order->amount = $amount;
|
||||
$needSave = true;
|
||||
}
|
||||
|
||||
$defaultCreatorIdInt = (int)$defaultCreatorId;
|
||||
if ($billPayeeUserid && $creatorId !== $defaultCreatorIdInt && (int)$order->creator_id === $defaultCreatorIdInt) {
|
||||
$order->payee_userid = $billPayeeUserid;
|
||||
$order->payer_external_userid = $payerExternalUserid;
|
||||
$order->creator_id = $creatorId;
|
||||
$order->order_type = $orderType;
|
||||
$needSave = true;
|
||||
} elseif ($billPayeeUserid !== '' && (string)$order->payee_userid !== $billPayeeUserid) {
|
||||
$order->payee_userid = $billPayeeUserid;
|
||||
$needSave = true;
|
||||
}
|
||||
if ($payerExternalUserid !== '' && (string)$order->payer_external_userid !== $payerExternalUserid) {
|
||||
$order->payer_external_userid = $payerExternalUserid;
|
||||
$needSave = true;
|
||||
}
|
||||
|
||||
if ($amount == 5.00 && (int)$order->order_type !== 1) {
|
||||
$order->order_type = 1;
|
||||
$needSave = true;
|
||||
}
|
||||
|
||||
if ($needSave) {
|
||||
$order->save();
|
||||
$result['updated']++;
|
||||
} else {
|
||||
$needSave = false;
|
||||
if ($billPayeeUserid && $creatorId !== $defaultCreatorId && (int)$order->creator_id === $defaultCreatorId) {
|
||||
$order->payee_userid = $billPayeeUserid;
|
||||
$order->payer_external_userid = $payerExternalUserid;
|
||||
$order->creator_id = $creatorId;
|
||||
$needSave = true;
|
||||
}
|
||||
if ($amount == 5.00 && (int)$order->order_type !== 1) {
|
||||
$order->order_type = 1;
|
||||
$needSave = true;
|
||||
}
|
||||
if ($needSave) {
|
||||
$order->save();
|
||||
}
|
||||
$result['skipped']++;
|
||||
}
|
||||
} else {
|
||||
@@ -172,11 +220,18 @@ class OrderLogic
|
||||
$order->creator_id = $creatorId;
|
||||
$order->order_type = $orderType;
|
||||
$order->amount = $amount;
|
||||
$order->status = 2;
|
||||
$order->status = $targetStatus;
|
||||
$order->payment_method = 'wechat';
|
||||
$order->payment_time = $payTime ? (is_numeric($payTime) ? date('Y-m-d H:i:s', (int)$payTime) : $payTime) : date('Y-m-d H:i:s');
|
||||
$order->payment_time = $paymentTimeStr;
|
||||
$order->trade_no = $tradeNo;
|
||||
$order->remark = '企业微信直接收款同步,待关联患者';
|
||||
$remark = '企业微信直接收款同步,待关联患者';
|
||||
if ($tradeState === 3) {
|
||||
$refundCent = (int)($bill['total_refund_fee'] ?? 0);
|
||||
if ($refundCent > 0) {
|
||||
$remark .= '(账单含退款' . round($refundCent / 100, 2) . '元)';
|
||||
}
|
||||
}
|
||||
$order->remark = $remark;
|
||||
$order->payee_userid = $billPayeeUserid;
|
||||
$order->payer_external_userid = $payerExternalUserid;
|
||||
$order->save();
|
||||
@@ -652,15 +707,17 @@ class OrderLogic
|
||||
|
||||
/**
|
||||
* @notes 订单统计(按订单类型或退款)
|
||||
* @param array $params order_type(0退款,1挂号费,2问诊费,3药品费用,4首付,5尾款,6其他), days(0今天,7,30)
|
||||
* @param array $params order_type(-1全部已支付类型合计,0退款,1挂号费,2问诊费,3药品费用,4首付,5尾款,6其他), days(0今天,7,30)
|
||||
* @return array
|
||||
*/
|
||||
public static function orderStats(array $params = [])
|
||||
{
|
||||
$orderType = isset($params['order_type']) ? (int)$params['order_type'] : 1;
|
||||
if (!array_key_exists($orderType, self::$orderTypeNames)) {
|
||||
$allPaidOrderTypes = [1, 2, 3, 4, 5, 6];
|
||||
$orderType = array_key_exists('order_type', $params) ? (int) $params['order_type'] : 1;
|
||||
if ($orderType !== -1 && $orderType !== 0 && !array_key_exists($orderType, self::$orderTypeNames)) {
|
||||
$orderType = 1;
|
||||
}
|
||||
$orderTypeName = $orderType === -1 ? '全部(已支付)' : self::$orderTypeNames[$orderType];
|
||||
$days = isset($params['days']) ? (int)$params['days'] : 7;
|
||||
|
||||
$endTime = !empty($params['end_time']) ? strtotime($params['end_time']) : time();
|
||||
@@ -682,6 +739,8 @@ class OrderLogic
|
||||
->whereNull('delete_time');
|
||||
if ($orderType === 0) {
|
||||
$query->where('status', 4);
|
||||
} elseif ($orderType === -1) {
|
||||
$query->whereIn('order_type', $allPaidOrderTypes)->where('status', 2);
|
||||
} else {
|
||||
$query->where('order_type', $orderType)->where('status', 2);
|
||||
}
|
||||
@@ -696,7 +755,7 @@ class OrderLogic
|
||||
if (empty($creatorIds)) {
|
||||
return [
|
||||
'order_type' => $orderType,
|
||||
'order_type_name' => self::$orderTypeNames[$orderType],
|
||||
'order_type_name' => $orderTypeName,
|
||||
'date_range' => [date('Y-m-d', $startTime), date('Y-m-d', $endTime)],
|
||||
'today_total' => 0,
|
||||
'today_amount' => '0.00',
|
||||
@@ -738,6 +797,8 @@ class OrderLogic
|
||||
->whereNull('delete_time');
|
||||
if ($orderType === 0) {
|
||||
$todayQuery->where('status', 4);
|
||||
} elseif ($orderType === -1) {
|
||||
$todayQuery->whereIn('order_type', $allPaidOrderTypes)->where('status', 2);
|
||||
} else {
|
||||
$todayQuery->where('order_type', $orderType)->where('status', 2);
|
||||
}
|
||||
@@ -835,7 +896,7 @@ class OrderLogic
|
||||
|
||||
return [
|
||||
'order_type' => $orderType,
|
||||
'order_type_name' => self::$orderTypeNames[$orderType],
|
||||
'order_type_name' => $orderTypeName,
|
||||
'date_range' => [date('Y-m-d', $startTime), date('Y-m-d', $endTime)],
|
||||
'today_total' => $todayTotal,
|
||||
'today_amount' => number_format($todayAmount, 2, '.', ''),
|
||||
@@ -848,4 +909,129 @@ class OrderLogic
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 与订单列表一致:超管或主管角色可见他人创建的订单
|
||||
*/
|
||||
public static function isOrderListSupervisor(int $adminId, array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$supervisorRoles = [0, 3];
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
|
||||
return count(array_intersect($myRoles, $supervisorRoles)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 某诊单下已支付支付单,供关联业务订单多选(主管/诊单医助看该诊单全部;否则仅本人创建)
|
||||
* 已占用(关联到未撤回/未取消的业务订单)的支付单会排除;编辑某业务订单时传 $exceptPrescriptionOrderId 以保留当前单已选中的项
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public static function listPaidOrdersForDiagnosis(int $diagnosisId, int $adminId, array $adminInfo, ?int $exceptPrescriptionOrderId = null): array
|
||||
{
|
||||
if ($diagnosisId <= 0) {
|
||||
return [];
|
||||
}
|
||||
$exists = Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->count() > 0;
|
||||
if (!$exists) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$q = Order::where('patient_id', $diagnosisId)
|
||||
->where('status', 2)
|
||||
->whereNull('delete_time');
|
||||
|
||||
if (!self::isOrderListSupervisor($adminId, $adminInfo)) {
|
||||
$assistantId = (int) Diagnosis::where('id', $diagnosisId)->value('assistant_id');
|
||||
if ($assistantId !== $adminId) {
|
||||
$q->where('creator_id', $adminId);
|
||||
}
|
||||
}
|
||||
|
||||
$rows = $q
|
||||
->field(['id', 'order_no', 'order_type', 'amount', 'status', 'create_time', 'creator_id', 'remark'])
|
||||
->order('id', 'desc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$activePoIds = PrescriptionOrder::whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->column('id');
|
||||
$busyPayIds = [];
|
||||
if ($activePoIds !== []) {
|
||||
$busyPayIds = PrescriptionOrderPayOrder::whereIn('prescription_order_id', $activePoIds)
|
||||
->column('pay_order_id');
|
||||
$busyPayIds = array_unique(array_map('intval', $busyPayIds));
|
||||
}
|
||||
$whiteList = [];
|
||||
if ($exceptPrescriptionOrderId !== null && $exceptPrescriptionOrderId > 0) {
|
||||
$whiteList = array_map(
|
||||
'intval',
|
||||
PrescriptionOrderPayOrder::where('prescription_order_id', $exceptPrescriptionOrderId)->column('pay_order_id')
|
||||
);
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($rows as $row) {
|
||||
$oid = (int) ($row['id'] ?? 0);
|
||||
if ($oid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$busy = in_array($oid, $busyPayIds, true);
|
||||
$allowed = in_array($oid, $whiteList, true);
|
||||
if ($busy && ! $allowed) {
|
||||
continue;
|
||||
}
|
||||
$out[] = $row;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验支付单均可关联到指定诊单(已支付、归属与权限)
|
||||
*
|
||||
* @param int[] $ids zyt_order.id
|
||||
*/
|
||||
public static function assertPayOrdersLinkable(array $ids, int $diagnosisId, int $adminId, array $adminInfo): ?string
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter(array_map('intval', $ids), static fn (int $v): bool => $v > 0)));
|
||||
if ($diagnosisId <= 0) {
|
||||
return '诊单无效';
|
||||
}
|
||||
if ($ids === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$diagExists = Diagnosis::where('id', $diagnosisId)->whereNull('delete_time')->count() > 0;
|
||||
if (!$diagExists) {
|
||||
return '诊单不存在';
|
||||
}
|
||||
|
||||
$supervisor = self::isOrderListSupervisor($adminId, $adminInfo);
|
||||
$assistantId = (int) Diagnosis::where('id', $diagnosisId)->value('assistant_id');
|
||||
$isDiagAssistant = $assistantId === $adminId;
|
||||
|
||||
$orders = Order::whereIn('id', $ids)->whereNull('delete_time')->select();
|
||||
if (count($orders) !== count($ids)) {
|
||||
return '部分支付单不存在或已删除';
|
||||
}
|
||||
|
||||
foreach ($orders as $o) {
|
||||
if ((int) $o->patient_id !== $diagnosisId) {
|
||||
return '支付单与诊单不匹配';
|
||||
}
|
||||
if ((int) $o->status !== 2) {
|
||||
return '仅能关联已支付订单';
|
||||
}
|
||||
if (! $supervisor && ! $isDiagAssistant && (int) $o->creator_id !== $adminId) {
|
||||
return '无权限关联所选支付单';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2066,7 +2066,57 @@ class DiagnosisLogic extends BaseLogic
|
||||
->order('a.id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
|
||||
if ($assistants === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$adminIds = array_column($assistants, 'id');
|
||||
$adminDepts = \app\common\model\auth\AdminDept::whereIn('admin_id', $adminIds)
|
||||
->select()
|
||||
->toArray();
|
||||
$adminToDeptIds = [];
|
||||
foreach ($adminDepts as $ad) {
|
||||
$aid = (int) ($ad['admin_id'] ?? 0);
|
||||
if ($aid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$adminToDeptIds[$aid][] = (int) ($ad['dept_id'] ?? 0);
|
||||
}
|
||||
$allDeptIds = [];
|
||||
foreach ($adminToDeptIds as $deptIdList) {
|
||||
foreach ($deptIdList as $did) {
|
||||
if ($did > 0) {
|
||||
$allDeptIds[$did] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$allDeptIds = array_keys($allDeptIds);
|
||||
$deptNameMap = [];
|
||||
if ($allDeptIds !== []) {
|
||||
$deptNameMap = \app\common\model\dept\Dept::whereIn('id', $allDeptIds)
|
||||
->whereNull('delete_time')
|
||||
->column('name', 'id');
|
||||
}
|
||||
foreach ($assistants as &$row) {
|
||||
$aid = (int) ($row['id'] ?? 0);
|
||||
$idsForAdmin = [];
|
||||
foreach ($adminToDeptIds[$aid] ?? [] as $did) {
|
||||
if ($did > 0) {
|
||||
$idsForAdmin[] = $did;
|
||||
}
|
||||
}
|
||||
$row['dept_ids'] = array_values(array_unique($idsForAdmin));
|
||||
$names = [];
|
||||
foreach ($row['dept_ids'] as $did) {
|
||||
if (!empty($deptNameMap[$did])) {
|
||||
$names[] = (string) $deptNameMap[$did];
|
||||
}
|
||||
}
|
||||
$row['dept_names'] = $names !== [] ? implode('、', array_unique($names)) : '';
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $assistants;
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('获取医助列表失败: ' . $e->getMessage());
|
||||
@@ -2737,7 +2787,9 @@ class DiagnosisLogic extends BaseLogic
|
||||
'systolic_pressure' => !empty($params['systolic_pressure']) ? intval($params['systolic_pressure']) : null,
|
||||
'diastolic_pressure' => !empty($params['diastolic_pressure']) ? intval($params['diastolic_pressure']) : null,
|
||||
'fasting_blood_sugar' => !empty($params['fasting_blood_sugar']) ? floatval($params['fasting_blood_sugar']) : null,
|
||||
'diabetes_discovery_year' => !empty($params['diabetes_discovery_year']) ? intval($params['diabetes_discovery_year']) : null,
|
||||
'diabetes_discovery_year' => isset($params['diabetes_discovery_year'])
|
||||
? (trim((string) $params['diabetes_discovery_year']) !== '' ? trim((string) $params['diabetes_discovery_year']) : null)
|
||||
: null,
|
||||
'local_hospital_diagnosis' => $params['local_hospital_diagnosis'] ?? '',
|
||||
'local_hospital_name' => $params['local_hospital_name'] ?? '',
|
||||
'local_hospital_visit_date' => $params['local_hospital_visit_date'] ?? '',
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace app\adminapi\logic\tcm;
|
||||
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\tcm\Prescription;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use app\common\service\wechat\WechatWorkAppMessageService;
|
||||
use think\facade\Config;
|
||||
@@ -82,6 +83,11 @@ class PrescriptionLogic
|
||||
return true;
|
||||
}
|
||||
|
||||
$assistantId = is_array($row) ? (int) ($row['assistant_id'] ?? 0) : (int) ($row->assistant_id ?? 0);
|
||||
if ($assistantId > 0 && $assistantId === $adminId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$isShared = is_array($row) ? (int) ($row['is_shared'] ?? 0) : (int) $row->is_shared;
|
||||
if ($isShared === 1) {
|
||||
return true;
|
||||
@@ -187,6 +193,12 @@ class PrescriptionLogic
|
||||
$sn = self::generateSn();
|
||||
}
|
||||
|
||||
$assistantIdForRx = 0;
|
||||
if ($diagnosisIdRule > 0) {
|
||||
$diagAssistant = Diagnosis::where('id', $diagnosisIdRule)->value('assistant_id');
|
||||
$assistantIdForRx = (int) ($diagAssistant ?? 0);
|
||||
}
|
||||
|
||||
$data = [
|
||||
'sn' => $sn,
|
||||
'prescription_name' => $params['prescription_name'] ?? '',
|
||||
@@ -228,11 +240,13 @@ class PrescriptionLogic
|
||||
'audit_by_name' => '',
|
||||
'audit_remark' => '',
|
||||
'creator_id' => $adminId,
|
||||
'assistant_id' => $assistantIdForRx,
|
||||
];
|
||||
|
||||
$prescription = new Prescription();
|
||||
$prescription->save($data);
|
||||
return (int)$prescription->id;
|
||||
|
||||
return (int) $prescription->id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,10 +261,13 @@ class PrescriptionLogic
|
||||
return false;
|
||||
}
|
||||
|
||||
// 已通过且未作废:不可编辑(与消费者端仅「查看」一致)
|
||||
// 已通过且未作废:不可编辑;例外:业务订单「处方审核」已驳回时允许医生改方,保存后业务订单侧审核会重置为待审
|
||||
if ((int) ($prescription->audit_status ?? 1) === 1 && (int) ($prescription->void_status ?? 0) === 0) {
|
||||
self::setError('该处方已通过审核,不可编辑');
|
||||
return false;
|
||||
if (!PrescriptionOrderLogic::allowDoctorEditApprovedPrescriptionDueToBusinessReject((int) $params['id'])) {
|
||||
self::setError('该处方已通过审核,不可编辑');
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 已驳回:仅当该诊单+当天+同一创建人下没有另一条「已通过且未作废」的处方时允许重新编辑(改后待审核、作废一并清除)
|
||||
@@ -300,8 +317,17 @@ class PrescriptionLogic
|
||||
|
||||
$wasVoid = (int) ($prescription->void_status ?? 0) === 1;
|
||||
|
||||
$assistantIdForRx = (int) ($prescription->assistant_id ?? 0);
|
||||
if ($newDiagnosisId > 0) {
|
||||
$diagAssistant = Diagnosis::where('id', $newDiagnosisId)->value('assistant_id');
|
||||
$assistantIdForRx = (int) ($diagAssistant ?? 0);
|
||||
} else {
|
||||
$assistantIdForRx = 0;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'diagnosis_id' => $newDiagnosisId,
|
||||
'assistant_id' => $assistantIdForRx,
|
||||
'prescription_name' => $params['prescription_name'] ?? $prescription->prescription_name,
|
||||
'prescription_type' => $params['prescription_type'] ?? $prescription->prescription_type,
|
||||
'patient_name' => $params['patient_name'] ?? $prescription->patient_name,
|
||||
@@ -344,6 +370,8 @@ class PrescriptionLogic
|
||||
}
|
||||
|
||||
$prescription->save($data);
|
||||
PrescriptionOrderLogic::onConsumerPrescriptionSaved((int) $params['id']);
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
@@ -395,6 +423,15 @@ class PrescriptionLogic
|
||||
$arr['gender_desc'] = $arr['gender'] == 1 ? '男' : '女';
|
||||
$arr['visible_role_ids'] = self::visibleRoleIdsToArray($arr['visible_role_ids'] ?? '');
|
||||
|
||||
$bizPo = PrescriptionOrder::where('prescription_id', $id)
|
||||
->where('prescription_audit_status', 2)
|
||||
->whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
$arr['business_prescription_audit_rejected'] = $bizPo ? 1 : 0;
|
||||
$arr['business_prescription_audit_remark'] = $bizPo ? (string) ($bizPo->prescription_audit_remark ?? '') : '';
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,780 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\tcm;
|
||||
|
||||
use app\adminapi\logic\order\OrderLogic;
|
||||
use app\common\model\Order;
|
||||
use app\common\model\tcm\Diagnosis;
|
||||
use app\common\model\tcm\PrescriptionOrder;
|
||||
use app\common\model\tcm\PrescriptionOrderPayOrder;
|
||||
use app\common\service\ExpressTrackService;
|
||||
use think\facade\Config;
|
||||
|
||||
class PrescriptionOrderLogic
|
||||
{
|
||||
private static string $error = '';
|
||||
|
||||
public static function setError(string $msg): void
|
||||
{
|
||||
self::$error = $msg;
|
||||
}
|
||||
|
||||
public static function getError(): string
|
||||
{
|
||||
return self::$error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $allowRoleIds
|
||||
*/
|
||||
private static function roleIntersect(array $adminInfo, array $allowRoleIds): bool
|
||||
{
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
$allow = array_map('intval', $allowRoleIds);
|
||||
|
||||
return count(array_intersect($myRoles, $allow)) > 0;
|
||||
}
|
||||
|
||||
/** 与 order 列表一致:超管或 order_edit_all_roles 可见全部业务订单 */
|
||||
public static function canSeeAllPrescriptionOrders(array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$roles = Config::get('project.order_edit_all_roles', [0, 3]);
|
||||
|
||||
return self::roleIntersect($adminInfo, is_array($roles) ? $roles : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PrescriptionOrder $row
|
||||
*/
|
||||
public static function canAccessOrder($row, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
if (self::canSeeAllPrescriptionOrders($adminInfo)) {
|
||||
return true;
|
||||
}
|
||||
if ((int) $row->creator_id === $adminId) {
|
||||
return true;
|
||||
}
|
||||
$aid = (int) Diagnosis::where('id', (int) $row->diagnosis_id)->whereNull('delete_time')->value('assistant_id');
|
||||
|
||||
return $aid === $adminId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PrescriptionOrder $row
|
||||
*/
|
||||
public static function canWithdrawOrder($row, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
return self::canAccessOrder($row, $adminId, $adminInfo);
|
||||
}
|
||||
|
||||
public static function canViewInternalCost(array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$allow = Config::get('project.prescription_order_finance_roles', [0, 3]);
|
||||
$allow = array_map('intval', is_array($allow) ? $allow : []);
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
|
||||
return count(array_intersect($myRoles, $allow)) > 0;
|
||||
}
|
||||
|
||||
public static function canAuditPrescriptionOrder(array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$allow = Config::get('project.prescription_audit_roles', [0, 3, 6]);
|
||||
|
||||
return self::roleIntersect($adminInfo, is_array($allow) ? $allow : []);
|
||||
}
|
||||
|
||||
public static function canAuditPaymentSlipOrder(array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$allow = Config::get('project.prescription_order_payment_audit_roles', [0, 3]);
|
||||
|
||||
return self::roleIntersect($adminInfo, is_array($allow) ? $allow : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $row
|
||||
*/
|
||||
public static function maskInternalCostIfNeeded(array &$row, array $adminInfo): void
|
||||
{
|
||||
if (!self::canViewInternalCost($adminInfo)) {
|
||||
unset($row['internal_cost']);
|
||||
}
|
||||
}
|
||||
|
||||
public static function generateOrderNo(): string
|
||||
{
|
||||
return 'PO' . date('YmdHis') . mt_rand(100000, 999999);
|
||||
}
|
||||
|
||||
private static function normalizeExpressCompany($raw): string
|
||||
{
|
||||
$ec = strtolower(trim((string) ($raw ?? '')));
|
||||
if ($ec === '') {
|
||||
return 'auto';
|
||||
}
|
||||
if (!in_array($ec, ['sf', 'jd', 'jt', 'jtexpress', 'auto'], true)) {
|
||||
return 'auto';
|
||||
}
|
||||
// 统一 jtexpress 为 jt
|
||||
if ($ec === 'jtexpress') {
|
||||
return 'jt';
|
||||
}
|
||||
|
||||
return $ec;
|
||||
}
|
||||
|
||||
/** 业务订单「处方审核」驳回后,允许医生继续编辑已通过的消费者处方 */
|
||||
public static function allowDoctorEditApprovedPrescriptionDueToBusinessReject(int $prescriptionId): bool
|
||||
{
|
||||
return PrescriptionOrder::where('prescription_id', $prescriptionId)
|
||||
->where('prescription_audit_status', 2)
|
||||
->whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->count() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 医生保存消费者处方后:重置关联业务订单的处方/支付单审核为待审(因处方内容已变)
|
||||
*/
|
||||
public static function onConsumerPrescriptionSaved(int $prescriptionId): void
|
||||
{
|
||||
$rows = PrescriptionOrder::where('prescription_id', $prescriptionId)
|
||||
->where('prescription_audit_status', 2)
|
||||
->whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->select();
|
||||
foreach ($rows as $order) {
|
||||
$order->prescription_audit_status = 0;
|
||||
$order->prescription_audit_remark = '';
|
||||
$order->payment_slip_audit_status = 0;
|
||||
$order->payment_slip_audit_remark = '';
|
||||
self::syncFulfillmentStatus($order);
|
||||
$order->save();
|
||||
}
|
||||
}
|
||||
|
||||
private static function syncFulfillmentStatus(PrescriptionOrder $order): void
|
||||
{
|
||||
$fs = (int) $order->fulfillment_status;
|
||||
if ($fs === 3 || $fs === 4) {
|
||||
return;
|
||||
}
|
||||
$pa = (int) $order->prescription_audit_status;
|
||||
$pay = (int) $order->payment_slip_audit_status;
|
||||
if ($pa === 1 && $pay === 1) {
|
||||
$order->fulfillment_status = 2;
|
||||
} elseif ($pa === 2 || $pay === 2) {
|
||||
$order->fulfillment_status = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $raw
|
||||
* @return int[]
|
||||
*/
|
||||
private static function normalizePayOrderIds($raw): array
|
||||
{
|
||||
if (!is_array($raw)) {
|
||||
return [];
|
||||
}
|
||||
$ids = array_map('intval', $raw);
|
||||
|
||||
return array_values(array_unique(array_filter($ids, static fn (int $v): bool => $v > 0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除业务订单与支付单的关联(撤回/驳回后支付单可再次被选用)
|
||||
*/
|
||||
private static function clearPayOrderLinks(PrescriptionOrder $order): void
|
||||
{
|
||||
PrescriptionOrderPayOrder::where('prescription_order_id', (int) $order->id)->delete();
|
||||
$order->linked_pay_order_id = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付单不可同时关联多条「有效」业务订单(未删除且未撤回/取消);$exceptPrescriptionOrderId 为当前编辑单 ID 时跳过自检
|
||||
*
|
||||
* @param int[] $payOrderIds
|
||||
*/
|
||||
private static function assertPayOrdersExclusive(array $payOrderIds, ?int $exceptPrescriptionOrderId): ?string
|
||||
{
|
||||
$ids = array_values(array_unique(array_filter(array_map('intval', $payOrderIds), static fn (int $v): bool => $v > 0)));
|
||||
if ($ids === []) {
|
||||
return null;
|
||||
}
|
||||
$activePoIds = PrescriptionOrder::whereNull('delete_time')
|
||||
->where('fulfillment_status', '<>', 4)
|
||||
->column('id');
|
||||
if ($activePoIds === []) {
|
||||
return null;
|
||||
}
|
||||
$activeSet = array_fill_keys(array_map('intval', $activePoIds), true);
|
||||
$links = PrescriptionOrderPayOrder::whereIn('pay_order_id', $ids)->select();
|
||||
foreach ($links as $link) {
|
||||
$poId = (int) $link->prescription_order_id;
|
||||
if ($exceptPrescriptionOrderId !== null && $poId === $exceptPrescriptionOrderId) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($activeSet[$poId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return '支付单 #' . (int) $link->pay_order_id . ' 已关联其他有效业务订单,不可重复关联';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int[] $payOrderIds
|
||||
*/
|
||||
private static function replacePayOrderLinks(int $prescriptionOrderId, array $payOrderIds): void
|
||||
{
|
||||
PrescriptionOrderPayOrder::where('prescription_order_id', $prescriptionOrderId)->delete();
|
||||
$t = time();
|
||||
foreach ($payOrderIds as $pid) {
|
||||
$link = new PrescriptionOrderPayOrder();
|
||||
$link->prescription_order_id = $prescriptionOrderId;
|
||||
$link->pay_order_id = $pid;
|
||||
$link->create_time = $t;
|
||||
$link->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private static function linkedPayOrderIdList(int $prescriptionOrderId): array
|
||||
{
|
||||
return array_map(
|
||||
'intval',
|
||||
PrescriptionOrderPayOrder::where('prescription_order_id', $prescriptionOrderId)->order('id', 'asc')->column('pay_order_id')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
private static function linkedPayOrdersPayload(array $ids): array
|
||||
{
|
||||
if ($ids === []) {
|
||||
return [];
|
||||
}
|
||||
$rows = Order::whereIn('id', $ids)->whereNull('delete_time')
|
||||
->field(['id', 'order_no', 'order_type', 'amount', 'status', 'create_time', 'creator_id', 'remark'])
|
||||
->order('id', 'asc')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $arr
|
||||
*/
|
||||
private static function attachLinkedPayOrders(array &$arr): void
|
||||
{
|
||||
$id = (int) ($arr['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
$arr['pay_order_ids'] = [];
|
||||
$arr['linked_pay_orders'] = [];
|
||||
|
||||
return;
|
||||
}
|
||||
$ids = self::linkedPayOrderIdList($id);
|
||||
$arr['pay_order_ids'] = $ids;
|
||||
$arr['linked_pay_orders'] = self::linkedPayOrdersPayload($ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $params
|
||||
* @return array<string,mixed>|false
|
||||
*/
|
||||
public static function create(array $params, int $adminId, array $adminInfo)
|
||||
{
|
||||
self::$error = '';
|
||||
$rxId = (int) $params['prescription_id'];
|
||||
$diagId = (int) $params['diagnosis_id'];
|
||||
$payOrderIds = self::normalizePayOrderIds($params['pay_order_ids'] ?? []);
|
||||
|
||||
$rx = PrescriptionLogic::detail($rxId, $adminId, $adminInfo);
|
||||
if ($rx === null) {
|
||||
$err = PrescriptionLogic::getError();
|
||||
|
||||
self::$error = $err !== '' ? $err : '处方不存在';
|
||||
|
||||
return false;
|
||||
}
|
||||
if ((int) ($rx['diagnosis_id'] ?? 0) !== $diagId) {
|
||||
self::$error = '诊单与处方不匹配';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PrescriptionOrder::where('prescription_id', $rxId)->whereNull('delete_time')->where('fulfillment_status', '<>', 4)->count() > 0) {
|
||||
self::$error = '该处方已存在有效业务订单(未撤回前不可重复创建)';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$diag = Diagnosis::where('id', $diagId)->whereNull('delete_time')->find();
|
||||
if (!$diag) {
|
||||
self::$error = '诊单不存在';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($payOrderIds !== []) {
|
||||
$linkErr = OrderLogic::assertPayOrdersLinkable($payOrderIds, $diagId, $adminId, $adminInfo);
|
||||
if ($linkErr !== null) {
|
||||
self::$error = $linkErr;
|
||||
|
||||
return false;
|
||||
}
|
||||
$exErr = self::assertPayOrdersExclusive($payOrderIds, null);
|
||||
if ($exErr !== null) {
|
||||
self::$error = $exErr;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$internalCost = null;
|
||||
if (isset($params['internal_cost']) && $params['internal_cost'] !== '' && $params['internal_cost'] !== null) {
|
||||
if (self::canViewInternalCost($adminInfo)) {
|
||||
$internalCost = round((float) $params['internal_cost'], 2);
|
||||
}
|
||||
}
|
||||
|
||||
$medDays = $params['medication_days'] ?? null;
|
||||
$medDays = $medDays === '' || $medDays === null ? null : (int) $medDays;
|
||||
|
||||
$order = new PrescriptionOrder();
|
||||
$order->order_no = self::generateOrderNo();
|
||||
$order->prescription_id = $rxId;
|
||||
$order->diagnosis_id = $diagId;
|
||||
$order->creator_id = $adminId;
|
||||
$order->recipient_name = (string) $params['recipient_name'];
|
||||
$order->recipient_phone = (string) $params['recipient_phone'];
|
||||
$order->shipping_address = (string) $params['shipping_address'];
|
||||
$order->is_follow_up = (int) ($params['is_follow_up'] ?? 0) === 1 ? 1 : 0;
|
||||
$order->medication_days = $medDays > 0 ? $medDays : null;
|
||||
$order->prev_staff = (string) ($params['prev_staff'] ?? '');
|
||||
$order->service_channel = (string) ($params['service_channel'] ?? '');
|
||||
$order->service_package = (string) ($params['service_package'] ?? '');
|
||||
$order->tracking_number = (string) ($params['tracking_number'] ?? '');
|
||||
$order->express_company = self::normalizeExpressCompany($params['express_company'] ?? 'auto');
|
||||
$order->fee_type = (int) $params['fee_type'];
|
||||
$order->amount = round((float) $params['amount'], 2);
|
||||
$order->internal_cost = $internalCost;
|
||||
$order->remark_extra = (string) ($params['remark_extra'] ?? '');
|
||||
$order->linked_pay_order_id = $payOrderIds !== [] ? $payOrderIds[0] : null;
|
||||
$order->prescription_audit_status = 0;
|
||||
$order->prescription_audit_remark = '';
|
||||
$order->payment_slip_audit_status = 0;
|
||||
$order->payment_slip_audit_remark = '';
|
||||
$order->fulfillment_status = 1;
|
||||
|
||||
try {
|
||||
$order->save();
|
||||
} catch (\Throwable $e) {
|
||||
self::$error = $e->getMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($payOrderIds !== []) {
|
||||
self::replacePayOrderLinks((int) $order->id, $payOrderIds);
|
||||
}
|
||||
|
||||
$out = $order->toArray();
|
||||
self::maskInternalCostIfNeeded($out, $adminInfo);
|
||||
self::attachLinkedPayOrders($out);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function detail(int $id, int $adminId, array $adminInfo): ?array
|
||||
{
|
||||
self::$error = '';
|
||||
$row = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find();
|
||||
if (!$row) {
|
||||
self::$error = '订单不存在';
|
||||
|
||||
return null;
|
||||
}
|
||||
if (!self::canAccessOrder($row, $adminId, $adminInfo)) {
|
||||
self::$error = '无权限查看';
|
||||
|
||||
return null;
|
||||
}
|
||||
$arr = $row->toArray();
|
||||
self::maskInternalCostIfNeeded($arr, $adminInfo);
|
||||
self::attachLinkedPayOrders($arr);
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* 物流轨迹(顺丰/京东等,优先快递100;未配置密钥时返回官网链接)
|
||||
*
|
||||
* @return array<string,mixed>|null
|
||||
*/
|
||||
public static function logisticsTrace(int $id, string $expressCompanyOverride, int $adminId, array $adminInfo): ?array
|
||||
{
|
||||
self::$error = '';
|
||||
$row = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find();
|
||||
if (!$row) {
|
||||
self::$error = '订单不存在';
|
||||
|
||||
return null;
|
||||
}
|
||||
if (!self::canAccessOrder($row, $adminId, $adminInfo)) {
|
||||
self::$error = '无权限查看';
|
||||
|
||||
return null;
|
||||
}
|
||||
$num = trim((string) ($row->tracking_number ?? ''));
|
||||
if ($num === '') {
|
||||
self::$error = '未填写快递单号';
|
||||
|
||||
return null;
|
||||
}
|
||||
$ec = trim($expressCompanyOverride) !== '' ? self::normalizeExpressCompany($expressCompanyOverride) : self::normalizeExpressCompany($row->express_company ?? 'auto');
|
||||
|
||||
// 优先从数据库查询物流追踪信息
|
||||
$trackingData = \app\common\service\ExpressTrackingService::getDetailByTrackingNumber($num);
|
||||
|
||||
if ($trackingData && !empty($trackingData['traces'])) {
|
||||
// 从数据库获取到数据,直接返回
|
||||
$payload = [
|
||||
'carrier' => $trackingData['express_company'],
|
||||
'carrier_label' => $trackingData['express_company_name'],
|
||||
'kuaidi_com' => $trackingData['express_company'],
|
||||
'traces' => array_map(function($trace) {
|
||||
return [
|
||||
'time' => $trace['trace_time'],
|
||||
'ftime' => $trace['trace_time'],
|
||||
'context' => $trace['trace_context'],
|
||||
'location' => $trace['location'],
|
||||
'status' => $trace['status'],
|
||||
'statusCode' => $trace['status_code'],
|
||||
];
|
||||
}, $trackingData['traces']),
|
||||
'state' => $trackingData['current_state'],
|
||||
'state_text' => $trackingData['current_state_text'],
|
||||
'source' => 'database',
|
||||
'hint' => '',
|
||||
'official_url' => '',
|
||||
'last_query_time' => date('Y-m-d H:i:s', $trackingData['last_query_time']),
|
||||
'query_count' => $trackingData['query_count'],
|
||||
];
|
||||
} else {
|
||||
// 数据库没有数据,调用快递100 API
|
||||
$payload = ExpressTrackService::query($ec, $num, (string) ($row->recipient_phone ?? ''));
|
||||
$payload['source'] = 'api';
|
||||
}
|
||||
|
||||
$payload['official_urls'] = ExpressTrackService::officialUrls($num);
|
||||
$payload['tracking_number'] = $num;
|
||||
$payload['express_company_used'] = $ec;
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,mixed> $params
|
||||
* @return array<string,mixed>|false
|
||||
*/
|
||||
public static function edit(array $params, int $adminId, array $adminInfo)
|
||||
{
|
||||
self::$error = '';
|
||||
$id = (int) $params['id'];
|
||||
$order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find();
|
||||
if (!$order) {
|
||||
self::$error = '订单不存在';
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!self::canAccessOrder($order, $adminId, $adminInfo)) {
|
||||
self::$error = '无权限编辑';
|
||||
|
||||
return false;
|
||||
}
|
||||
$fs = (int) $order->fulfillment_status;
|
||||
if ($fs === 3 || $fs === 4) {
|
||||
self::$error = '已完成或已取消的订单不可编辑';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$medDays = $params['medication_days'] ?? null;
|
||||
$medDays = $medDays === '' || $medDays === null ? null : (int) $medDays;
|
||||
|
||||
$order->recipient_name = (string) $params['recipient_name'];
|
||||
$order->recipient_phone = (string) $params['recipient_phone'];
|
||||
$order->shipping_address = (string) $params['shipping_address'];
|
||||
$order->is_follow_up = (int) ($params['is_follow_up'] ?? 0) === 1 ? 1 : 0;
|
||||
$order->medication_days = $medDays > 0 ? $medDays : null;
|
||||
$order->prev_staff = (string) ($params['prev_staff'] ?? '');
|
||||
$order->service_channel = (string) ($params['service_channel'] ?? '');
|
||||
$order->service_package = (string) ($params['service_package'] ?? '');
|
||||
$order->tracking_number = (string) ($params['tracking_number'] ?? '');
|
||||
if (array_key_exists('express_company', $params)) {
|
||||
$order->express_company = self::normalizeExpressCompany($params['express_company']);
|
||||
}
|
||||
$order->fee_type = (int) $params['fee_type'];
|
||||
$order->amount = round((float) $params['amount'], 2);
|
||||
$order->remark_extra = (string) ($params['remark_extra'] ?? '');
|
||||
|
||||
$diagId = (int) $order->diagnosis_id;
|
||||
|
||||
if (array_key_exists('pay_order_ids', $params)) {
|
||||
$payOrderIds = self::normalizePayOrderIds($params['pay_order_ids']);
|
||||
$linkErr = OrderLogic::assertPayOrdersLinkable($payOrderIds, $diagId, $adminId, $adminInfo);
|
||||
if ($linkErr !== null) {
|
||||
self::$error = $linkErr;
|
||||
|
||||
return false;
|
||||
}
|
||||
$exErr = self::assertPayOrdersExclusive($payOrderIds, (int) $order->id);
|
||||
if ($exErr !== null) {
|
||||
self::$error = $exErr;
|
||||
|
||||
return false;
|
||||
}
|
||||
self::replacePayOrderLinks((int) $order->id, $payOrderIds);
|
||||
$order->linked_pay_order_id = $payOrderIds !== [] ? $payOrderIds[0] : null;
|
||||
} elseif (array_key_exists('linked_pay_order_id', $params)) {
|
||||
$lp = $params['linked_pay_order_id'];
|
||||
if ($lp === '' || $lp === null || (int) $lp <= 0) {
|
||||
self::replacePayOrderLinks((int) $order->id, []);
|
||||
$order->linked_pay_order_id = null;
|
||||
} else {
|
||||
$single = [(int) $lp];
|
||||
$linkErr = OrderLogic::assertPayOrdersLinkable($single, $diagId, $adminId, $adminInfo);
|
||||
if ($linkErr !== null) {
|
||||
self::$error = $linkErr;
|
||||
|
||||
return false;
|
||||
}
|
||||
$exErr = self::assertPayOrdersExclusive($single, (int) $order->id);
|
||||
if ($exErr !== null) {
|
||||
self::$error = $exErr;
|
||||
|
||||
return false;
|
||||
}
|
||||
self::replacePayOrderLinks((int) $order->id, $single);
|
||||
$order->linked_pay_order_id = $single[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('internal_cost', $params) && self::canViewInternalCost($adminInfo)) {
|
||||
$raw = $params['internal_cost'];
|
||||
if ($raw === '' || $raw === null) {
|
||||
$order->internal_cost = null;
|
||||
} else {
|
||||
$order->internal_cost = round((float) $raw, 2);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$order->save();
|
||||
} catch (\Throwable $e) {
|
||||
self::$error = $e->getMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = $order->toArray();
|
||||
self::maskInternalCostIfNeeded($out, $adminInfo);
|
||||
self::attachLinkedPayOrders($out);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>|false
|
||||
*/
|
||||
public static function withdraw(int $id, int $adminId, array $adminInfo)
|
||||
{
|
||||
self::$error = '';
|
||||
$order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find();
|
||||
if (!$order) {
|
||||
self::$error = '订单不存在';
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!self::canWithdrawOrder($order, $adminId, $adminInfo)) {
|
||||
self::$error = '无权限撤回';
|
||||
|
||||
return false;
|
||||
}
|
||||
if ((int) $order->fulfillment_status !== 1) {
|
||||
self::$error = '仅「待双审通过」状态可撤回;双审通过后请走履约/完成流程';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$order->fulfillment_status = 4;
|
||||
self::clearPayOrderLinks($order);
|
||||
|
||||
try {
|
||||
$order->save();
|
||||
} catch (\Throwable $e) {
|
||||
self::$error = $e->getMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = $order->toArray();
|
||||
self::maskInternalCostIfNeeded($out, $adminInfo);
|
||||
self::attachLinkedPayOrders($out);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>|false
|
||||
*/
|
||||
public static function auditPrescription(int $id, string $action, string $remark, int $adminId, array $adminInfo)
|
||||
{
|
||||
self::$error = '';
|
||||
$order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find();
|
||||
if (!$order) {
|
||||
self::$error = '订单不存在';
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!self::canAuditPrescriptionOrder($adminInfo)) {
|
||||
self::$error = '无处方审核权限';
|
||||
|
||||
return false;
|
||||
}
|
||||
if ((int) $order->prescription_audit_status !== 0) {
|
||||
self::$error = '当前无需处方审核';
|
||||
|
||||
return false;
|
||||
}
|
||||
$fs = (int) $order->fulfillment_status;
|
||||
if ($fs === 3 || $fs === 4) {
|
||||
self::$error = '订单已结束';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($action === 'reject' && trim($remark) === '') {
|
||||
self::$error = '驳回时请填写审核意见';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($action === 'approve') {
|
||||
$order->prescription_audit_status = 1;
|
||||
$order->prescription_audit_remark = mb_substr(trim($remark), 0, 500);
|
||||
} else {
|
||||
$order->prescription_audit_status = 2;
|
||||
$order->prescription_audit_remark = mb_substr(trim($remark), 0, 500);
|
||||
$order->payment_slip_audit_status = 0;
|
||||
$order->payment_slip_audit_remark = '';
|
||||
}
|
||||
self::syncFulfillmentStatus($order);
|
||||
if ($action === 'reject') {
|
||||
self::clearPayOrderLinks($order);
|
||||
}
|
||||
|
||||
try {
|
||||
$order->save();
|
||||
} catch (\Throwable $e) {
|
||||
self::$error = $e->getMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = $order->toArray();
|
||||
self::maskInternalCostIfNeeded($out, $adminInfo);
|
||||
self::attachLinkedPayOrders($out);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>|false
|
||||
*/
|
||||
public static function auditPaymentSlip(int $id, string $action, string $remark, int $adminId, array $adminInfo)
|
||||
{
|
||||
self::$error = '';
|
||||
$order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find();
|
||||
if (!$order) {
|
||||
self::$error = '订单不存在';
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!self::canAuditPaymentSlipOrder($adminInfo)) {
|
||||
self::$error = '无支付单审核权限';
|
||||
|
||||
return false;
|
||||
}
|
||||
if ((int) $order->prescription_audit_status !== 1) {
|
||||
self::$error = '请先通过处方审核';
|
||||
|
||||
return false;
|
||||
}
|
||||
if ((int) $order->payment_slip_audit_status !== 0) {
|
||||
self::$error = '当前无需支付单审核';
|
||||
|
||||
return false;
|
||||
}
|
||||
$fs = (int) $order->fulfillment_status;
|
||||
if ($fs === 3 || $fs === 4) {
|
||||
self::$error = '订单已结束';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($action === 'reject' && trim($remark) === '') {
|
||||
self::$error = '驳回时请填写审核意见';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($action === 'approve') {
|
||||
$order->payment_slip_audit_status = 1;
|
||||
$order->payment_slip_audit_remark = mb_substr(trim($remark), 0, 500);
|
||||
} else {
|
||||
$order->payment_slip_audit_status = 2;
|
||||
$order->payment_slip_audit_remark = mb_substr(trim($remark), 0, 500);
|
||||
}
|
||||
self::syncFulfillmentStatus($order);
|
||||
if ($action === 'reject') {
|
||||
self::clearPayOrderLinks($order);
|
||||
}
|
||||
|
||||
try {
|
||||
$order->save();
|
||||
} catch (\Throwable $e) {
|
||||
self::$error = $e->getMessage();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = $order->toArray();
|
||||
self::maskInternalCostIfNeeded($out, $adminInfo);
|
||||
self::attachLinkedPayOrders($out);
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ class DiagnosisValidate extends BaseValidate
|
||||
'current_medications' => 'max:2000',
|
||||
'tongue_images' => 'array',
|
||||
'report_files' => 'array',
|
||||
'diabetes_discovery_year' => 'max:50',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
@@ -53,6 +54,7 @@ class DiagnosisValidate extends BaseValidate
|
||||
'diagnosis_type.require' => '请选择诊断类型',
|
||||
'status.in' => '状态参数错误',
|
||||
'current_medications.max' => '在用药物最多2000个字符',
|
||||
'diabetes_discovery_year.max' => '发现糖尿病患病史最多50个字符',
|
||||
];
|
||||
|
||||
public function sceneAdd()
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\validate\tcm;
|
||||
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
class PrescriptionOrderValidate extends BaseValidate
|
||||
{
|
||||
protected $rule = [
|
||||
'id' => 'require|integer',
|
||||
'diagnosis_id' => 'require|integer|gt:0',
|
||||
'prescription_id' => 'require|integer',
|
||||
'pay_order_ids' => 'array',
|
||||
'recipient_name' => 'require|max:50',
|
||||
'recipient_phone' => 'require|max:20',
|
||||
'shipping_address' => 'require|max:500',
|
||||
'is_follow_up' => 'in:0,1',
|
||||
'prev_staff' => 'max:100',
|
||||
'service_channel' => 'max:100',
|
||||
'service_package' => 'max:100',
|
||||
'tracking_number' => 'max:80',
|
||||
'express_company' => 'max:20',
|
||||
'fee_type' => 'require|in:1,2,3,4,5,6,7',
|
||||
'amount' => 'require|float',
|
||||
'remark_extra' => 'max:500',
|
||||
'action' => 'require|in:approve,reject',
|
||||
'remark' => 'max:500',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'prescription_id.require' => '请选择处方',
|
||||
'diagnosis_id.require' => '请选择诊单',
|
||||
'recipient_name.require' => '请输入收货人',
|
||||
'recipient_phone.require' => '请输入收货手机',
|
||||
'shipping_address.require' => '请输入收货地址',
|
||||
'fee_type.require' => '请选择费用类别',
|
||||
'amount.require' => '请输入订单金额',
|
||||
'action.require' => '请选择审核操作',
|
||||
];
|
||||
|
||||
protected $scene = [
|
||||
'create' => [
|
||||
'prescription_id', 'diagnosis_id', 'recipient_name', 'recipient_phone', 'shipping_address',
|
||||
'is_follow_up', 'prev_staff', 'service_channel', 'service_package',
|
||||
'tracking_number', 'express_company', 'fee_type', 'amount', 'remark_extra', 'pay_order_ids',
|
||||
],
|
||||
'detail' => ['id'],
|
||||
'edit' => [
|
||||
'id', 'recipient_name', 'recipient_phone', 'shipping_address',
|
||||
'is_follow_up', 'prev_staff', 'service_channel', 'service_package',
|
||||
'tracking_number', 'express_company', 'fee_type', 'amount', 'remark_extra', 'pay_order_ids',
|
||||
],
|
||||
'logisticsTrace' => ['id'],
|
||||
'auditPrescription' => ['id', 'action', 'remark'],
|
||||
'auditPayment' => ['id', 'action', 'remark'],
|
||||
'withdraw' => ['id'],
|
||||
'paidPayOrders' => ['diagnosis_id'],
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user