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
@@ -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) {
// 忽略日志写入错误
}
}
}