This commit is contained in:
Your Name
2026-04-10 14:43:58 +08:00
parent fdf714f833
commit 4909ec6daa
38 changed files with 6549 additions and 735 deletions
+3
View File
@@ -41,6 +41,9 @@ UNIQUE_IDENTIFICATION = likeadmin
# 演示环境
DEMO_ENV = false
; 处方业务订单:关联收款每笔金额须大于等于该值(元),业务订单金额也须大于等于该值;0=不校验
PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT = 0
# 物流轨迹(快递100):https://api.kuaidi100.com
; ThinkPHP .env 为 INI:下列建议写在分区声明之前;若误写在 trtc 分区内,config 已兼容
# 同时填 CUSTOMER、KEY 即启用;临时关闭设 LOGISTICS_KUAIDI100_DISABLE = true
@@ -68,6 +68,7 @@ class PrescriptionController extends BaseAdminController
{
$params = (new PrescriptionValidate())->get()->goCheck('detail');
$detail = PrescriptionLogic::detail((int) $params['id'], (int) $this->adminId, $this->adminInfo);
if (!$detail) {
$msg = PrescriptionLogic::getError();
@@ -129,8 +130,12 @@ class PrescriptionController extends BaseAdminController
if (!$appointmentId) {
return $this->fail('预约ID不能为空');
}
$prescription = PrescriptionLogic::getByAppointment($appointmentId);
return $this->data($prescription ?? []);
$prescription = PrescriptionLogic::getByAppointment($appointmentId, (int)$this->adminId, $this->adminInfo);
if ($prescription === null) {
//$msg = PrescriptionLogic::getError();
return '';
}
return $this->data($prescription);
}
/**
@@ -34,7 +34,19 @@ class PrescriptionOrderController extends BaseAdminController
$exceptPo > 0 ? $exceptPo : null
);
return $this->success('', ['lists' => $lists]);
$min = PrescriptionOrderLogic::depositMinAmount();
// if ($min > 0) {
// $lists = array_values(array_filter(
// $lists,
// static fn(array $r): bool => round((float) ($r['amount'] ?? 0), 2) >= $min
// ));
// }
return $this->success('', [
'lists' => $lists,
'deposit_min_amount' => $min,
]);
}
public function create()
@@ -107,6 +119,24 @@ class PrescriptionOrderController extends BaseAdminController
return $this->success('操作成功', $result);
}
/**
* 撤回处方审核
*/
public function revokeRxAudit()
{
$params = (new PrescriptionOrderValidate())->post()->goCheck('detail');
$result = PrescriptionOrderLogic::revokeRxAudit(
(int) $params['id'],
$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');
@@ -124,6 +154,44 @@ class PrescriptionOrderController extends BaseAdminController
return $this->success('操作成功', $result);
}
/**
* 撤回支付单审核
*/
public function revokePayAudit()
{
$params = (new PrescriptionOrderValidate())->post()->goCheck('detail');
$result = PrescriptionOrderLogic::revokePayAudit(
(int) $params['id'],
$this->adminId,
$this->adminInfo
);
if ($result === false) {
return $this->fail(PrescriptionOrderLogic::getError());
}
return $this->success('支付单审核已撤回', $result);
}
/**
* 确认发货:将履约状态推进到 5(已发货),同时更新快递信息
*/
public function ship()
{
$params = (new PrescriptionOrderValidate())->post()->goCheck('ship');
$result = PrescriptionOrderLogic::ship(
(int) $params['id'],
(string) ($params['express_company'] ?? 'auto'),
(string) ($params['tracking_number'] ?? ''),
$this->adminId,
$this->adminInfo
);
if ($result === false) {
return $this->fail(PrescriptionOrderLogic::getError());
}
return $this->success('发货成功', $result);
}
/**
* 医助/创建人撤回:仅「待双审通过」可撤(未通过或双审均驳回等仍为该状态)
*/
@@ -137,4 +205,48 @@ class PrescriptionOrderController extends BaseAdminController
return $this->success('已撤回', $result);
}
/**
* 获取业务订单操作日志
*/
public function logs()
{
$params = (new PrescriptionOrderValidate())->get()->goCheck('logs');
$result = PrescriptionOrderLogic::getLogs((int) $params['id'], $this->adminId, $this->adminInfo);
// 遇到没有权限或者订单不存在,逻辑里设置了 self::$error
if ($result === [] && PrescriptionOrderLogic::getError() !== '') {
return $this->fail(PrescriptionOrderLogic::getError());
}
return $this->success('', $result);
}
/**
* 为「已发货」订单新增一条关联支付单,并重置支付单审核状态为待审核
*/
public function addPayOrder()
{
$params = (new PrescriptionOrderValidate())->post()->goCheck('addPayOrder');
$result = PrescriptionOrderLogic::addPayOrder($params, $this->adminId, $this->adminInfo);
if ($result === false) {
return $this->fail(PrescriptionOrderLogic::getError());
}
return $this->success('新增支付单成功', $result);
}
/**
* 将「已发货」且支付审核已通过的订单标记为「已完成」
*/
public function complete()
{
$params = (new PrescriptionOrderValidate())->post()->goCheck('complete');
$result = PrescriptionOrderLogic::complete((int) $params['id'], $this->adminId, $this->adminInfo);
if ($result === false) {
return $this->fail(PrescriptionOrderLogic::getError());
}
return $this->success('订单已完成', $result);
}
}
@@ -82,7 +82,7 @@ class OrderLists extends BaseAdminDataLists implements ListsSearchInterface
$where[] = ['patient_id', 'in', function ($query) {
$query->table('zyt_tcm_diagnosis')
->whereNull('delete_time')
->where('patient_name|patient_phone', 'like', '%' . $this->params['patient_keyword'] . '%')
->where('patient_name|phone', 'like', '%' . $this->params['patient_keyword'] . '%')
->field('id');
}];
}
@@ -128,7 +128,7 @@ class OrderLists extends BaseAdminDataLists implements ListsSearchInterface
$where[] = ['patient_id', 'in', function ($query) {
$query->table('zyt_tcm_diagnosis')
->whereNull('delete_time')
->where('patient_name|patient_phone', 'like', '%' . $this->params['patient_keyword'] . '%')
->where('patient_name|phone', 'like', '%' . $this->params['patient_keyword'] . '%')
->field('id');
}];
}
@@ -6,12 +6,14 @@ namespace app\adminapi\lists\tcm;
use app\adminapi\lists\BaseAdminDataLists;
use app\adminapi\logic\tcm\PrescriptionOrderLogic;
use app\common\lists\ListsExtendInterface;
use app\common\lists\ListsSearchInterface;
use app\common\model\Order;
use app\common\model\tcm\Diagnosis;
use app\common\model\tcm\PrescriptionOrder;
use app\common\model\tcm\PrescriptionOrderPayOrder;
class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchInterface
class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
{
public function setSearch(): array
{
@@ -38,18 +40,115 @@ class PrescriptionOrderLists extends BaseAdminDataLists implements ListsSearchIn
->limit($this->limitOffset, $this->limitLength)
->select()
->toArray();
$poIds = array_values(array_filter(array_map('intval', array_column($lists, 'id'))));
$paidSumByPo = [];
if ($poIds !== []) {
$links = PrescriptionOrderPayOrder::whereIn('prescription_order_id', $poIds)
->field(['prescription_order_id', 'pay_order_id'])
->select()
->toArray();
$byPo = [];
foreach ($links as $l) {
$poid = (int) ($l['prescription_order_id'] ?? 0);
$oid = (int) ($l['pay_order_id'] ?? 0);
if ($poid > 0 && $oid > 0) {
$byPo[$poid][] = $oid;
}
}
$allOids = [];
foreach ($byPo as $oids) {
$allOids = array_merge($allOids, $oids);
}
$allOids = array_values(array_unique($allOids));
$amountByOid = [];
if ($allOids !== []) {
$amountByOid = Order::whereIn('id', $allOids)->whereNull('delete_time')->column('amount', 'id');
}
foreach ($poIds as $pid) {
$s = 0.0;
foreach ($byPo[$pid] ?? [] as $oid) {
$s += (float) ($amountByOid[$oid] ?? 0);
}
$paidSumByPo[$pid] = round($s, 2);
}
}
// 获取创建人姓名
$creatorIds = array_values(array_unique(array_filter(array_map('intval', array_column($lists, 'creator_id')))));
$creatorNames = [];
if ($creatorIds !== []) {
$creatorNames = \app\common\model\auth\Admin::whereIn('id', $creatorIds)->column('name', 'id');
}
// 获取处方ID对应的开方人姓名
$prescriptionIds = array_values(array_unique(array_filter(array_map('intval', array_column($lists, 'prescription_id')))));
$prescriptionCreators = [];
if ($prescriptionIds !== []) {
$prescriptions = \app\common\model\tcm\Prescription::whereIn('id', $prescriptionIds)
->whereNull('delete_time')
->field(['id', 'creator_id', 'doctor_name'])
->select()
->toArray();
$doctorIds = [];
foreach ($prescriptions as $rx) {
$rxId = (int) ($rx['id'] ?? 0);
$doctorId = (int) ($rx['creator_id'] ?? 0);
$doctorName = (string) ($rx['doctor_name'] ?? '');
if ($rxId > 0) {
$prescriptionCreators[$rxId] = [
'doctor_id' => $doctorId,
'doctor_name' => $doctorName
];
if ($doctorId > 0) {
$doctorIds[] = $doctorId;
}
}
}
// 如果 doctor_name 为空,从 Admin 表获取
$doctorIds = array_values(array_unique($doctorIds));
if ($doctorIds !== []) {
$doctorNamesFromAdmin = \app\common\model\auth\Admin::whereIn('id', $doctorIds)->column('name', 'id');
foreach ($prescriptionCreators as &$pc) {
if (empty($pc['doctor_name']) && $pc['doctor_id'] > 0) {
$pc['doctor_name'] = $doctorNamesFromAdmin[$pc['doctor_id']] ?? '';
}
}
unset($pc);
}
}
$depMin = PrescriptionOrderLogic::depositMinAmount();
foreach ($lists as &$item) {
PrescriptionOrderLogic::maskInternalCostIfNeeded($item, $this->adminInfo);
$pid = (int) ($item['id'] ?? 0);
$item['linked_pay_order_count'] = (int) PrescriptionOrderPayOrder::where(
'prescription_order_id',
(int) ($item['id'] ?? 0)
$pid
)->count();
$item['linked_pay_paid_total'] = $paidSumByPo[$pid] ?? 0.0;
$item['deposit_min_amount'] = $depMin;
// 添加创建人姓名
$creatorId = (int) ($item['creator_id'] ?? 0);
$item['creator_name'] = $creatorId > 0 ? ($creatorNames[$creatorId] ?? '') : '';
// 添加开方人姓名
$rxId = (int) ($item['prescription_id'] ?? 0);
$item['doctor_name'] = $rxId > 0 ? ($prescriptionCreators[$rxId]['doctor_name'] ?? '') : '';
}
unset($item);
return $lists;
}
public function extend(): array
{
return [
'deposit_min_amount' => PrescriptionOrderLogic::depositMinAmount(),
];
}
public function count(): int
{
$query = PrescriptionOrder::where($this->searchWhere)->whereNull('delete_time');
+169 -168
View File
@@ -47,7 +47,7 @@ class OrderLogic
$order->status = 1; // 待支付
$order->remark = $params['remark'] ?? '';
$order->save();
return $order;
} catch (\Exception $e) {
self::setError($e->getMessage());
@@ -102,146 +102,146 @@ class OrderLogic
$cursor = '';
do {
$resp = $service->getBillList($beginTime, $endTime, $payeeUserid, $cursor, 100);
if (($resp['errcode'] ?? -1) !== 0) {
$result['errors'][] = '获取账单失败: ' . ($resp['errmsg'] ?? '未知错误');
break;
}
$billList = $resp['bill_list'] ?? [];
$cursor = $resp['next_cursor'] ?? '';
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;
if (($resp['errcode'] ?? -1) !== 0) {
$result['errors'][] = '获取账单失败: ' . ($resp['errmsg'] ?? '未知错误');
break;
}
$targetStatus = $tradeState === 3 ? 4 : 2; // 4=已退款 2=已支付
$billList = $resp['bill_list'] ?? [];
$cursor = $resp['next_cursor'] ?? '';
$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'] ?? '');
$payTime = $bill['pay_time'] ?? $bill['time_end'] ?? '';
$billPayeeUserid = (string)($bill['payee_userid'] ?? '');
if (empty($orderNo) || $totalFee <= 0) {
$result['skipped']++;
continue;
}
$amount = round($totalFee / 100, 2);
$orderType = ($amount == 5.00) ? 1 : $defaultOrderType;
$creatorId = 0;
if ($billPayeeUserid) {
$admin = Admin::where('work_wechat_userid', $billPayeeUserid)->whereNull('delete_time')->find();
if ($admin) {
$creatorId = (int)$admin->id;
}
}
$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) {
$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;
$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 {
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;
}
} else {
try {
$order = new Order();
$order->order_no = $orderNo;
$order->patient_id = null;
$order->creator_id = $creatorId;
$order->order_type = $orderType;
$order->amount = $amount;
$order->status = $targetStatus;
$order->payment_method = 'wechat';
$order->payment_time = $paymentTimeStr;
$order->trade_no = $tradeNo;
$remark = '企业微信直接收款同步,待关联患者';
if ($tradeState === 3) {
$refundCent = (int)($bill['total_refund_fee'] ?? 0);
if ($refundCent > 0) {
$remark .= '(账单含退款' . round($refundCent / 100, 2) . '元)';
$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'] ?? '');
$payTime = $bill['pay_time'] ?? $bill['time_end'] ?? '';
$billPayeeUserid = (string)($bill['payee_userid'] ?? '');
if (empty($orderNo) || $totalFee <= 0) {
$result['skipped']++;
continue;
}
$amount = round($totalFee / 100, 2);
$orderType = ($amount == 5.00) ? 1 : $defaultOrderType;
$creatorId = 0;
if ($billPayeeUserid) {
$admin = Admin::where('work_wechat_userid', $billPayeeUserid)->whereNull('delete_time')->find();
if ($admin) {
$creatorId = (int)$admin->id;
}
}
$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) {
$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;
}
}
$order->remark = $remark;
$order->payee_userid = $billPayeeUserid;
$order->payer_external_userid = $payerExternalUserid;
$order->save();
$result['created']++;
} catch (\Exception $e) {
$result['errors'][] = "订单 {$orderNo}: " . $e->getMessage();
Log::error("企业微信账单同步创建订单失败: {$orderNo}, " . $e->getMessage());
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;
$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 {
$result['skipped']++;
}
} else {
try {
$order = new Order();
$order->order_no = $orderNo;
$order->patient_id = null;
$order->creator_id = $creatorId;
$order->order_type = $orderType;
$order->amount = $amount;
$order->status = $targetStatus;
$order->payment_method = 'wechat';
$order->payment_time = $paymentTimeStr;
$order->trade_no = $tradeNo;
$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();
$result['created']++;
} catch (\Exception $e) {
$result['errors'][] = "订单 {$orderNo}: " . $e->getMessage();
Log::error("企业微信账单同步创建订单失败: {$orderNo}, " . $e->getMessage());
}
}
}
}
} while ($cursor);
}
@@ -295,7 +295,7 @@ class OrderLogic
self::setError('订单不存在');
return false;
}
if (isset($params['remark'])) {
$order->remark = $params['remark'];
}
@@ -307,7 +307,7 @@ class OrderLogic
if (isset($params['order_type'])) {
$order->order_type = (int)$params['order_type'];
}
$order->save();
return true;
} catch (\Exception $e) {
@@ -330,17 +330,17 @@ class OrderLogic
self::setError('订单不存在');
return false;
}
if ($order->status != 1) {
self::setError('订单状态不允许支付');
return false;
}
$order->status = 2; // 已支付
$order->payment_method = $paymentMethod;
$order->payment_time = date('Y-m-d H:i:s');
$order->save();
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
@@ -361,15 +361,15 @@ class OrderLogic
self::setError('订单不存在');
return false;
}
if ($order->status != 1) {
self::setError('只有待支付的订单才能取消');
return false;
}
$order->status = 3; // 已取消
$order->save();
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
@@ -390,15 +390,15 @@ class OrderLogic
self::setError('订单不存在');
return false;
}
if ($order->status != 2) {
self::setError('只有已支付的订单才能退款');
return false;
}
$order->status = 4; // 已退款
$order->save();
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
@@ -419,10 +419,10 @@ class OrderLogic
self::setError('订单不存在');
return false;
}
// 删除订单
$order->delete();
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
@@ -466,11 +466,11 @@ class OrderLogic
{
$order = Order::with(['patient', 'creator'])
->find($id);
if (!$order) {
return null;
}
return $order->toArray();
}
@@ -496,10 +496,10 @@ class OrderLogic
{
// 移除sign字段
unset($params['sign']);
// 按键排序
ksort($params);
// 构建签名字符串
$signStr = '';
foreach ($params as $key => $value) {
@@ -508,12 +508,12 @@ class OrderLogic
}
}
$signStr = rtrim($signStr, '&');
// 使用私钥签名
$privateKeyResource = openssl_pkey_get_private($privateKey);
openssl_sign($signStr, $sign, $privateKeyResource, OPENSSL_ALGO_SHA256);
openssl_free_key($privateKeyResource);
return base64_encode($sign);
}
@@ -527,10 +527,10 @@ class OrderLogic
{
// 移除sign字段
unset($params['sign']);
// 按键排序
ksort($params);
// 构建签名字符串
$signStr = '';
foreach ($params as $key => $value) {
@@ -539,7 +539,7 @@ class OrderLogic
}
}
$signStr .= 'key=' . $apiKey;
// MD5签名
return strtoupper(md5($signStr));
}
@@ -606,14 +606,14 @@ class OrderLogic
// 获取订单号
$orderNo = is_array($order) ? $order['order_no'] : $order->order_no;
$amount = is_array($order) ? ($order['amount'] ?? 0) : $order->amount;
// 获取支付宝配置
$alipayConfig = config('pay.alipay');
if (empty($alipayConfig['app_id']) || empty($alipayConfig['merchant_private_key'])) {
throw new \Exception('支付宝配置不完整');
}
// 使用支付宝SDK生成支付URL
$params = [
'app_id' => $alipayConfig['app_id'],
@@ -632,14 +632,14 @@ class OrderLogic
'body' => '订单号:' . $orderNo,
], JSON_UNESCAPED_UNICODE),
];
// 生成签名
$sign = self::generateAlipaySign($params, $alipayConfig['merchant_private_key']);
$params['sign'] = $sign;
// 生成支付URL
$payUrl = $alipayConfig['gateway_url'] . '?' . http_build_query($params);
return $payUrl;
} catch (\Exception $e) {
self::setError($e->getMessage());
@@ -658,14 +658,14 @@ class OrderLogic
// 获取订单号
$orderNo = is_array($order) ? $order['order_no'] : $order->order_no;
$amount = is_array($order) ? ($order['amount'] ?? 0) : $order->amount;
// 获取微信配置
$wechatConfig = config('pay.wechat');
if (empty($wechatConfig['app_id']) || empty($wechatConfig['mch_id']) || empty($wechatConfig['api_key'])) {
throw new \Exception('微信支付配置不完整');
}
// 生成微信支付数据
$payData = [
'appid' => $wechatConfig['app_id'],
@@ -678,11 +678,11 @@ class OrderLogic
'notify_url' => $wechatConfig['notify_url'] ?: config('app.url') . '/api/order/wechat-notify',
'trade_type' => 'NATIVE', // 二维码支付
];
// 生成签名
$sign = self::generateWechatSign($payData, $wechatConfig['api_key']);
$payData['sign'] = $sign;
// 返回支付数据
return [
'pay_url' => $wechatConfig['gateway_url'] . '/pay/unifiedorder',
@@ -918,7 +918,7 @@ class OrderLogic
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
return true;
}
$supervisorRoles = [0, 3];
$supervisorRoles = config('project.order_edit_all_roles', [0, 3]);
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
return count(array_intersect($myRoles, $supervisorRoles)) > 0;
@@ -985,6 +985,7 @@ class OrderLogic
if ($busy && ! $allowed) {
continue;
}
$out[] = $row;
}
@@ -998,7 +999,7 @@ class OrderLogic
*/
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)));
$ids = array_values(array_unique(array_filter(array_map('intval', $ids), static fn(int $v): bool => $v > 0)));
if ($diagnosisId <= 0) {
return '诊单无效';
}
@@ -74,30 +74,54 @@ class PrescriptionLogic
*/
public static function canViewPrescription($row, int $adminId, array $adminInfo): bool
{
// 超级管理员
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
return true;
}
// 创建人
$creatorId = is_array($row) ? (int) ($row['creator_id'] ?? 0) : (int) $row->creator_id;
if ($creatorId === $adminId) {
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;
}
// 可见角色
$vis = is_array($row) ? (string) ($row['visible_role_ids'] ?? '') : (string) ($row->visible_role_ids ?? '');
$allowed = array_filter(array_map('intval', explode(',', $vis)));
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
if (count(array_intersect($myRoles, $allowed)) > 0) {
return true;
}
return count(array_intersect($myRoles, $allowed)) > 0;
// 有开方权限的医生可以查看所有处方(只读)
// 这样其他医生可以查看患者的历史处方记录
$permissions = $adminInfo['permissions'] ?? [];
if (in_array('tcm.diagnosis/kaifang', $permissions, true)) {
return true;
}
// 有业务订单管理权限的角色(如药师)可以查看处方(只读)
// 这样药师可以在业务订单中查看关联的处方信息
$orderEditAllRoles = Config::get('project.order_edit_all_roles', [0, 3]);
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
$allowedRoles = array_map('intval', is_array($orderEditAllRoles) ? $orderEditAllRoles : []);
if (count(array_intersect($myRoles, $allowedRoles)) > 0) {
return true;
}
return false;
}
private static function generateSn(): string
@@ -261,13 +285,10 @@ class PrescriptionLogic
return false;
}
// 已通过且未作废:不可编辑;例外:业务订单「处方审核」已驳回时允许医生改方,保存后业务订单侧审核会重置为待审
// 已通过且未作废:不可编辑
if ((int) ($prescription->audit_status ?? 1) === 1 && (int) ($prescription->void_status ?? 0) === 0) {
if (!PrescriptionOrderLogic::allowDoctorEditApprovedPrescriptionDueToBusinessReject((int) $params['id'])) {
self::setError('该处方已通过审核,不可编辑');
return false;
}
self::setError('该处方已通过审核,不可编辑');
return false;
}
// 已驳回:仅当该诊单+当天+同一创建人下没有另一条「已通过且未作废」的处方时允许重新编辑(改后待审核、作废一并清除)
@@ -435,6 +456,51 @@ class PrescriptionLogic
return $arr;
}
/**
* 业务订单「处方审核」通过时:消费者处方仍为待审核则一并记为已通过,
* 避免消费者处方列表筛选「未通过」仍包含该条、与业务侧已通过不一致。
*/
public static function syncApproveConsumerPrescriptionIfPending(
int $prescriptionId,
int $adminId,
array $adminInfo,
string $remark
): void {
if ($prescriptionId <= 0) {
return;
}
$row = Prescription::find($prescriptionId);
if (!$row) {
return;
}
if ((int) ($row->void_status ?? 0) === 1) {
return;
}
if ((int) ($row->audit_status ?? 1) !== 0) {
return;
}
self::$lastAuditWecomNotify = null;
$name = (string) ($adminInfo['name'] ?? '');
$now = time();
$finalRemark = trim($remark) !== '' ? trim($remark) : '业务订单处方审核通过';
$finalRemark = mb_substr($finalRemark, 0, 500);
$row->audit_status = 1;
$row->audit_time = $now;
$row->audit_by = $adminId;
$row->audit_by_name = $name;
$row->audit_remark = $finalRemark;
try {
if ($row->save()) {
self::$lastAuditWecomNotify = self::notifyCreatorAuditResult($row, 'approve', $finalRemark, $adminInfo);
}
} catch (\Throwable $e) {
Log::error('syncApproveConsumerPrescriptionIfPending: ' . $e->getMessage());
}
}
/**
* @return int[]
*/
@@ -601,15 +667,28 @@ class PrescriptionLogic
}
/**
* 根据预约ID获取处方
* 根据预约ID获取处方(带权限检查)
*/
public static function getByAppointment(int $appointmentId): ?array
public static function getByAppointment(int $appointmentId, int $viewerAdminId, array $viewerAdminInfo): ?array
{
self::$error = '';
$row = Prescription::where('appointment_id', $appointmentId)
->where('creator_id',$viewerAdminId)
->whereNull('delete_time')
->order('id', 'desc')
->find();
return $row ? $row->toArray() : null;
if (!$row) {
return null;
}
// 权限检查:只返回当前用户有权限查看的处方
if (!self::canViewPrescription($row, $viewerAdminId, $viewerAdminInfo)) {
self::setError('无权限查看此处方');
return null;
}
return $row->toArray();
}
/**
@@ -4,11 +4,15 @@ declare(strict_types=1);
namespace app\adminapi\logic\tcm;
use app\adminapi\logic\auth\AuthLogic;
use app\adminapi\logic\order\OrderLogic;
use app\common\model\Order;
use app\common\model\tcm\Diagnosis;
use app\common\model\tcm\Prescription;
use app\common\model\tcm\PrescriptionOrder;
use app\common\model\tcm\PrescriptionOrderLog;
use app\common\model\tcm\PrescriptionOrderPayOrder;
use app\common\model\auth\Admin;
use app\common\service\ExpressTrackService;
use think\facade\Config;
@@ -84,6 +88,20 @@ class PrescriptionOrderLogic
return count(array_intersect($myRoles, $allow)) > 0;
}
/**
* 业务订单详情内是否返回/展示处方「药材明细」(菜单权限 tcm.prescriptionOrder/viewRxHerbs
*/
private static function canViewPrescriptionHerbsInOrderDetail(array $adminInfo): bool
{
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
return true;
}
$perms = AuthLogic::getAuthByAdminId((int) ($adminInfo['admin_id'] ?? 0));
return in_array('tcm.prescriptionOrder/viewRxHerbs', $perms, true);
}
public static function canAuditPrescriptionOrder(array $adminInfo): bool
{
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
@@ -114,6 +132,47 @@ class PrescriptionOrderLogic
}
}
/** 定金门槛(元),0 表示不校验 */
public static function depositMinAmount(): float
{
return round((float) Config::get('prescription_order.link_pay_min_amount', 0), 2);
}
/**
* @param int[] $payOrderIds
*/
public static function assertDepositAndLinkPayRules(float $orderAmount, array $payOrderIds): ?string
{
$min = self::depositMinAmount();
if ($min <= 0) {
return null;
}
if ($orderAmount < $min) {
return '订单金额须大于等于定金门槛 ¥' . $min . '(当前 ¥' . $orderAmount . '';
}
if ($payOrderIds === []) {
return '未关联支付单,关联支付单总金额须大于等于定金门槛 ¥' . $min;
}
// 检查关联支付单的总金额
$rows = Order::whereIn('id', $payOrderIds)->whereNull('delete_time')->column('amount', 'id');
$totalAmount = 0.0;
foreach ($payOrderIds as $pid) {
$pid = (int) $pid;
if ($pid <= 0) {
continue;
}
$amt = round((float) ($rows[$pid] ?? 0), 2);
$totalAmount += $amt;
}
if ($totalAmount < $min) {
return '关联支付单总金额须大于等于定金门槛 ¥' . $min . '(当前总金额 ¥' . $totalAmount . '';
}
return null;
}
public static function generateOrderNo(): string
{
return 'PO' . date('YmdHis') . mt_rand(100000, 999999);
@@ -279,6 +338,36 @@ class PrescriptionOrderLogic
->select()
->toArray();
$creatorIds = array_values(array_unique(array_filter(array_map('intval', array_column($rows, 'creator_id')))));
$creatorNames = [];
if ($creatorIds !== []) {
$creatorNames = \app\common\model\auth\Admin::whereIn('id', $creatorIds)->column('name', 'id');
}
$typeMap = [
1 => '挂号费',
2 => '问诊费',
3 => '药品费用',
4 => '首付费用',
5 => '尾款费用',
6 => '其他费用',
7 => '全部费用',
];
$statusMap = [
1 => '待支付',
2 => '已支付',
3 => '已取消',
4 => '已退款',
];
foreach ($rows as &$r) {
$ot = (int) ($r['order_type'] ?? 0);
$st = (int) ($r['status'] ?? 0);
$r['order_type_desc'] = $typeMap[$ot] ?? '—';
$r['status_desc'] = $statusMap[$st] ?? '—';
$cid = (int) ($r['creator_id'] ?? 0);
$r['creator_name'] = $cid > 0 ? (string) ($creatorNames[$cid] ?? '') : '';
}
unset($r);
return $rows;
}
@@ -291,12 +380,20 @@ class PrescriptionOrderLogic
if ($id <= 0) {
$arr['pay_order_ids'] = [];
$arr['linked_pay_orders'] = [];
$arr['linked_pay_paid_total'] = 0.0;
$arr['deposit_min_amount'] = self::depositMinAmount();
return;
}
$ids = self::linkedPayOrderIdList($id);
$arr['pay_order_ids'] = $ids;
$arr['linked_pay_orders'] = self::linkedPayOrdersPayload($ids);
$sum = 0.0;
foreach ($arr['linked_pay_orders'] as $o) {
$sum += (float) ($o['amount'] ?? 0);
}
$arr['linked_pay_paid_total'] = round($sum, 2);
$arr['deposit_min_amount'] = self::depositMinAmount();
}
/**
@@ -352,6 +449,14 @@ class PrescriptionOrderLogic
}
}
$orderAmtPreview = round((float) ($params['amount'] ?? 0), 2);
$depErrCreate = self::assertDepositAndLinkPayRules($orderAmtPreview, $payOrderIds);
if ($depErrCreate !== null) {
self::$error = $depErrCreate;
return false;
}
$internalCost = null;
if (isset($params['internal_cost']) && $params['internal_cost'] !== '' && $params['internal_cost'] !== null) {
if (self::canViewInternalCost($adminInfo)) {
@@ -396,6 +501,8 @@ class PrescriptionOrderLogic
return false;
}
self::writeLog((int) $order->id, $adminId, $adminInfo, 'create', '创建业务订单');
if ($payOrderIds !== []) {
self::replacePayOrderLinks((int) $order->id, $payOrderIds);
}
@@ -425,6 +532,44 @@ class PrescriptionOrderLogic
self::maskInternalCostIfNeeded($arr, $adminInfo);
self::attachLinkedPayOrders($arr);
// 添加创建人姓名
$creatorId = (int) ($arr['creator_id'] ?? 0);
if ($creatorId > 0) {
$creatorName = Admin::where('id', $creatorId)->value('name');
$arr['creator_name'] = $creatorName ? (string) $creatorName : '';
} else {
$arr['creator_name'] = '';
}
$rxId = (int) ($arr['prescription_id'] ?? 0);
$arr['prescription'] = null;
$arr['prescription_detail_error'] = '';
$arr['doctor_name'] = '';
if ($rxId > 0) {
$rxDetail = PrescriptionLogic::detail($rxId, $adminId, $adminInfo);
if ($rxDetail !== null) {
$arr['prescription'] = $rxDetail;
// 添加开方人姓名(优先使用处方的 doctor_name,否则从 creator_id 获取)
$doctorName = (string) ($rxDetail['doctor_name'] ?? '');
if ($doctorName === '') {
$rxCreatorId = (int) ($rxDetail['creator_id'] ?? 0);
if ($rxCreatorId > 0) {
$doctorName = (string) (Admin::where('id', $rxCreatorId)->value('name') ?? '');
}
}
$arr['doctor_name'] = $doctorName;
} else {
$err = PrescriptionLogic::getError();
$arr['prescription_detail_error'] = $err !== '' ? $err : '处方不存在';
}
}
$canHerbs = self::canViewPrescriptionHerbsInOrderDetail($adminInfo);
$arr['prescription_detail_herbs_visible'] = $canHerbs;
if (! $canHerbs && isset($arr['prescription']) && is_array($arr['prescription'])) {
unset($arr['prescription']['herbs']);
}
return $arr;
}
@@ -581,9 +726,10 @@ class PrescriptionOrderLogic
$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 {
@@ -591,6 +737,14 @@ class PrescriptionOrderLogic
}
}
$finalPayIds = self::linkedPayOrderIdList((int) $order->id);
$depErr = self::assertDepositAndLinkPayRules(round((float) $order->amount, 2), $finalPayIds);
if ($depErr !== null) {
self::$error = $depErr;
return false;
}
try {
$order->save();
} catch (\Throwable $e) {
@@ -599,6 +753,71 @@ class PrescriptionOrderLogic
return false;
}
self::writeLog((int) $order->id, $adminId, $adminInfo, 'edit', '编辑了业务订单及相关信息');
$out = $order->toArray();
self::maskInternalCostIfNeeded($out, $adminInfo);
self::attachLinkedPayOrders($out);
return $out;
}
/**
* 确认发货:更新快递信息并将 fulfillment_status 从 2(履约中)推进到 5(已发货)
*
* @return array<string,mixed>|false
*/
public static function ship(int $id, string $expressCompany, string $trackingNumber, int $adminId, array $adminInfo)
{
self::$error = '';
$trackingNumber = trim($trackingNumber);
if ($trackingNumber === '') {
self::$error = '快递单号不能为空';
return false;
}
$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;
}
if ((int) $order->fulfillment_status !== 2 && (int) $order->fulfillment_status !== 5) {
self::$error = '仅「履约中」或「已发货」状态的订单可更新快递信息';
return false;
}
$order->express_company = self::normalizeExpressCompany($expressCompany);
$order->tracking_number = $trackingNumber;
// 仅履约中(2)时才推进到已发货(5),已发货(5)则只更新快递信息保持状态
$isFirstShip = false;
if ((int) $order->fulfillment_status === 2) {
$order->fulfillment_status = 5;
$isFirstShip = true;
}
try {
$order->save();
} catch (\Throwable $e) {
self::$error = $e->getMessage();
return false;
}
if ($isFirstShip) {
self::writeLog((int) $order->id, $adminId, $adminInfo, 'ship', '确认发货,运单:' . $trackingNumber . '' . $expressCompany . '');
} else {
self::writeLog((int) $order->id, $adminId, $adminInfo, 'fill_tracking', '更新发货信息,运单:' . $trackingNumber . '' . $expressCompany . '');
}
$out = $order->toArray();
self::maskInternalCostIfNeeded($out, $adminInfo);
self::attachLinkedPayOrders($out);
@@ -634,12 +853,29 @@ class PrescriptionOrderLogic
try {
$order->save();
// 撤回订单后,将关联的处方审核状态改回"待审核"
$prescriptionId = (int) $order->prescription_id;
if ($prescriptionId > 0) {
Prescription::where('id', $prescriptionId)
->whereNull('delete_time')
->update([
'audit_status' => 0, // 0=待审核
'audit_time' => null,
'audit_by' => null,
'audit_by_name' => '',
'audit_remark' => '',
'update_time' => time(),
]);
}
} catch (\Throwable $e) {
self::$error = $e->getMessage();
return false;
}
self::writeLog((int) $order->id, $adminId, $adminInfo, 'withdraw', '撤回订单,状态变更为「已取消」');
$out = $order->toArray();
self::maskInternalCostIfNeeded($out, $adminInfo);
self::attachLinkedPayOrders($out);
@@ -704,6 +940,20 @@ class PrescriptionOrderLogic
return false;
}
if ($action === 'approve') {
$syncRemark = trim($remark) !== '' ? trim($remark) : '业务订单处方审核通过';
PrescriptionLogic::syncApproveConsumerPrescriptionIfPending(
(int) $order->prescription_id,
$adminId,
$adminInfo,
$syncRemark
);
}
$actionName = $action === 'approve' ? 'audit_rx_approve' : 'audit_rx_reject';
$summary = $action === 'approve' ? '处方审核通过' . ($remark ? ',意见:' . $remark : '') : '处方审核驳回,意见:' . $remark;
self::writeLog((int) $order->id, $adminId, $adminInfo, $actionName, $summary);
$out = $order->toArray();
self::maskInternalCostIfNeeded($out, $adminInfo);
self::attachLinkedPayOrders($out);
@@ -744,6 +994,7 @@ class PrescriptionOrderLogic
return false;
}
// 已发货状态(5)允许重新发起支付单审核(新增支付单场景)
if ($action === 'reject' && trim($remark) === '') {
self::$error = '驳回时请填写审核意见';
@@ -771,10 +1022,306 @@ class PrescriptionOrderLogic
return false;
}
$actionName = $action === 'approve' ? 'audit_pay_approve' : 'audit_pay_reject';
$summary = $action === 'approve' ? '支付单审核通过' . ($remark ? ',意见:' . $remark : '') : '支付单审核驳回,意见:' . $remark;
self::writeLog((int) $order->id, $adminId, $adminInfo, $actionName, $summary);
$out = $order->toArray();
self::maskInternalCostIfNeeded($out, $adminInfo);
self::attachLinkedPayOrders($out);
return $out;
}
/**
* 撤回处方审核:将已通过/已驳回的处方审核重置为待审核
*
* @return array<string,mixed>|false
*/
public static function revokeRxAudit(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::canAuditPrescriptionOrder($adminInfo)) {
self::$error = '无处方审核权限';
return false;
}
$rxStatus = (int) $order->prescription_audit_status;
if ($rxStatus === 0) {
self::$error = '当前已是待审核状态,无需撤回';
return false;
}
$fs = (int) $order->fulfillment_status;
if ($fs === 3 || $fs === 4) {
self::$error = '订单已结束,不可撤回审核';
return false;
}
// 如果处方审核已通过,且支付审核也已通过或已驳回,不允许撤回处方审核
if ($rxStatus === 1 && (int) $order->payment_slip_audit_status !== 0) {
self::$error = '支付单审核已进行,请先撤回支付单审核';
return false;
}
$order->prescription_audit_status = 0;
$order->prescription_audit_remark = '';
// 如果支付审核不是待审核,也一并重置
if ((int) $order->payment_slip_audit_status !== 0) {
$order->payment_slip_audit_status = 0;
$order->payment_slip_audit_remark = '';
}
self::syncFulfillmentStatus($order);
try {
$order->save();
} catch (\Throwable $e) {
self::$error = $e->getMessage();
return false;
}
self::writeLog((int) $order->id, $adminId, $adminInfo, 'revoke_rx_audit', '撤回处方审核,状态重置为待审核');
$out = $order->toArray();
self::maskInternalCostIfNeeded($out, $adminInfo);
self::attachLinkedPayOrders($out);
return $out;
}
/**
* 撤回支付单审核:将已通过/已驳回的支付单审核重置为待审核
*
* @return array<string,mixed>|false
*/
public static function revokePayAudit(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::canAuditPaymentSlipOrder($adminInfo)) {
self::$error = '无支付单审核权限';
return false;
}
if ((int) $order->prescription_audit_status !== 1) {
self::$error = '处方审核未通过,无法撤回支付单审核';
return false;
}
$payStatus = (int) $order->payment_slip_audit_status;
if ($payStatus === 0) {
self::$error = '当前已是待审核状态,无需撤回';
return false;
}
$fs = (int) $order->fulfillment_status;
if ($fs === 3 || $fs === 4) {
self::$error = '订单已结束,不可撤回审核';
return false;
}
$order->payment_slip_audit_status = 0;
$order->payment_slip_audit_remark = '';
self::syncFulfillmentStatus($order);
try {
$order->save();
} catch (\Throwable $e) {
self::$error = $e->getMessage();
return false;
}
self::writeLog((int) $order->id, $adminId, $adminInfo, 'revoke_pay_audit', '撤回支付单审核,状态重置为待审核');
$out = $order->toArray();
self::maskInternalCostIfNeeded($out, $adminInfo);
self::attachLinkedPayOrders($out);
return $out;
}
/**
* @return array<int, array<string, mixed>>
*/
public static function getLogs(int $orderId, int $adminId, array $adminInfo): array
{
self::$error = '';
$order = PrescriptionOrder::where('id', $orderId)->whereNull('delete_time')->find();
if (!$order) {
self::$error = '订单不存在';
return [];
}
if (!self::canAccessOrder($order, $adminId, $adminInfo)) {
self::$error = '无权限查看';
return [];
}
return PrescriptionOrderLog::where('prescription_order_id', $orderId)
->order('id', 'desc')
->select()
->toArray();
}
/**
* 为「已发货」(fulfillment_status=5) 的业务订单新增一条关联支付单(zyt_order),
* 创建后将支付单链接到业务订单,并将处方/支付审核状态重置为待审核以启动再次审核流程。
*
* @param array<string,mixed> $params id, order_type, amount, remark
* @return array<string,mixed>|false
*/
public static function addPayOrder(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;
}
if ((int) $order->fulfillment_status !== 5) {
self::$error = '仅「已发货」状态的订单可新增支付单';
return false;
}
$orderType = (int) ($params['order_type'] ?? 3);
$amount = round((float) ($params['pay_amount'] ?? 0), 2);
$remark = mb_substr(trim((string) ($params['pay_remark'] ?? '')), 0, 200);
if ($amount <= 0) {
self::$error = '支付单金额须大于 0';
return false;
}
// 创建 zyt_order 并标记为已支付(后台直接确认)
$payOrder = new Order();
$payOrder->order_no = OrderLogic::generateOrderNo();
$payOrder->patient_id = (int) $order->diagnosis_id;
$payOrder->creator_id = $adminId;
$payOrder->order_type = $orderType;
$payOrder->amount = $amount;
$payOrder->status = 2; // 已支付
$payOrder->payment_method = 'manual';
$payOrder->payment_time = date('Y-m-d H:i:s');
$payOrder->remark = $remark;
try {
$payOrder->save();
} catch (\Throwable $e) {
self::$error = $e->getMessage();
return false;
}
// 将新支付单追加到业务订单关联列表
$existingIds = self::linkedPayOrderIdList($id);
$newIds = array_unique(array_merge($existingIds, [(int) $payOrder->id]));
self::replacePayOrderLinks($id, $newIds);
$order->linked_pay_order_id = $newIds[0];
// 重置支付单审核为待审核,以启动新一轮审核(处方审核保持通过状态不变)
$order->payment_slip_audit_status = 0;
$order->payment_slip_audit_remark = '';
try {
$order->save();
} catch (\Throwable $e) {
self::$error = $e->getMessage();
return false;
}
self::writeLog($id, $adminId, $adminInfo, 'add_pay_order',
'新增支付单 #' . $payOrder->id . '(¥' . $amount . '),类型:' . $orderType . ',等待支付审核');
$out = $order->toArray();
self::maskInternalCostIfNeeded($out, $adminInfo);
self::attachLinkedPayOrders($out);
return $out;
}
/**
* 将「已发货」(fulfillment_status=5) 且支付审核已通过的订单标记为「已完成」(3)。
*
* @return array<string,mixed>|false
*/
public static function complete(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::canAccessOrder($order, $adminId, $adminInfo)) {
self::$error = '无权限操作';
return false;
}
if ((int) $order->fulfillment_status !== 5) {
self::$error = '仅「已发货」状态可标记为已完成';
return false;
}
if ((int) $order->payment_slip_audit_status !== 1) {
self::$error = '支付单审核尚未通过,不可完成订单';
return false;
}
$order->fulfillment_status = 3;
try {
$order->save();
} catch (\Throwable $e) {
self::$error = $e->getMessage();
return false;
}
self::writeLog($id, $adminId, $adminInfo, 'complete', '订单完成,状态变更为「已完成」');
$out = $order->toArray();
self::maskInternalCostIfNeeded($out, $adminInfo);
self::attachLinkedPayOrders($out);
return $out;
}
private static function writeLog(int $orderId, int $adminId, array $adminInfo, string $action, string $summary): void
{
$adminName = $adminInfo['name'] ?? '';
if ($adminName === '' && $adminId > 0) {
$adminName = (string) \app\common\model\auth\Admin::where('id', $adminId)->value('name');
}
$log = new PrescriptionOrderLog();
$log->prescription_order_id = $orderId;
$log->admin_id = $adminId;
$log->admin_name = mb_substr($adminName, 0, 64);
$log->action = mb_substr($action, 0, 32);
$log->summary = mb_substr($summary, 0, 500);
$log->create_time = time();
try {
$log->save();
} catch (\Throwable $e) {
// 忽略日志写入错误
}
}
}
@@ -9,24 +9,27 @@ 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',
'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',
'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',
'order_type' => 'require|in:1,2,3,4,5,6,7',
'pay_amount' => 'require|float|gt:0',
'pay_remark' => 'max:200',
'remark_extra' => 'max:500',
'action' => 'require|in:approve,reject',
'remark' => 'max:500',
];
protected $message = [
@@ -56,6 +59,10 @@ class PrescriptionOrderValidate extends BaseValidate
'auditPrescription' => ['id', 'action', 'remark'],
'auditPayment' => ['id', 'action', 'remark'],
'withdraw' => ['id'],
'ship' => ['id', 'tracking_number', 'express_company'],
'logs' => ['id'],
'paidPayOrders' => ['diagnosis_id'],
'addPayOrder' => ['id', 'order_type', 'pay_amount', 'pay_remark'],
'complete' => ['id'],
];
}
+28 -1
View File
@@ -3,13 +3,40 @@
namespace app\api\controller;
use app\api\logic\ChatNotifyLogic;
use think\facade\Db;
/**
* 聊天相关接口(C端-患者)
*/
class ChatController extends BaseApiController
{
public array $notNeedLogin = ['notifyOpen'];
public array $notNeedLogin = ['notifyOpen', 'logAvPermission'];
/**
* 上报音视频权限拒绝日志
* 患者小程序请求摄像头/麦克风权限被拒绝,且用户点击「取消」未前往设置页时调用
*/
public function logAvPermission()
{
$patientId = trim((string) ($this->request->post('patient_id', '')));
$doctorId = trim((string) ($this->request->post('doctor_id', '')));
$deniedScope = trim((string) ($this->request->post('denied_scope', '')));
$scene = trim((string) ($this->request->post('scene', '')));
$action = trim((string) ($this->request->post('action', 'cancel')));
$wxVersion = trim((string) ($this->request->post('wx_version', '')));
Db::name('av_permission_log')->insert([
'patient_id' => $patientId,
'doctor_id' => $doctorId,
'denied_scope' => $deniedScope ?: 'unknown',
'scene' => $scene ?: 'unknown',
'action' => in_array($action, ['cancel', 'open_setting']) ? $action : 'cancel',
'wx_version' => $wxVersion,
'create_time' => time(),
]);
return $this->success('已记录');
}
/**
* 患者打开会话时通知医生
@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace app\common\model\tcm;
use app\common\model\BaseModel;
/**
* 处方业务订单操作日志
*/
class PrescriptionOrderLog extends BaseModel
{
protected $name = 'tcm_prescription_order_log';
protected $autoWriteTimestamp = false; // 时间戳由代码里显式写
}
+14
View File
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
/**
* 处方业务订单(履约单)
*
* .env 示例:PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT = 100
* 为 0 或未配置时不校验。
*/
return [
// 定金门槛(元):关联的每笔已支付单金额须 **大于等于** 此值;业务订单金额须 **大于等于** 此值
'link_pay_min_amount' => (float) env('PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT', 0),
];
+5 -5
View File
@@ -102,20 +102,20 @@ return [
'tabbar_style' => ['default_color' => '#999999', 'selected_color' => '#c455ff'],
],
// 订单编辑权限:拥有以下角色ID的用户可修改任意订单,其他用户只能修改自己创建的订单
'order_edit_all_roles' => [0, 3],
// 医助创建订单权限:拥有以下角色ID的用户可修改任意订单,其他用户只能修改自己创建的订单
'order_edit_all_roles' => [0, 3, 6],
// 处方库:以下角色ID + 超级管理员(root) 可查看/编辑/删除全部;他人仅可管理自己创建的,公开处方对他人只读
'prescription_library_manage_all_roles' => [0, 3],
// 消费者处方单:以下角色 + root 可对「待审核」处方进行通过/驳回(驳回即作废处方)
'prescription_audit_roles' => [0, 3,6],
'prescription_audit_roles' => [0, 3, 6],
// 处方业务订单 internal_cost 等财务字段:以下角色 ID + root 可见
'prescription_order_finance_roles' => [0, 3],
'prescription_order_finance_roles' => [0, 3, 6],
// 业务订单「关联支付单」审核:以下角色 ID + root 可操作(可与财务角色一致)
'prescription_order_payment_audit_roles' => [0, 3],
'prescription_order_payment_audit_roles' => [0, 3, 6],
// 腾讯云实时音视频(TRTC)配置
'trtc' => [
+15
View File
@@ -0,0 +1,15 @@
-- 音视频权限拒绝日志表
CREATE TABLE IF NOT EXISTS `zyt_av_permission_log` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`patient_id` varchar(64) NOT NULL DEFAULT '' COMMENT '患者IDpatient_xxx格式)',
`doctor_id` varchar(64) NOT NULL DEFAULT '' COMMENT '医生IDdoctor_xxx格式)',
`denied_scope` varchar(32) NOT NULL DEFAULT '' COMMENT '被拒绝的权限 scopecamera/record',
`scene` varchar(32) NOT NULL DEFAULT '' COMMENT '触发场景(makeCall / incoming',
`action` varchar(16) NOT NULL DEFAULT 'cancel' COMMENT '用户行为:cancel=取消 / open_setting=去设置',
`wx_version` varchar(32) NOT NULL DEFAULT '' COMMENT '微信基础库版本',
`create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间(Unix秒)',
PRIMARY KEY (`id`),
KEY `idx_patient` (`patient_id`),
KEY `idx_doctor` (`doctor_id`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='音视频权限拒绝行为日志';
@@ -0,0 +1,13 @@
-- 处方业务订单详情:查看处方药材明细(无此权限时接口不返回 herbs)
SET @po_menu_id := (SELECT id FROM zyt_system_menu WHERE perms = 'tcm.prescriptionOrder/lists' LIMIT 1);
INSERT INTO zyt_system_menu (
pid, type, name, icon, sort, perms, paths, component,
selected, params, is_cache, is_show, is_disable, create_time, update_time
)
SELECT
@po_menu_id, 'A', '药材明细', '', 9, 'tcm.prescriptionOrder/viewRxHerbs', '', '',
'', '', 0, 1, 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()
FROM DUAL
WHERE @po_menu_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM zyt_system_menu WHERE perms = 'tcm.prescriptionOrder/viewRxHerbs');
BIN
View File
Binary file not shown.