Batch historical balance writes (#356)
## Summary - Batch calculated historical account balances with one upsert - Keep existing balance dates untouched - Add regression coverage for one account_balances write query Fixes PHP-LARAVEL-1C ## Tests - php artisan test --compact tests/Feature/OpenBanking/BalanceSyncServiceTest.php - vendor/bin/pint --dirty --format agent
This commit is contained in:
parent
22043ced29
commit
0fe7bf7fc6
|
|
@ -4,8 +4,10 @@ namespace App\Services\Banking;
|
|||
|
||||
use App\Contracts\BankingProviderInterface;
|
||||
use App\Models\Account;
|
||||
use App\Models\AccountBalance;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BalanceSyncService
|
||||
{
|
||||
|
|
@ -87,18 +89,28 @@ class BalanceSyncService
|
|||
|
||||
$runningBalance = $referenceBalance->balance;
|
||||
$referenceDate = $referenceBalance->balance_date->toDateString();
|
||||
$now = now();
|
||||
$rows = [];
|
||||
|
||||
foreach ($dailyTotals as $date => $sum) {
|
||||
if ($date < $referenceDate && ! isset($existingDates[$date])) {
|
||||
$account->balances()->create([
|
||||
$rows[] = [
|
||||
'id' => (string) Str::uuid(),
|
||||
'account_id' => $account->id,
|
||||
'balance_date' => $date,
|
||||
'balance' => $runningBalance,
|
||||
]);
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
|
||||
$runningBalance -= (int) $sum;
|
||||
}
|
||||
|
||||
if ($rows !== []) {
|
||||
AccountBalance::upsert($rows, ['account_id', 'balance_date'], ['balance', 'updated_at']);
|
||||
}
|
||||
|
||||
Log::info('Calculated historical balances', [
|
||||
'account_id' => $account->id,
|
||||
'reference_date' => $referenceDate,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,39 @@ test('calculateHistoricalBalances derives balances from transactions', function
|
|||
expect($feb5->balance)->toBe(85000);
|
||||
});
|
||||
|
||||
test('calculateHistoricalBalances writes missing balances in one query', function () {
|
||||
$user = User::factory()->onboarded()->create();
|
||||
$connection = BankingConnection::factory()->create(['user_id' => $user->id]);
|
||||
$account = Account::factory()->connected()->create([
|
||||
'user_id' => $user->id,
|
||||
'banking_connection_id' => $connection->id,
|
||||
'external_account_id' => 'ext-123',
|
||||
]);
|
||||
|
||||
AccountBalance::factory()->create([
|
||||
'account_id' => $account->id,
|
||||
'balance_date' => '2026-02-10',
|
||||
'balance' => 100000,
|
||||
]);
|
||||
|
||||
foreach (range(0, 5) as $daysBack) {
|
||||
Transaction::factory()->enableBanking()->create([
|
||||
'user_id' => $user->id,
|
||||
'account_id' => $account->id,
|
||||
'transaction_date' => sprintf('2026-02-%02d', 10 - $daysBack),
|
||||
'amount' => -1000,
|
||||
]);
|
||||
}
|
||||
|
||||
$service = new BalanceSyncService(Mockery::mock(BankingProviderInterface::class));
|
||||
|
||||
$result = countQueries(fn () => $service->calculateHistoricalBalances($account));
|
||||
$balanceWrites = collect($result['queries'])->filter(fn (string $query): bool => str_contains($query, 'account_balances') && str_contains(strtolower($query), 'insert'));
|
||||
|
||||
expect($balanceWrites)->toHaveCount(1)
|
||||
->and($account->balances()->count())->toBe(6);
|
||||
});
|
||||
|
||||
test('calculateHistoricalBalances handles multiple transactions per day', function () {
|
||||
$user = User::factory()->onboarded()->create();
|
||||
$connection = BankingConnection::factory()->create(['user_id' => $user->id]);
|
||||
|
|
|
|||
Loading…
Reference in New Issue