perf: 复用支付宝店铺期初缓存 (#1042)
This commit is contained in:
parent
906b9ea197
commit
4ee396151d
@ -98,6 +98,33 @@ class TaobaoAlipaySettlementBillRepository
|
||||
return $this->summaryForQuery($query, $filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复用总览期初查询已经缓存的店铺余额,避免店铺明细再次扫描全历史。
|
||||
*
|
||||
* @return array<string,float>
|
||||
*/
|
||||
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,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user