. */ declare(strict_types=1); namespace FireflyIII\Console\Commands\Correction; use DB; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use Illuminate\Console\Command; use stdClass; /** * Class FixUnevenAmount */ class FixUnevenAmount extends Command { /** * The console command description. * * @var string */ protected $description = 'Fix journals with uneven amounts.'; /** * The name and signature of the console command. * * @var string */ protected $signature = 'firefly-iii:fix-uneven-amount'; /** * Execute the console command. * * @return int */ public function handle(): int { $start = microtime(true); $count = 0; // get invalid journals $journals = DB::table('transactions') ->groupBy('transaction_journal_id') ->whereNull('deleted_at') ->get(['transaction_journal_id', DB::raw('SUM(amount) AS the_sum')]); /** @var stdClass $entry */ foreach ($journals as $entry) { if (0 !== bccomp((string)$entry->the_sum, '0')) { $this->fixJournal((int)$entry->transaction_journal_id); $count++; } } if (0 === $count) { $this->info('Amount integrity OK!'); } $end = round(microtime(true) - $start, 2); $this->info(sprintf('Verified amount integrity in %s seconds', $end)); return 0; } /** * @param int $param */ private function fixJournal(int $param): void { // one of the transactions is bad. $journal = TransactionJournal::find($param); if (!$journal) { return; // @codeCoverageIgnore } /** @var Transaction $source */ $source = $journal->transactions()->where('amount', '<', 0)->first(); if (null === $source) { $this->error( sprintf( 'Journal #%d ("%s") has no source transaction. It will be deleted to maintain database consistency.', $journal->id ?? 0, $journal->description ?? '' ) ); Transaction::where('transaction_journal_id', $journal->id ?? 0)->forceDelete(); TransactionJournal::where('id', $journal-> ?? 0)->forceDelete(); return; } $amount = bcmul('-1', (string)$source->amount); // fix amount of destination: /** @var Transaction $destination */ $destination = $journal->transactions()->where('amount', '>', 0)->first(); if (null === $destination) { $this->error( sprintf( 'Journal #%d ("%s") has no destination transaction. It will be deleted to maintain database consistency.', $journal->id ?? 0, $journal->description ?? '' ) ); Transaction::where('transaction_journal_id', $journal->id ?? 0)->forceDelete(); TransactionJournal::where('id', $journal-> ?? 0)->forceDelete(); return; } $destination->amount = $amount; $destination->save(); $message = sprintf('Corrected amount in transaction journal #%d', $param); $this->line($message); } }