fix(after-sale): 售后工单已完结即不可再申请,不看订单是否另标已退款

原来的重复申请闸门只看「进行中」的工单(find_active = pending/processing),
于是工单已 completed、但订单没被另标「已退款」时,订单还能再发起一次售后。
已退款订单之所以不能再申请,是因为 status_group 变了导致 enabled 为 false,
属于间接效果;工单层面的规则一直是漏的。

- create() 改为以 latest_for_order() 为准:completed 直接挡掉;待审核/处理中
  仍走同人同因幂等(返回原工单),rejected / cancelled 放行再次发起
- after_sale_state() 的 can_apply 同步收紧,与 can_reapply 同一口径:
  有工单时只有 rejected / cancelled 才放行,completed 不再漏过去
- 界面上售后详细弹窗的「再次申请」本就只对 rejected / cancelled 渲染,
  这条改动把服务端与 API 口径补齐,堵住绕过前端直接打 REST 的口子

harness 补两个场景:completed 挡住而 rejected / cancelled 放行;待审核工单
同因幂等返回原单、换因挡住。

验证:tests/widget-system/ 462 项、tests/admin/ 76 项通过;after-sale-log-harness
7/7(新增两场景);dev harness 渲染两个后台页面与 unchecked 处理场景均正常。
未做浏览器视觉验收。
This commit is contained in:
kate522 committed 2026-10-09 18:25:02 +08:00
1 parent 36b3ba6f45
commit c955fd35f2
5 files changed
+69 -8

No files matched your search

+16 -5
View File
@@ -149,12 +149,23 @@ final class AfterSaleService {
try {
$order = OrderRepository::get_instance()->get_user_order($user_id, $order_number);
if (!$order || (string) ($order['status'] ?? '') !== Order::STATUS_PAID) return new \WP_Error('aksrt_after_sale_order', __('只有已支付订单可以申请售后', 'aksrt'), ['status' => 409]);
$active = $this->find_active($order_number);
if ($active) {
if ((int) ($active['user_id'] ?? 0) === $user_id && hash_equals((string) ($active['reason'] ?? ''), $reason)) {
return $active;
// 以「最新一条工单」为准,而不是只看进行中的:已完结的工单同样要挡住再次申请。
// 订单有没有另标「已退款」不影响这条——工单 completed 即售后已终结。
$latest = $this->latest_for_order($order_number);
if ($latest !== null) {
$latest_status = (string) ($latest['status'] ?? '');
// 待审核 / 处理中:工单仍在流转,挡住重复申请;同人同因视为重复提交,幂等返回原工单。
if (in_array($latest_status, [self::STATUS_PENDING, self::STATUS_PROCESSING], true)) {
if ((int) ($latest['user_id'] ?? 0) === $user_id && hash_equals((string) ($latest['reason'] ?? ''), $reason)) {
return $latest;
}
return new \WP_Error('aksrt_after_sale_exists', __('该订单已有未结束售后申请', 'aksrt'), ['status' => 409]);
}
return new \WP_Error('aksrt_after_sale_exists', __('该订单已有未结束售后申请', 'aksrt'), ['status' => 409]);
// 已完成:售后已终结,不再受理新的申请。
if ($latest_status === self::STATUS_COMPLETED) {
return new \WP_Error('aksrt_after_sale_exists', __('该订单的售后申请已完结,不能再次申请', 'aksrt'), ['status' => 409]);
}
// rejected / cancelled:允许补充资料后再次发起。
}
$amount = $this->refund_amount($order);
if ($amount === null) return new \WP_Error('aksrt_after_sale_amount', __('订单退款金额无效', 'aksrt'), ['status' => 400]);
+4 -1
View File
@@ -293,13 +293,16 @@ class UserOrders {
// - completed:退款已完成,工单终结,不允许再次申请。
$reapplyable_statuses = [AfterSaleService::STATUS_REJECTED, AfterSaleService::STATUS_CANCELLED];
$latest_status = (string) ($latest['status'] ?? '');
// 有工单时只有 rejected / cancelled 才放行再次申请:completed 的工单已终结,
// 订单有没有另标「已退款」都不影响——AfterSaleService::create() 按同一口径拦截。
$reapplyable = !$has_application || in_array($latest_status, $reapplyable_statuses, true);
return [
'enabled' => $enabled,
'expired' => (bool) $expired,
'has_application' => $has_application,
'open' => $open,
'can_apply' => $enabled && !$expired && !$open,
'can_apply' => $enabled && !$expired && !$open && $reapplyable,
'can_reapply' => $enabled && !$expired && $has_application && in_array($latest_status, $reapplyable_statuses, true),
'latest' => $has_application ? [
'status' => (string) ($latest['status'] ?? ''),