From 707e4888adcdf45d626ae92a4b76eb48df74c71d Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 2 Jun 2026 10:16:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/src/api/tcm.ts | 7 +- .../consumer/prescription/order_list.vue | 109 +++++++++++++++++- .../consumer/prescription/order_list_h5.vue | 108 ++++++++++++++++- .../tcm/PrescriptionOrderController.php | 19 +++ .../logic/tcm/PrescriptionOrderLogic.php | 86 ++++++++++++-- .../tcm/PrescriptionOrderValidate.php | 5 +- 6 files changed, 316 insertions(+), 18 deletions(-) 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'],