From 137b719187e8575caa0680c01c75768806ea6605 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=90=B4=E6=98=8A=E5=A4=A9?= <442384644@qq.com>
Date: Thu, 22 Oct 2026 13:18:51 +0800
Subject: [PATCH] =?UTF-8?q?fix(=E5=94=AE=E5=90=8E):=20=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E7=A1=AE=E8=AE=A4=E6=94=B6=E8=B4=A7=E7=8A=B6=E6=80=81=E4=B8=8E?=
=?UTF-8?q?=E9=80=80=E6=AC=BE=E6=B5=81=E6=B0=B4=E5=8F=B7=E8=A6=86=E7=9B=96?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 退货退款/换货确认收货后按 receive_time 展示待退款/待补发,并切换确认收货、执行退款、补发按钮
- 后台填写的退款流水号写入售后单,退款完成时优先使用,不再被系统退款单号覆盖
- 退款流水号限制 64 个字符
---
.../app/services/order/AfterSaleServices.php | 35 ++++++++++++++++---
.../services/order/RefundEffectServices.php | 10 ++++--
.../services/order/RefundOrderServices.php | 13 ++++++-
.../src/pages/order/afterSale/detail.vue | 20 +++++++----
4 files changed, 63 insertions(+), 15 deletions(-)
diff --git a/backend/app/services/order/AfterSaleServices.php b/backend/app/services/order/AfterSaleServices.php
index 7c98c9358..e63c4ad96 100644
--- a/backend/app/services/order/AfterSaleServices.php
+++ b/backend/app/services/order/AfterSaleServices.php
@@ -696,6 +696,29 @@ class AfterSaleServices extends BaseService
'completed' => '已完成',
];
+ /**
+ * 解析售后单展示状态文案
+ * 退货退款/换货在商家确认收货后仍沿用 receiving 状态(退款/补发动作依赖该状态),
+ * 展示层按 receive_time 细分为“待退款”“待补发”,避免确认收货后仍显示“待收货”。
+ * @param array|object $afterSale 售后单数据
+ * @return string
+ */
+ protected function resolveStatusText($afterSale): string
+ {
+ $data = is_object($afterSale) ? $afterSale->toArray() : (array)$afterSale;
+ $status = (string)($data['status'] ?? '');
+ $type = (string)($data['type'] ?? '');
+ if ($status === 'receiving' && (int)($data['receive_time'] ?? 0) > 0) {
+ if ($type === 'return_refund') {
+ return '待退款';
+ }
+ if ($type === 'exchange') {
+ return '待补发';
+ }
+ }
+ return self::STATUS_TEXT_MAP[$status] ?? $status;
+ }
+
/**
* 售后状态分组映射
* 用于用户端列表 tab 筛选:processing=处理中,completed=已完成,canceled=已取消
@@ -908,7 +931,7 @@ class AfterSaleServices extends BaseService
'type' => $type,
'type_text' => self::TYPE_TEXT_MAP[$type] ?? $type,
'status' => $status,
- 'status_text' => self::STATUS_TEXT_MAP[$status] ?? $status,
+ 'status_text' => $this->resolveStatusText($afterSale),
'refund_price' => (float)($afterSale['refund_price'] ?? 0),
'actual_refund_price' => (float)($afterSale['actual_refund_price'] ?? 0),
// 退款标的物与退还积分:积分订单售后展示退还积分数
@@ -933,6 +956,8 @@ class AfterSaleServices extends BaseService
],
'timeline' => $timeline,
'actions' => $actions,
+ // 商家确认收货时间:前端据此区分“待收货”与“待退款/待补发”
+ 'receive_time' => (int)($afterSale['receive_time'] ?? 0),
'add_time' => (int)($afterSale['add_time'] ?? 0),
];
}
@@ -1786,7 +1811,7 @@ class AfterSaleServices extends BaseService
$specOrders = [];
foreach ($list as &$item) {
$item['type_text'] = self::TYPE_TEXT_MAP[$item['type'] ?? ''] ?? ($item['type'] ?? '');
- $item['status_text'] = self::STATUS_TEXT_MAP[$item['status'] ?? ''] ?? ($item['status'] ?? '');
+ $item['status_text'] = $this->resolveStatusText($item);
$item['status_group'] = $this->resolveStatusGroup($item['status'] ?? '');
// 产品类型中文名:动态从注册中心获取,新增产品类型注册即生效
$productType = $item['product_type'] ?? '';
@@ -1945,7 +1970,7 @@ class AfterSaleServices extends BaseService
// 附加文案
foreach ($list as &$item) {
$item['type_text'] = self::TYPE_TEXT_MAP[$item['type'] ?? ''] ?? ($item['type'] ?? '');
- $item['status_text'] = self::STATUS_TEXT_MAP[$item['status'] ?? ''] ?? ($item['status'] ?? '');
+ $item['status_text'] = $this->resolveStatusText($item);
}
unset($item);
@@ -2507,7 +2532,7 @@ class AfterSaleServices extends BaseService
$refundStatus = 1;
}
$item['refund_status'] = $refundStatus;
- $item['refund_status_text'] = self::STATUS_TEXT_MAP[$status] ?? $status;
+ $item['refund_status_text'] = $this->resolveStatusText($item);
$item['refund_type_text'] = self::TYPE_TEXT_MAP[$item['type'] ?? ''] ?? ($item['type'] ?? '');
// 旧 _status 结构:-1=申请中 -2=已退款 -3=已拒绝
@@ -2560,7 +2585,7 @@ class AfterSaleServices extends BaseService
// 旧结构兼容键
$detail['order_id'] = $detail['after_sale_no'] ?? '';
$detail['store_order_id'] = (int)($detail['order_center_id'] ?? 0);
- $detail['refund_status_text'] = self::STATUS_TEXT_MAP[$detail['status'] ?? ''] ?? ($detail['status'] ?? '');
+ $detail['refund_status_text'] = $this->resolveStatusText($detail);
$detail['cartInfo'] = $detail['cart_info'] = $this->tidyOutItems($detail['product_snapshot'] ?? []);
$detail['refunded_price'] = $detail['actual_refund_price'] ?? '0.00';
$detail['_add_time'] = !empty($detail['add_time']) ? date('Y-m-d H:i:s', (int)$detail['add_time']) : '';
diff --git a/backend/app/services/order/RefundEffectServices.php b/backend/app/services/order/RefundEffectServices.php
index 7a35cb851..38ddfec1a 100644
--- a/backend/app/services/order/RefundEffectServices.php
+++ b/backend/app/services/order/RefundEffectServices.php
@@ -174,16 +174,22 @@ class RefundEffectServices extends BaseService implements RefundEffectContextInt
$handler->applyRefundEffects($this, $lockedArray, $afterSale, $order, $items);
$now = time();
+ // 后台手工填写的退款流水号优先;未填写时才回退渠道退款号/系统退款单号
+ $refundContext = (array)($lockedArray['context'] ?? []);
+ $manualTradeNo = trim((string)($refundContext['refund_trade_no'] ?? ''));
+ $refundTradeNo = $manualTradeNo !== ''
+ ? $manualTradeNo
+ : (string)($locked['channel_refund_id'] ?: $locked['refund_no']);
$this->afterSaleDao->update((int)$afterSale['id'], [
'status' => 'completed',
'actual_refund_price' => (string)$locked['amount'],
'refund_type' => (string)$locked['refund_method'],
- 'refund_trade_no' => (string)($locked['channel_refund_id'] ?: $locked['refund_no']),
+ 'refund_trade_no' => $refundTradeNo,
'refund_time' => $now,
'complete_time' => $now,
]);
- $handlerContext = (array)($lockedArray['context'] ?? []);
+ $handlerContext = $refundContext;
$handlerContext['order_no'] = (string)$afterSale['order_no'];
$handlerContext['refund_no'] = (string)$locked['refund_no'];
$handlerContext['total_refund_amount'] = $projectedTotal;
diff --git a/backend/app/services/order/RefundOrderServices.php b/backend/app/services/order/RefundOrderServices.php
index 447afdce0..86d896270 100644
--- a/backend/app/services/order/RefundOrderServices.php
+++ b/backend/app/services/order/RefundOrderServices.php
@@ -43,6 +43,12 @@ class RefundOrderServices extends BaseService
throw new \RuntimeException('退款金额格式错误');
}
$amount = bcadd((string)$amount, '0', 2);
+ // 后台选填的退款流水号:去空格并限制字段长度(eb_after_sale.refund_trade_no varchar(64))
+ $refundTradeNo = trim((string)($context['refund_trade_no'] ?? ''));
+ if ($refundTradeNo !== '' && mb_strlen($refundTradeNo) > 64) {
+ throw new \RuntimeException('退款流水号不能超过64个字符');
+ }
+ $context['refund_trade_no'] = $refundTradeNo;
$refund = $this->transaction(function () use ($afterSaleId, $amount, $context) {
$afterSale = $this->afterSaleDao->getByIdForUpdate($afterSaleId);
if (!$afterSale) {
@@ -115,7 +121,12 @@ class RefundOrderServices extends BaseService
'add_time' => time(),
'update_time' => time(),
]);
- $this->afterSaleDao->update($afterSaleId, ['status' => 'refunding']);
+ $afterSaleUpdate = ['status' => 'refunding'];
+ if ((string)($context['refund_trade_no'] ?? '') !== '') {
+ // 先落库后台填写的流水号,退款处理中即可在详情看到,成功回调也不会被覆盖
+ $afterSaleUpdate['refund_trade_no'] = (string)$context['refund_trade_no'];
+ }
+ $this->afterSaleDao->update($afterSaleId, $afterSaleUpdate);
/** @var OrderCenterServices $centerServices */
$centerServices = $this->app->make(OrderCenterServices::class);
$centerServices->syncStatus((string)$afterSale['order_no'], OrderCenter::STATUS_REFUNDING, [
diff --git a/frontend/admin/src/pages/order/afterSale/detail.vue b/frontend/admin/src/pages/order/afterSale/detail.vue
index 8f3a44763..07725fb67 100644
--- a/frontend/admin/src/pages/order/afterSale/detail.vue
+++ b/frontend/admin/src/pages/order/afterSale/detail.vue
@@ -364,7 +364,7 @@
-
+
{
return d && (d.type || d.no);
});
+/**
+ * 商家是否已确认收到退货
+ * 退货退款/换货确认收货后仍沿用 receiving 状态,用 receive_time 区分收货前后动作
+ */
+const isReceived = computed(() => Number((detail.value && detail.value.receive_time) || 0) > 0);
+
/**
* 是否可执行退款
* - 仅退款:audit_passed 状态可退款
- * - 退货退款:receiving 状态(确认收货后)可退款
+ * - 退货退款:receiving 状态且已确认收货后可退款
*/
const canRefund = computed(() => {
if (!detail.value) return false;
const { status, type } = detail.value;
if (type === 'refund_only' && status === 'audit_passed') return true;
- if (type === 'return_refund' && status === 'receiving') return true;
+ if (type === 'return_refund' && status === 'receiving' && isReceived.value) return true;
return false;
});
/**
- * 是否可确认收货(退货退款/换货类型,receiving 状态)
+ * 是否可确认收货(退货退款/换货类型,receiving 状态且尚未确认收货)
*/
const canReceive = computed(() => {
if (!detail.value) return false;
const { status, type } = detail.value;
- return status === 'receiving' && ['return_refund', 'exchange'].includes(type);
+ return status === 'receiving' && ['return_refund', 'exchange'].includes(type) && !isReceived.value;
});
/**
@@ -559,14 +565,14 @@ const canStartRepair = computed(() => {
/**
* 是否可补发商品
* - 补发类型:audit_passed 状态可补发
- * - 换货类型:receiving 状态(确认收货后)可补发
+ * - 换货类型:receiving 状态且已确认收货后可补发
* - 维修类型:repair_complete 状态可补发
*/
const canResend = computed(() => {
if (!detail.value) return false;
const { status, type } = detail.value;
if (type === 'resend' && status === 'audit_passed') return true;
- if (type === 'exchange' && status === 'receiving') return true;
+ if (type === 'exchange' && status === 'receiving' && isReceived.value) return true;
if (type === 'repair' && status === 'repair_complete') return true;
return false;
});