diff --git a/modules/EcommerceFinance/Repositories/TaobaoAlipaySettlementBillRepository.php b/modules/EcommerceFinance/Repositories/TaobaoAlipaySettlementBillRepository.php index ffd3d67e..b02654ea 100644 --- a/modules/EcommerceFinance/Repositories/TaobaoAlipaySettlementBillRepository.php +++ b/modules/EcommerceFinance/Repositories/TaobaoAlipaySettlementBillRepository.php @@ -98,6 +98,33 @@ class TaobaoAlipaySettlementBillRepository return $this->summaryForQuery($query, $filters); } + /** + * 复用总览期初查询已经缓存的店铺余额,避免店铺明细再次扫描全历史。 + * + * @return array + */ + public function storeOpeningBalances(?string $periodStart): array + { + $start = $this->date($periodStart); + if ($start === null) { + return []; + } + + $monthStart = $start->modify('first day of this month'); + $cutoff = $monthStart->format('Y-m-d') . ' 00:00:00'; + $importVersion = (string) ($this->connection->table(self::IMPORT_TABLE) + ->where('status', 'succeeded') + ->max('completed_at') ?? ''); + $cacheKey = $this->openingBalanceCacheKey($cutoff, '', $importVersion); + $opening = Cache::get($cacheKey); + if (! is_array($opening) || ! is_array($opening['stores'] ?? null)) { + $this->openingBalance(['start' => $periodStart]); + $opening = Cache::get($cacheKey); + } + + return is_array($opening['stores'] ?? null) ? $opening['stores'] : []; + } + /** * @return array{income:float,expense:float,opening_balance:float,opening_balance_date:?string,opening_balance_source:string,balance:float,count:int} */ @@ -223,11 +250,7 @@ class TaobaoAlipaySettlementBillRepository $importVersion = (string) ($this->connection->table(self::IMPORT_TABLE) ->where('status', 'succeeded') ->max('completed_at') ?? ''); - $cacheKey = 'taobao-alipay-opening-balance:v1:' . hash('sha256', implode('|', [ - $cutoff, - $store, - $importVersion, - ])); + $cacheKey = $this->openingBalanceCacheKey($cutoff, $store, $importVersion); $opening = Cache::remember( $cacheKey, @@ -259,9 +282,20 @@ class TaobaoAlipaySettlementBillRepository ->get(); if ($latestRows->isNotEmpty()) { + $stores = []; + foreach ($latestRows as $row) { + $storeName = trim((string) ($row->store_name ?? '')); + $storeName = $storeName !== '' ? $storeName : '未标注店铺'; + $stores[$storeName] = round( + (float) ($stores[$storeName] ?? 0) + (float) $row->balance_amount, + 2, + ); + } + return [ 'balance' => round((float) $latestRows->sum('balance_amount'), 2), 'source' => 'previous_period', + 'stores' => $stores, ]; } @@ -291,6 +325,18 @@ class TaobaoAlipaySettlementBillRepository ->fromSub($firstRows, 'first_balances') ->where('row_rank', 1) ->get(); + $stores = []; + foreach ($first as $row) { + $storeName = trim((string) ($row->store_name ?? '')); + $storeName = $storeName !== '' ? $storeName : '未标注店铺'; + $stores[$storeName] = round( + (float) ($stores[$storeName] ?? 0) + + (float) $row->balance_amount + - (float) $row->income_amount + + (float) $row->expense_amount, + 2, + ); + } return [ 'balance' => round((float) $first->sum( @@ -299,6 +345,7 @@ class TaobaoAlipaySettlementBillRepository + (float) $row->expense_amount, ), 2), 'source' => 'first_row_estimate', + 'stores' => $stores, ]; }, ); @@ -356,4 +403,13 @@ class TaobaoAlipaySettlementBillRepository { return str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $value); } + + private function openingBalanceCacheKey(string $cutoff, string $store, string $importVersion): string + { + return 'taobao-alipay-opening-balance:v2:' . hash('sha256', implode('|', [ + $cutoff, + $store, + $importVersion, + ])); + } } diff --git a/modules/EcommerceFinance/Services/StoreReceivingBalanceOverviewService.php b/modules/EcommerceFinance/Services/StoreReceivingBalanceOverviewService.php index 65c3ac6a..8672a0ba 100644 --- a/modules/EcommerceFinance/Services/StoreReceivingBalanceOverviewService.php +++ b/modules/EcommerceFinance/Services/StoreReceivingBalanceOverviewService.php @@ -20,6 +20,7 @@ use Modules\EcommerceFinance\Repositories\PinduoduoSettlementBillRepository; use Modules\EcommerceFinance\Repositories\StoreReceivingAlipayBillMirrorRepository; use Modules\EcommerceFinance\Repositories\StoreReceivingDouyinBillMirrorRepository; use Modules\EcommerceFinance\Repositories\StoreReceivingJdWalletBillRepository; +use Modules\EcommerceFinance\Repositories\TaobaoAlipaySettlementBillRepository; use Modules\EcommerceFinance\Repositories\TaobaoWechatOpeningBalanceRepository; use Modules\EcommerceFinance\Repositories\TaobaoWechatSettlementBillRepository; use Modules\EcommerceFinance\Repositories\VipshopJxOpeningBalanceRepository; @@ -623,77 +624,8 @@ class StoreReceivingBalanceOverviewService ->where('occurred_at', '>=', $start . ' 00:00:00') ->where('occurred_at', '<', $this->dayAfter($end) . ' 00:00:00'), ); - $cutoff = $start . ' 00:00:00'; - $periodEnd = $this->dayAfter($end) . ' 00:00:00'; - $ranked = $connection->table(self::TAOBAO_ALIPAY_ROW_TABLE) - ->where('occurred_at', '<', $cutoff) - ->whereNotNull('alipay_account') - ->where('alipay_account', '<>', '') - ->select([ - 'store_name', - 'alipay_account', - 'occurred_at', - 'source_row_number', - 'income_amount', - 'expense_amount', - 'balance_amount', - ]) - ->selectRaw( - 'ROW_NUMBER() OVER (' . - 'PARTITION BY alipay_account ' . - 'ORDER BY occurred_at DESC, source_row_number DESC' . - ') AS row_rank', - ); - $latestRows = $connection->query() - ->fromSub($ranked, 'latest_store_balances') - ->where('row_rank', 1) - ->get(); - $opening = []; - - if ($latestRows->isNotEmpty()) { - foreach ($latestRows as $row) { - $storeName = $this->storeName($row->store_name); - $opening[$storeName] = round( - (float) ($opening[$storeName] ?? 0) + (float) $row->balance_amount, - 2, - ); - } - } else { - $firstRanked = $connection->table(self::TAOBAO_ALIPAY_ROW_TABLE) - ->where('occurred_at', '>=', $cutoff) - ->where('occurred_at', '<', $periodEnd) - ->whereNotNull('alipay_account') - ->where('alipay_account', '<>', '') - ->select([ - 'store_name', - 'alipay_account', - 'occurred_at', - 'source_row_number', - 'income_amount', - 'expense_amount', - 'balance_amount', - ]) - ->selectRaw( - 'ROW_NUMBER() OVER (' . - 'PARTITION BY alipay_account ' . - 'ORDER BY occurred_at ASC, source_row_number ASC' . - ') AS row_rank', - ); - $firstRows = $connection->query() - ->fromSub($firstRanked, 'first_store_balances') - ->where('row_rank', 1) - ->get(); - foreach ($firstRows as $row) { - $storeName = $this->storeName($row->store_name); - $opening[$storeName] = round( - (float) ($opening[$storeName] ?? 0) - + (float) $row->balance_amount - - (float) $row->income_amount - + (float) $row->expense_amount, - 2, - ); - } - } + $opening = app(TaobaoAlipaySettlementBillRepository::class) + ->storeOpeningBalances($start); return $this->mergeStoreBalances($opening, $activity); } diff --git a/tests/Unit/EcommerceFinance/TaobaoAlipaySettlementBillRepositoryTest.php b/tests/Unit/EcommerceFinance/TaobaoAlipaySettlementBillRepositoryTest.php index d0d68f34..5a42c430 100644 --- a/tests/Unit/EcommerceFinance/TaobaoAlipaySettlementBillRepositoryTest.php +++ b/tests/Unit/EcommerceFinance/TaobaoAlipaySettlementBillRepositoryTest.php @@ -65,6 +65,11 @@ class TaobaoAlipaySettlementBillRepositoryTest extends TestCase 'end' => '2026-10-31', ]), ); + $this->assertSame( + ['天猫-A店' => 180.0, '淘宝-B店' => 50.0], + app(TaobaoAlipaySettlementBillRepository::class) + ->storeOpeningBalances('2026-10-01'), + ); } public function test_month_balance_respects_store_filter(): void @@ -116,6 +121,11 @@ class TaobaoAlipaySettlementBillRepositoryTest extends TestCase $this->assertSame(35.0, $result['summary']['income']); $this->assertSame(15.0, $result['summary']['expense']); $this->assertSame(320.0, $result['summary']['balance']); + $this->assertSame( + ['天猫-A店' => 200.0, '淘宝-B店' => 100.0], + app(TaobaoAlipaySettlementBillRepository::class) + ->storeOpeningBalances('2026-07-01'), + ); } private function createTables(): void