From 9afe8c135d54c82ecc11905065a7f19130998865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=9D=E5=8D=9C?= Date: Sun, 15 Mar 2026 06:39:22 +0000 Subject: [PATCH] platform orders: batch mark activated guard renewal missing subscription --- .../Admin/PlatformOrderController.php | 36 ++++++- ...newalMissingSubscriptionShouldSkipTest.php | 98 +++++++++++++++++++ ...minPlatformOrderBatchMarkActivatedTest.php | 6 +- 3 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 tests/Feature/AdminPlatformOrderBatchMarkActivatedRenewalMissingSubscriptionShouldSkipTest.php diff --git a/app/Http/Controllers/Admin/PlatformOrderController.php b/app/Http/Controllers/Admin/PlatformOrderController.php index 284bad3..d471245 100644 --- a/app/Http/Controllers/Admin/PlatformOrderController.php +++ b/app/Http/Controllers/Admin/PlatformOrderController.php @@ -1758,6 +1758,8 @@ class PlatformOrderController extends Controller $processed = $orders->count(); $success = 0; + $failed = 0; + $failedReasonCounts = []; $nowStr = now()->toDateTimeString(); // 筛选摘要:用于审计记录(便于追溯本次批量处理口径) @@ -1780,6 +1782,27 @@ class PlatformOrderController extends Controller continue; } + // 治理优先:续费单必须绑定订阅(兼容历史脏数据/手工改库等场景) + if ((string) ($order->order_type ?? '') === 'renewal' && ! (int) ($order->site_subscription_id ?? 0)) { + $failed++; + + $reason = '续费单未绑定订阅(site_subscription_id 为空),不允许批量仅标记为已生效。'; + $failedReasonCounts[$reason] = ($failedReasonCounts[$reason] ?? 0) + 1; + + $meta = (array) ($order->meta ?? []); + data_set($meta, 'batch_mark_activated_error', [ + 'message' => $reason, + 'at' => $nowStr, + 'admin_id' => $admin->id, + 'scope' => $scope, + 'filters' => $filterSummary, + ]); + $order->meta = $meta; + $order->save(); + + continue; + } + $order->status = 'activated'; $order->activated_at = $order->activated_at ?: now(); @@ -1808,7 +1831,18 @@ class PlatformOrderController extends Controller $success++; } - $msg = '批量仅标记为已生效完成:成功 ' . $success . ' 条(命中 ' . $matchedTotal . ' 条,本次处理 ' . $processed . ' 条,limit=' . $limit . ')'; + $msg = '批量仅标记为已生效完成:成功 ' . $success . ' 条,失败 ' . $failed . ' 条(命中 ' . $matchedTotal . ' 条,本次处理 ' . $processed . ' 条,limit=' . $limit . ')'; + + if ($failed > 0 && count($failedReasonCounts) > 0) { + arsort($failedReasonCounts); + $top = array_slice($failedReasonCounts, 0, 3, true); + $topText = collect($top)->map(function ($cnt, $reason) { + $reason = mb_substr((string) $reason, 0, 60); + return $reason . '(' . $cnt . ')'; + })->implode(';'); + + $msg .= ';失败原因Top:' . $topText; + } return redirect()->back()->with('success', $msg); } diff --git a/tests/Feature/AdminPlatformOrderBatchMarkActivatedRenewalMissingSubscriptionShouldSkipTest.php b/tests/Feature/AdminPlatformOrderBatchMarkActivatedRenewalMissingSubscriptionShouldSkipTest.php new file mode 100644 index 0000000..984ec34 --- /dev/null +++ b/tests/Feature/AdminPlatformOrderBatchMarkActivatedRenewalMissingSubscriptionShouldSkipTest.php @@ -0,0 +1,98 @@ +seed(); + + $this->post('/admin/login', [ + 'email' => 'platform.admin@demo.local', + 'password' => 'Platform@123456', + ])->assertRedirect('/admin'); + } + + public function test_batch_mark_activated_should_skip_renewal_orders_missing_subscription_and_write_error_meta(): void + { + $this->loginAsPlatformAdmin(); + + $merchant = Merchant::query()->firstOrFail(); + $plan = Plan::query()->create([ + 'code' => 'batch_mark_activated_renewal_missing_sub_plan', + 'name' => '批量仅标记生效跳过(续费缺订阅)测试套餐', + 'billing_cycle' => 'monthly', + 'price' => 10, + 'list_price' => 10, + 'status' => 'active', + 'sort' => 10, + 'published_at' => now(), + ]); + + $bad = PlatformOrder::query()->create([ + 'merchant_id' => $merchant->id, + 'plan_id' => $plan->id, + 'order_no' => 'PO_BATCH_MARK_ACT_SKIP_RENEW_NO_SUB_0001', + 'order_type' => 'renewal', + 'status' => 'pending', + 'payment_status' => 'paid', + 'plan_name' => $plan->name, + 'billing_cycle' => $plan->billing_cycle, + 'period_months' => 1, + 'quantity' => 1, + 'payable_amount' => 10, + 'paid_amount' => 10, + 'placed_at' => now()->subMinutes(10), + 'paid_at' => now()->subMinutes(5), + 'meta' => [], + ]); + + $ok = PlatformOrder::query()->create([ + 'merchant_id' => $merchant->id, + 'plan_id' => $plan->id, + 'order_no' => 'PO_BATCH_MARK_ACT_SKIP_OK_0002', + 'order_type' => 'new_purchase', + 'status' => 'pending', + 'payment_status' => 'paid', + 'plan_name' => $plan->name, + 'billing_cycle' => $plan->billing_cycle, + 'period_months' => 1, + 'quantity' => 1, + 'payable_amount' => 10, + 'paid_amount' => 10, + 'placed_at' => now()->subMinutes(9), + 'paid_at' => now()->subMinutes(4), + 'meta' => [], + ]); + + $res = $this->post('/admin/platform-orders/batch-mark-activated', [ + 'scope' => 'filtered', + 'payment_status' => 'paid', + 'status' => 'pending', + 'limit' => 50, + ]); + + $res->assertRedirect()->assertSessionHas('success'); + + $bad->refresh(); + $ok->refresh(); + + $this->assertSame('pending', $bad->status); + $this->assertSame('activated', $ok->status); + + $this->assertNotEmpty(data_get($bad->meta, 'batch_mark_activated_error.message')); + $msg = (string) $res->getSession()->get('success'); + $this->assertStringContainsString('失败', $msg); + $this->assertStringContainsString('失败原因Top', $msg); + $this->assertStringContainsString('续费单未绑定订阅', $msg); + } +} diff --git a/tests/Feature/AdminPlatformOrderBatchMarkActivatedTest.php b/tests/Feature/AdminPlatformOrderBatchMarkActivatedTest.php index 9e50109..d879cd8 100644 --- a/tests/Feature/AdminPlatformOrderBatchMarkActivatedTest.php +++ b/tests/Feature/AdminPlatformOrderBatchMarkActivatedTest.php @@ -42,7 +42,7 @@ class AdminPlatformOrderBatchMarkActivatedTest extends TestCase 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_MARK_0001', - 'order_type' => 'renewal', + 'order_type' => 'new_purchase', 'status' => 'pending', 'payment_status' => 'paid', 'plan_name' => $plan->name, @@ -60,7 +60,7 @@ class AdminPlatformOrderBatchMarkActivatedTest extends TestCase 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_MARK_0002', - 'order_type' => 'renewal', + 'order_type' => 'new_purchase', 'status' => 'pending', 'payment_status' => 'paid', 'plan_name' => $plan->name, @@ -79,7 +79,7 @@ class AdminPlatformOrderBatchMarkActivatedTest extends TestCase 'merchant_id' => $merchant->id, 'plan_id' => $plan->id, 'order_no' => 'PO_BATCH_MARK_0003', - 'order_type' => 'renewal', + 'order_type' => 'new_purchase', 'status' => 'pending', 'payment_status' => 'unpaid', 'plan_name' => $plan->name,