diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index ffa8e6bb..9172eda1 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -338,11 +338,29 @@ class Pages extends Controller ? app(TmallTaobaoSpreadBillRepository::class) : app(PinduoduoSpreadBillRepository::class); $scope = $isTaobao ? 'taobao-alipay-bills.list' : 'pinduoduo-bills.list'; - $result = $this->upstreamCaches->forSource('jiansun')->remember( - $scope, - ['view' => 'monthly-balances', 'filters' => $filters, 'page' => 1, 'limit' => 5000, 'export' => true], - fn () => $repository->paginateMonthlyBalances($filters['store_name'], $filters['month'], 1, 5000), - ); + $redirectParameters = array_filter([ + 'section' => $section, + 'page' => $page, + 'tab' => $tab, + 'store_name' => $storeName, + 'month' => $month, + ], static fn (mixed $value): bool => $value !== ''); + + try { + $result = $this->upstreamCaches->forSource('jiansun')->remember( + $scope, + ['view' => 'monthly-balances', 'filters' => $filters, 'page' => 1, 'limit' => 5000, 'export' => true], + fn () => $repository->paginateMonthlyBalances($filters['store_name'], $filters['month'], 1, 5000), + ); + } catch (ServiceUnavailableHttpException) { + return redirect()->route('business.pages.show', $redirectParameters) + ->with('error', '首份共享快照正在准备,系统已登记刷新请求;请稍后刷新页面后再导出。'); + } catch (Throwable $exception) { + report($exception); + + return redirect()->route('business.pages.show', $redirectParameters) + ->with('error', '余额数据导出暂时无法完成,请稍后重试。'); + } $rows = $result['rows'] ?? []; return response()->streamDownload(function () use ($rows): void { diff --git a/resources/views/business/pages/show.blade.php b/resources/views/business/pages/show.blade.php index fb9f8657..36d2e7fb 100644 --- a/resources/views/business/pages/show.blade.php +++ b/resources/views/business/pages/show.blade.php @@ -411,6 +411,10 @@
{{ session('success') }}
@endif + @if (is_string(session('error'))) + + @endif + @if ($errors->has('import')) @endif diff --git a/tests/Feature/Business/MarketingAccountBillsTabsTest.php b/tests/Feature/Business/MarketingAccountBillsTabsTest.php index 8e46a15e..e5b0c98e 100644 --- a/tests/Feature/Business/MarketingAccountBillsTabsTest.php +++ b/tests/Feature/Business/MarketingAccountBillsTabsTest.php @@ -6,6 +6,7 @@ use App\Models\Auth\Permission; use Mockery; use Modules\EcommerceFinance\Repositories\JdWalletBillRepository; use Modules\EcommerceFinance\Repositories\PinduoduoSpreadBillRepository; +use Modules\EcommerceFinance\Repositories\TmallTaobaoSpreadBillRepository; use Modules\EcommerceFinance\Services\UpstreamQueryCache; use Modules\EcommerceFinance\Services\UpstreamQueryCacheFactory; use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; @@ -156,6 +157,71 @@ class MarketingAccountBillsTabsTest extends FeatureTestCase ], $scopes); } + public function testMarketingAccountBalanceExportColdStartReturnsActionableRedirect(): void + { + $this->allowMarketingAccountBalancePage(); + $cache = Mockery::mock(UpstreamQueryCache::class); + $cache->shouldReceive('remember') + ->once() + ->withArgs(function (string $scope, array $parameters, callable $resolver): bool { + return $scope === 'taobao-alipay-bills.list' + && ($parameters['export'] ?? false) === true; + }) + ->andThrow(new ServiceUnavailableHttpException(30, '首份共享快照正在准备。')); + + $factory = Mockery::mock(UpstreamQueryCacheFactory::class); + $factory->shouldReceive('forSource') + ->once() + ->with('jiansun') + ->andReturn($cache); + + $this->app->instance(UpstreamQueryCacheFactory::class, $factory); + $this->app->instance(TmallTaobaoSpreadBillRepository::class, Mockery::mock(TmallTaobaoSpreadBillRepository::class)); + + $this->loginAs() + ->get(route('business.pages.export', [ + 'section' => 'billing-transactions', + 'page' => 'marketing-account-balance', + 'tab' => 'taobao-alipay', + 'store_name' => '测试店铺', + 'month' => '2026-08', + ])) + ->assertRedirect(route('business.pages.show', [ + 'section' => 'billing-transactions', + 'page' => 'marketing-account-balance', + 'tab' => 'taobao-alipay', + 'store_name' => '测试店铺', + 'month' => '2026-08', + ])) + ->assertSessionHas('error', '首份共享快照正在准备,系统已登记刷新请求;请稍后刷新页面后再导出。'); + } + + public function testMarketingAccountBalanceExportUnexpectedFailureReturnsRedactedRedirect(): void + { + $this->allowMarketingAccountBalancePage(); + $cache = Mockery::mock(UpstreamQueryCache::class); + $cache->shouldReceive('remember')->once()->andThrow(new \RuntimeException('sensitive DSN details')); + + $factory = Mockery::mock(UpstreamQueryCacheFactory::class); + $factory->shouldReceive('forSource')->once()->with('jiansun')->andReturn($cache); + + $this->app->instance(UpstreamQueryCacheFactory::class, $factory); + $this->app->instance(TmallTaobaoSpreadBillRepository::class, Mockery::mock(TmallTaobaoSpreadBillRepository::class)); + + $this->loginAs() + ->get(route('business.pages.export', [ + 'section' => 'billing-transactions', + 'page' => 'marketing-account-balance', + 'tab' => 'taobao-alipay', + ])) + ->assertRedirect(route('business.pages.show', [ + 'section' => 'billing-transactions', + 'page' => 'marketing-account-balance', + 'tab' => 'taobao-alipay', + ])) + ->assertSessionHas('error', '余额数据导出暂时无法完成,请稍后重试。'); + } + private function allowMarketingAccountBillsPage(): void { $this->user->update(['locale' => 'zh-CN']);