diff --git a/admin/src/api/tcm.ts b/admin/src/api/tcm.ts
index b504c09b..6a587cd0 100644
--- a/admin/src/api/tcm.ts
+++ b/admin/src/api/tcm.ts
@@ -482,11 +482,16 @@ export function prescriptionOrderRequestCompletion(params: { id: number }) {
return request.post({ url: '/tcm.prescriptionOrder/requestCompletion', params })
}
-/** 将「已发货/已签收」且支付审核通过的订单结案(3=已完成 或 7-12 业务状态) */
+/** 将「已发货/已签收」且支付审核通过的订单结案(3=已完成 或 7-12 业务状态,不含退款) */
export function prescriptionOrderComplete(params: { id: number; fulfillment_status: number }) {
return request.post({ url: '/tcm.prescriptionOrder/complete', params })
}
+/** 业务订单退款(须填写原因;关联收款单标记已退款、已付总额清零) */
+export function prescriptionOrderRefund(params: { id: number; reason: string }) {
+ return request.post({ url: '/tcm.prescriptionOrder/refund', params })
+}
+
/** 甘草药管家:中药处方下单(服务端 CTM_PREVIEW → CTM_SUBMIT_RECIPEL) */
export function prescriptionOrderSubmitGancaoRecipel(params: { id: number }) {
return request.post({ url: '/tcm.prescriptionOrder/submitGancaoRecipel', params })
diff --git a/admin/src/views/consumer/prescription/order_list.vue b/admin/src/views/consumer/prescription/order_list.vue
index 94a6cbaa..d79c3dbe 100644
--- a/admin/src/views/consumer/prescription/order_list.vue
+++ b/admin/src/views/consumer/prescription/order_list.vue
@@ -530,6 +530,13 @@
link
@click="confirmComplete(row)"
>完成订单
+ 退款
完成订单
+
+ 退款
+
@@ -2103,6 +2120,41 @@
+
+
+
+
+
+
+
+
+
+ 取消
+
+ 确认退款
+
+
+
()
+const refundOrderForm = reactive({ reason: '' })
+const refundOrderRules: FormRules = {
+ reason: [{ required: true, message: '请填写退款原因', trigger: 'blur' }]
+}
+
+function onRefundOrderDialogClosed() {
+ refundOrderId.value = 0
+ refundOrderForm.reason = ''
+ refundOrderFormRef.value?.clearValidate()
+}
+
+function openRefundOrder(row: { id: number }) {
+ refundOrderId.value = row.id
+ refundOrderForm.reason = ''
+ refundOrderDialogVisible.value = true
+}
+
+async function submitRefundOrder() {
+ const id = refundOrderId.value
+ if (!id) return
+ const valid = await refundOrderFormRef.value?.validate().catch(() => false)
+ if (!valid) return
+ refundOrderSubmitting.value = true
+ try {
+ await prescriptionOrderRefund({ id, reason: refundOrderForm.reason.trim() })
+ feedback.msgSuccess('退款成功')
+ refundOrderDialogVisible.value = false
+ getLists()
+ if (detailVisible.value && detailData.value?.id === id) {
+ try {
+ const r: any = await prescriptionOrderDetail({ id })
+ const nd = r?.data ?? r ?? null
+ if (nd) detailData.value = nd
+ await fetchLogs(id)
+ } catch { /* 静默 */ }
+ }
+ } catch {
+ /* 拦截器已提示 */
+ } finally {
+ refundOrderSubmitting.value = false
+ }
+}
+
// ─── 补齐支付单(已发货状态)────────────────────────────────────────────
const addPayOrderVisible = ref(false)
const addPayOrderSaving = ref(false)
diff --git a/admin/src/views/consumer/prescription/order_list_h5.vue b/admin/src/views/consumer/prescription/order_list_h5.vue
index 75d797db..869ecbed 100644
--- a/admin/src/views/consumer/prescription/order_list_h5.vue
+++ b/admin/src/views/consumer/prescription/order_list_h5.vue
@@ -498,6 +498,9 @@
补齐支付单
完成订单
+
+ 退款
+
上传药方
撤回订单
@@ -612,6 +615,16 @@
>
完成订单
+
+ 退款
+
@@ -1854,6 +1867,42 @@
+
+
+
+
+
+
+
+
+
+ 取消
+
+ 确认退款
+
+
+
{
// H5: 判断列表卡片「更多」下拉是否需要展示
function hasMoreCardActions(row: Record) {
- return canAddPayOrderRow(row) || canCompleteRow(row) || canUploadGancaoRow(row) || canWithdrawRow(row)
+ return canAddPayOrderRow(row) || canCompleteRow(row) || canRefundRow(row) || canUploadGancaoRow(row) || canWithdrawRow(row)
}
// H5: 处理列表卡片「更多」下拉指令
function handleCardMoreCommand(cmd: string, row: Record) {
if (cmd === 'addPayOrder') openAddPayOrder(row as any)
else if (cmd === 'complete') confirmComplete(row as any)
+ else if (cmd === 'refund') openRefundOrder(row as any)
else if (cmd === 'submitGancao') confirmSubmitGancaoRecipel(row as any)
else if (cmd === 'withdraw') confirmWithdraw(row as any)
}
@@ -3607,7 +3662,8 @@ function logActionText(act: string) {
gancao_submit: '甘草下单',
patch_rx_patient: '处方患者信息',
update_amount: '修改订单金额',
- complete: '完成订单'
+ complete: '完成订单',
+ refund: '退款'
}
return m[act] || act
}
@@ -4553,7 +4609,6 @@ const completeOrderStatusOptions = [
{ value: 7, label: '进行中' },
{ value: 8, label: '暂不制药' },
{ value: 9, label: '拒收' },
- { value: 10, label: '退款' },
{ value: 11, label: '保留药方' },
{ value: 12, label: '制药缓发' }
] as const
@@ -4586,6 +4641,53 @@ async function submitCompleteOrder() {
}
}
+const refundOrderDialogVisible = ref(false)
+const refundOrderId = ref(0)
+const refundOrderSubmitting = ref(false)
+const refundOrderFormRef = ref()
+const refundOrderForm = reactive({ reason: '' })
+const refundOrderRules: FormRules = {
+ reason: [{ required: true, message: '请填写退款原因', trigger: 'blur' }]
+}
+
+function onRefundOrderDialogClosed() {
+ refundOrderId.value = 0
+ refundOrderForm.reason = ''
+ refundOrderFormRef.value?.clearValidate()
+}
+
+function openRefundOrder(row: { id: number }) {
+ refundOrderId.value = row.id
+ refundOrderForm.reason = ''
+ refundOrderDialogVisible.value = true
+}
+
+async function submitRefundOrder() {
+ const id = refundOrderId.value
+ if (!id) return
+ const valid = await refundOrderFormRef.value?.validate().catch(() => false)
+ if (!valid) return
+ refundOrderSubmitting.value = true
+ try {
+ await prescriptionOrderRefund({ id, reason: refundOrderForm.reason.trim() })
+ feedback.msgSuccess('退款成功')
+ refundOrderDialogVisible.value = false
+ getLists()
+ if (detailVisible.value && detailData.value?.id === id) {
+ try {
+ const r: any = await prescriptionOrderDetail({ id })
+ const nd = r?.data ?? r ?? null
+ if (nd) detailData.value = nd
+ await fetchLogs(id)
+ } catch { /* 静默 */ }
+ }
+ } catch {
+ /* 拦截器已提示 */
+ } finally {
+ refundOrderSubmitting.value = false
+ }
+}
+
// ─── 补齐支付单(已发货状态)────────────────────────────────────────────
const addPayOrderVisible = ref(false)
const addPayOrderSaving = ref(false)
diff --git a/server/app/adminapi/controller/tcm/PrescriptionOrderController.php b/server/app/adminapi/controller/tcm/PrescriptionOrderController.php
index a2dcc6f1..6733ca64 100755
--- a/server/app/adminapi/controller/tcm/PrescriptionOrderController.php
+++ b/server/app/adminapi/controller/tcm/PrescriptionOrderController.php
@@ -345,6 +345,25 @@ class PrescriptionOrderController extends BaseAdminController
return $this->success('操作成功', $result);
}
+ /**
+ * 业务订单退款(须填写原因;关联收款单标记已退款)
+ */
+ public function refund()
+ {
+ $params = (new PrescriptionOrderValidate())->post()->goCheck('refund');
+ $result = PrescriptionOrderLogic::refund(
+ (int) $params['id'],
+ (string) ($params['reason'] ?? ''),
+ $this->adminId,
+ $this->adminInfo
+ );
+ if ($result === false) {
+ return $this->fail(PrescriptionOrderLogic::getError());
+ }
+
+ return $this->success('退款成功', $result);
+ }
+
/**
* 甘草药管家:处方下单(CTM_PREVIEW → CTM_SUBMIT_RECIPEL)
*
diff --git a/server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php b/server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php
index b408247c..5f5f44ae 100755
--- a/server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php
+++ b/server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php
@@ -2534,8 +2534,8 @@ class PrescriptionOrderLogic
return $out;
}
- /** 完成订单时可选的履约终态:3=已完成,7-12=业务结案类状态 */
- private const COMPLETE_FULFILLMENT_TARGETS = [3, 7, 8, 9, 10, 11, 12];
+ /** 完成订单时可选的履约终态:3=已完成,7-12 业务结案类状态(退款 10 走独立 refund 接口) */
+ private const COMPLETE_FULFILLMENT_TARGETS = [3, 7, 8, 9, 11, 12];
/** 完成时是否按「实付=关联支付单金额」重算并清空诊单医助(与 clearDiagnosisAssistantOnComplete 的终态判断一致) */
private const COMPLETE_FULFILLMENT_TERMINAL = [3, 8, 9, 10, 11, 12];
@@ -2592,13 +2592,7 @@ class PrescriptionOrderLogic
$order->fulfillment_status = $targetFulfillmentStatus;
}
- $refundedPayCount = 0;
try {
- if ($targetFulfillmentStatus === 10 && $linkedPayOrderIds !== []) {
- $refundedPayCount = Order::whereIn('id', $linkedPayOrderIds)
- ->whereNull('delete_time')
- ->update(['status' => 4]);
- }
$order->save();
} catch (\Throwable $e) {
self::$error = $e->getMessage();
@@ -2614,9 +2608,6 @@ class PrescriptionOrderLogic
$logLine = '订单完成,状态变更为「' . $label . '」,实付金额更新为 ¥' . $linkedPayPaidTotal . $unassignSummary;
} else {
$logLine = '订单处理完成,状态变更为「' . $label . '」' . $unassignSummary;
- if ($targetFulfillmentStatus === 10 && $refundedPayCount > 0) {
- $logLine .= ',关联支付单 ' . $refundedPayCount . ' 笔已标记为已退款';
- }
}
self::writeLog(
$id,
@@ -2634,6 +2625,79 @@ class PrescriptionOrderLogic
return $out;
}
+ /**
+ * 业务订单退款:须填写原因;关联收款单标记为已退款,实付清零。
+ *
+ * @return array|false
+ */
+ public static function refund(int $id, string $reason, int $adminId, array $adminInfo)
+ {
+ self::$error = '';
+ $reason = trim($reason);
+ if ($reason === '') {
+ 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;
+ }
+ $curFs = (int) $order->fulfillment_status;
+ if ($curFs !== 5 && $curFs !== 6) {
+ self::$error = '仅「已发货」或「已签收」状态可退款';
+
+ return false;
+ }
+ if ((int) $order->payment_slip_audit_status !== 1) {
+ self::$error = '支付单审核尚未通过,不可退款';
+
+ return false;
+ }
+
+ $linkedPayOrderIds = self::linkedPayOrderIdList($id);
+ $refundedPayCount = 0;
+
+ try {
+ if ($linkedPayOrderIds !== []) {
+ $refundedPayCount = Order::whereIn('id', $linkedPayOrderIds)
+ ->whereNull('delete_time')
+ ->update(['status' => 4]);
+ }
+ $order->fulfillment_status = 10;
+ $order->paid = 0;
+ $order->save();
+ } catch (\Throwable $e) {
+ self::$error = $e->getMessage();
+
+ return false;
+ }
+
+ $unassignSummary = self::clearDiagnosisAssistantOnComplete((int) $order->diagnosis_id);
+ $logLine = '订单退款,状态变更为「退款」;原因:' . $reason;
+ if ($refundedPayCount > 0) {
+ $logLine .= ';关联支付单 ' . $refundedPayCount . ' 笔已标记为已退款';
+ }
+ $logLine .= ';已付总额清零' . $unassignSummary;
+
+ self::writeLog($id, $adminId, $adminInfo, 'refund', $logLine);
+
+ $out = $order->toArray();
+ self::maskInternalCostIfNeeded($out, $adminInfo);
+ self::maskRemarkExtraIfNeeded($out, $adminInfo);
+ self::attachLinkedPayOrders($out);
+
+ return $out;
+ }
+
public static function fulfillmentStatusLabel(int $fs): string
{
$m = [
diff --git a/server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php b/server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php
index 00c67ddb..cb5a4ad0 100755
--- a/server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php
+++ b/server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php
@@ -32,7 +32,8 @@ class PrescriptionOrderValidate extends BaseValidate
'remark_assistant' => 'max:500',
'action' => 'require|in:approve,reject',
'remark' => 'max:500',
- 'fulfillment_status' => 'require|integer|in:3,7,8,9,10,11,12',
+ 'fulfillment_status' => 'require|integer|in:3,7,8,9,11,12',
+ 'reason' => 'require|max:500',
'patient_name' => 'require|max:50',
'phone' => 'require|max:20',
'phone_tail' => 'max:20|regex:/^\\d*$/',
@@ -50,6 +51,7 @@ class PrescriptionOrderValidate extends BaseValidate
'patient_name.require' => '请输入患者姓名',
'phone.require' => '请输入手机号',
'phone_tail.regex' => '手机后四位仅支持数字',
+ 'reason.require' => '请填写退款原因',
];
protected $scene = [
@@ -75,6 +77,7 @@ class PrescriptionOrderValidate extends BaseValidate
'linkPayOrder' => ['id', 'pay_order_id'],
'requestCompletion' => ['id'],
'complete' => ['id', 'fulfillment_status'],
+ 'refund' => ['id', 'reason'],
'submitGancaoRecipel' => ['id'],
'previewGancaoRecipel' => ['id'],
'patchPrescriptionPatient' => ['id', 'patient_name', 'phone'],