2024-05-12 13:31:33 +02:00
< ? php
/*
* AccountBalanceCalculator . php
* Copyright ( c ) 2024 james @ firefly - iii . org .
*
* This file is part of Firefly III ( https :// github . com / firefly - iii ) .
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see https :// www . gnu . org / licenses /.
*/
declare ( strict_types = 1 );
namespace FireflyIII\Support\Models ;
use FireflyIII\Models\Account ;
use FireflyIII\Models\AccountBalance ;
use FireflyIII\Models\Transaction ;
2024-05-12 18:24:38 +02:00
use FireflyIII\Models\TransactionJournal ;
2024-05-12 13:31:33 +02:00
use Illuminate\Support\Facades\Log ;
class AccountBalanceCalculator
{
2024-05-12 18:24:38 +02:00
public static function recalculate ( ? Account $account , ? TransactionJournal $transactionJournal ) : void
2024-05-12 13:31:33 +02:00
{
// first collect normal amounts (in whatever currency), and set them.
// select account_id, transaction_currency_id, foreign_currency_id, sum(amount), sum(foreign_amount) from transactions group by account_id, transaction_currency_id, foreign_currency_id
2024-05-13 05:10:16 +02:00
$query = Transaction :: groupBy ([ 'transactions.account_id' , 'transactions.transaction_currency_id' , 'transactions.foreign_currency_id' ]);
$title = 'balance' ;
2024-05-12 17:50:54 +02:00
if ( null !== $account ) {
2024-05-12 18:24:38 +02:00
$query -> where ( 'transactions.account_id' , $account -> id );
}
if ( null !== $transactionJournal ) {
$query -> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' );
$query -> where ( 'transaction_journals.date' , '<=' , $transactionJournal -> date );
$title = 'balance_after_journal' ;
2024-05-12 17:50:54 +02:00
}
2024-05-13 05:10:16 +02:00
$result = $query -> get ([ 'transactions.account_id' , 'transactions.transaction_currency_id' , 'transactions.foreign_currency_id' , \DB :: raw ( 'SUM(transactions.amount) as sum_amount' ), \DB :: raw ( 'SUM(transactions.foreign_amount) as sum_foreign_amount' )]);
2024-05-12 13:31:33 +02:00
// reset account balances:
2024-05-12 18:24:38 +02:00
self :: resetAccountBalances ( $title , $account , $transactionJournal );
2024-05-12 13:31:33 +02:00
2024-05-13 05:10:16 +02:00
/** @var \stdClass $row */
2024-05-12 13:31:33 +02:00
foreach ( $result as $row ) {
2024-05-13 05:10:16 +02:00
$account = ( int ) $row -> account_id ;
$transactionCurrency = ( int ) $row -> transaction_currency_id ;
$foreignCurrency = ( int ) $row -> foreign_currency_id ;
$sumAmount = $row -> sum_amount ;
$sumForeignAmount = $row -> sum_foreign_amount ;
2024-05-12 13:31:33 +02:00
// first create for normal currency:
2024-05-12 18:24:38 +02:00
$entry = self :: getBalance ( $title , $account , $transactionCurrency , $transactionJournal ? -> id );
$entry -> balance = bcadd ( $entry -> balance , $sumAmount );
$entry -> transaction_journal_id = $transactionJournal ? -> id ;
2024-05-12 13:31:33 +02:00
$entry -> save ();
2024-05-12 18:24:38 +02:00
Log :: debug ( sprintf ( 'Set balance entry "%s" #%d to amount %s' , $title , $entry -> id , $entry -> balance ));
2024-05-12 13:31:33 +02:00
// then do foreign amount, if present:
if ( $foreignCurrency > 0 ) {
2024-05-12 18:24:38 +02:00
$entry = self :: getBalance ( $title , $account , $foreignCurrency , $transactionJournal ? -> id );
$entry -> balance = bcadd ( $entry -> balance , $sumForeignAmount );
$entry -> transaction_journal_id = $transactionJournal ? -> id ;
2024-05-12 13:31:33 +02:00
$entry -> save ();
2024-05-12 18:24:38 +02:00
Log :: debug ( sprintf ( 'Set balance entry "%s" #%d to amount %s' , $title , $entry -> id , $entry -> balance ));
2024-05-12 13:31:33 +02:00
}
}
}
2024-05-12 17:50:54 +02:00
2024-05-12 18:24:38 +02:00
private static function getBalance ( string $title , int $account , int $currency , ? int $journal ) : AccountBalance
2024-05-12 13:31:33 +02:00
{
2024-05-13 05:10:16 +02:00
$query = AccountBalance :: where ( 'title' , $title ) -> where ( 'account_id' , $account ) -> where ( 'transaction_currency_id' , $currency );
2024-05-12 18:24:38 +02:00
if ( null !== $journal ) {
$query -> where ( 'transaction_journal_id' , $journal );
}
2024-05-13 05:10:16 +02:00
$entry = $query -> first ();
2024-05-12 13:31:33 +02:00
if ( null !== $entry ) {
2024-05-12 18:24:38 +02:00
Log :: debug ( sprintf ( 'Found account balance "%s" for account #%d and currency #%d: %s' , $title , $account , $currency , $entry -> balance ));
2024-05-13 05:10:16 +02:00
2024-05-12 13:31:33 +02:00
return $entry ;
}
2024-05-13 05:10:16 +02:00
$entry = new AccountBalance ();
2024-05-12 13:31:33 +02:00
$entry -> title = $title ;
$entry -> account_id = $account ;
$entry -> transaction_currency_id = $currency ;
2024-05-12 18:24:38 +02:00
$entry -> transaction_journal_id = $journal ;
2024-05-12 13:31:33 +02:00
$entry -> balance = '0' ;
$entry -> save ();
Log :: debug ( sprintf ( 'Created new account balance for account #%d and currency #%d: %s' , $account , $currency , $entry -> balance ));
2024-05-13 05:10:16 +02:00
2024-05-12 13:31:33 +02:00
return $entry ;
}
2024-05-12 18:24:38 +02:00
private static function resetAccountBalances ( string $title , ? Account $account , ? TransactionJournal $transactionJournal ) : void
2024-05-12 13:31:33 +02:00
{
2024-05-12 18:24:38 +02:00
if ( null === $account && null === $transactionJournal ) {
AccountBalance :: whereNotNull ( 'updated_at' ) -> where ( 'title' , $title ) -> update ([ 'balance' => '0' ]);
2024-05-12 13:31:33 +02:00
Log :: debug ( 'Set ALL balances to zero.' );
2024-05-13 05:10:16 +02:00
2024-05-12 13:31:33 +02:00
return ;
}
2024-05-13 05:10:16 +02:00
if ( null !== $account && null === $transactionJournal ) {
2024-05-12 18:24:38 +02:00
AccountBalance :: where ( 'account_id' , $account -> id ) -> where ( 'title' , $title ) -> update ([ 'balance' => '0' ]);
Log :: debug ( sprintf ( 'Set balances of account #%d to zero.' , $account -> id ));
2024-05-13 05:10:16 +02:00
2024-05-12 18:24:38 +02:00
return ;
}
AccountBalance :: where ( 'account_id' , $account -> id ) -> where ( 'transaction_journal_id' , $transactionJournal -> id ) -> where ( 'title' , $title ) -> update ([ 'balance' => '0' ]);
Log :: debug ( sprintf ( 'Set balances of account #%d + journal #%d to zero.' , $account -> id , $transactionJournal -> id ));
}
2024-05-13 05:10:16 +02:00
public static function recalculateByJournal ( TransactionJournal $transactionJournal ) : void
2024-05-12 18:24:38 +02:00
{
Log :: debug ( sprintf ( 'Recalculate balance after journal #%d' , $transactionJournal -> id ));
// update both account balances, but limit to this transaction or earlier.
foreach ( $transactionJournal -> transactions as $transaction ) {
self :: recalculate ( $transaction -> account , $transactionJournal );
}
2024-05-12 13:31:33 +02:00
}
}