whisper-money/app/Services/TransactionSplitter.php

57 lines
1.8 KiB
PHP

<?php
namespace App\Services;
use App\Models\Transaction;
use Illuminate\Support\Facades\DB;
class TransactionSplitter
{
/**
* Replace a transaction's split lines. The lines become the source of truth
* for its category attribution: the transaction itself is left with no
* category and no labels (both now live on the lines). Amounts are assumed
* validated (same sign as the transaction, summing to its total).
*
* @param array<int, array{category_id?: ?string, amount: int|string, label_ids?: array<int, string>}> $lines
*/
public function apply(Transaction $transaction, array $lines): void
{
DB::transaction(function () use ($transaction, $lines): void {
$transaction->labels()->detach();
$transaction->splits()->delete();
foreach ($lines as $line) {
$split = $transaction->splits()->create([
'category_id' => $line['category_id'] ?? null,
'amount' => (int) $line['amount'],
]);
$labelIds = $line['label_ids'] ?? [];
if ($labelIds !== []) {
$split->labels()->sync($labelIds);
}
}
$transaction->forceFill([
'category_id' => null,
'category_source' => null,
'ai_confidence' => null,
'categorized_by_rule_id' => null,
'ai_suggested_category_id' => null,
'ai_suggested_category_at' => null,
])->save();
});
}
/**
* Collapse a split back to a single-category transaction by dropping its
* lines. The caller restores the single category via the normal update path.
*/
public function remove(Transaction $transaction): void
{
$transaction->splits()->delete();
}
}