mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 13:31:04 +00:00
fix(售后): 修复确认收货状态与退款流水号覆盖
- 退货退款/换货确认收货后按 receive_time 展示待退款/待补发,并切换确认收货、执行退款、补发按钮 - 后台填写的退款流水号写入售后单,退款完成时优先使用,不再被系统退款单号覆盖 - 退款流水号限制 64 个字符
This commit is contained in:
parent
b2fbcf257a
commit
137b719187
@ -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']) : '';
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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, [
|
||||
|
||||
@ -364,7 +364,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="退款流水号">
|
||||
<el-input v-model="refundForm.refund_trade_no" placeholder="退款交易流水号(选填)"></el-input>
|
||||
<el-input v-model="refundForm.refund_trade_no" maxlength="64" placeholder="退款交易流水号(选填)"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer
|
||||
@ -525,26 +525,32 @@ const hasResendDelivery = computed(() => {
|
||||
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;
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user