新增
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user