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-16 07:22:12 +02:00
private function __construct ()
{
2024-05-16 07:14:44 +02:00
// no-op
}
2024-05-13 20:31:52 +02:00
/**
* Recalculate all balances for a given account .
*
* Je moet toch altijd wel alles doen want je weet niet waar een transaction journal invloed op heeft .
* Dus dit aantikken per transaction journal is zinloos , beide accounts moeten gedaan worden .
*/
2024-05-16 07:14:44 +02:00
public static function recalculateAll () : void
2024-05-12 13:31:33 +02:00
{
2024-05-16 07:22:12 +02:00
$object = new self ();
2024-05-16 07:14:44 +02:00
$object -> recalculateLatest ( null );
2024-05-16 07:22:12 +02:00
// $object->recalculateJournals(null, null);
2024-05-13 20:31:52 +02:00
}
2024-05-12 13:31:33 +02:00
2024-05-16 07:14:44 +02:00
public static function recalculateForJournal ( TransactionJournal $transactionJournal ) : void
2024-05-13 20:31:52 +02:00
{
2024-05-16 07:22:12 +02:00
$object = new self ();
2024-05-13 20:31:52 +02:00
foreach ( $transactionJournal -> transactions as $transaction ) {
2024-05-16 07:14:44 +02:00
$object -> recalculateLatest ( $transaction -> account );
2024-05-16 07:22:12 +02:00
// $object->recalculateJournals($transaction->account, $transactionJournal);
2024-05-12 17:50:54 +02:00
}
2024-05-13 20:31:52 +02:00
}
2024-05-12 17:50:54 +02:00
2024-05-16 07:14:44 +02:00
private function getAccountBalanceByAccount ( int $account , int $currency ) : AccountBalance
2024-05-13 20:31:52 +02:00
{
2024-06-16 20:26:04 +02:00
$query = AccountBalance :: where ( 'title' , 'balance' ) -> where ( 'account_id' , $account ) -> where ( 'transaction_currency_id' , $currency );
2024-05-12 13:31:33 +02:00
2024-06-16 20:26:04 +02:00
$entry = $query -> first ();
2024-05-13 20:31:52 +02:00
if ( null !== $entry ) {
2024-05-16 05:10:41 +02:00
// Log::debug(sprintf('Found account balance "balance" for account #%d and currency #%d: %s', $account, $currency, $entry->balance));
2024-05-13 20:31:52 +02:00
return $entry ;
2024-05-12 13:31:33 +02:00
}
2024-05-13 20:31:52 +02:00
$entry = new AccountBalance ();
$entry -> title = 'balance' ;
$entry -> account_id = $account ;
$entry -> transaction_currency_id = $currency ;
$entry -> balance = '0' ;
$entry -> save ();
2024-05-16 05:10:41 +02:00
// Log::debug(sprintf('Created new account balance for account #%d and currency #%d: %s', $account, $currency, $entry->balance));
2024-05-13 20:31:52 +02:00
return $entry ;
2024-05-12 13:31:33 +02:00
}
2024-05-12 17:50:54 +02:00
2024-05-16 07:14:44 +02:00
private function getAccountBalanceByJournal ( string $title , int $account , int $journal , int $currency ) : AccountBalance
2024-05-12 13:31:33 +02:00
{
2024-06-16 20:26:04 +02:00
$query = AccountBalance :: where ( 'title' , $title ) -> where ( 'account_id' , $account ) -> where ( 'transaction_journal_id' , $journal ) -> where ( 'transaction_currency_id' , $currency );
2024-05-12 18:24:38 +02:00
2024-06-16 20:26:04 +02:00
$entry = $query -> first ();
2024-05-12 13:31:33 +02:00
if ( null !== $entry ) {
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 ;
2024-05-12 18:24:38 +02:00
$entry -> transaction_journal_id = $journal ;
2024-05-13 20:31:52 +02:00
$entry -> transaction_currency_id = $currency ;
2024-05-12 13:31:33 +02:00
$entry -> balance = '0' ;
$entry -> save ();
2024-05-13 05:10:16 +02:00
2024-05-12 13:31:33 +02:00
return $entry ;
}
2024-05-16 07:14:44 +02:00
private function recalculateLatest ( ? Account $account ) : void
2024-05-12 13:31:33 +02:00
{
2024-06-16 20:26:04 +02:00
$query = Transaction :: groupBy ([ 'transactions.account_id' , 'transactions.transaction_currency_id' , 'transactions.foreign_currency_id' ]);
2024-05-13 05:10:16 +02:00
2024-05-13 20:31:52 +02:00
if ( null !== $account ) {
$query -> where ( 'transactions.account_id' , $account -> id );
2024-05-12 13:31:33 +02:00
}
2024-05-13 20:31:52 +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' )]);
// reset account balances:
2024-05-16 07:14:44 +02:00
$this -> resetAccountBalancesByAccount ( 'balance' , $account );
2024-05-13 20:31:52 +02:00
2024-05-16 05:10:41 +02:00
/** @var \stdClass $row */
2024-05-13 20:31:52 +02:00
foreach ( $result as $row ) {
$account = ( int ) $row -> account_id ;
$transactionCurrency = ( int ) $row -> transaction_currency_id ;
$foreignCurrency = ( int ) $row -> foreign_currency_id ;
2024-05-21 17:36:05 +02:00
$sumAmount = ( string ) $row -> sum_amount ;
$sumForeignAmount = ( string ) $row -> sum_foreign_amount ;
2024-06-16 19:27:38 +02:00
$sumAmount = '' === $sumAmount ? '0' : $sumAmount ;
$sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount ;
2024-05-13 20:31:52 +02:00
// first create for normal currency:
2024-06-16 20:26:04 +02:00
$entry = $this -> getAccountBalanceByAccount ( $account , $transactionCurrency );
$entry -> balance = bcadd (( string ) $entry -> balance , $sumAmount );
2024-05-13 20:31:52 +02:00
$entry -> save ();
// then do foreign amount, if present:
if ( $foreignCurrency > 0 ) {
2024-05-16 07:14:44 +02:00
$entry = $this -> getAccountBalanceByAccount ( $account , $foreignCurrency );
2024-05-25 08:10:23 +02:00
$entry -> balance = bcadd (( string ) $entry -> balance , $sumForeignAmount );
2024-05-13 20:31:52 +02:00
$entry -> save ();
}
}
2024-05-16 07:14:44 +02:00
Log :: debug ( sprintf ( 'Recalculated %d account balance(s)' , $result -> count ()));
2024-05-13 20:31:52 +02:00
}
2024-05-16 07:14:44 +02:00
private function resetAccountBalancesByAccount ( string $title , ? Account $account ) : void
2024-05-13 20:31:52 +02:00
{
if ( null === $account ) {
2024-05-16 07:14:44 +02:00
$count = AccountBalance :: whereNotNull ( 'updated_at' ) -> where ( 'title' , $title ) -> update ([ 'balance' => '0' ]);
Log :: debug ( sprintf ( 'Set %d account balance(s) to zero.' , $count ));
2024-05-13 05:10:16 +02:00
2024-05-12 18:24:38 +02:00
return ;
}
2024-05-16 07:14:44 +02:00
$count = AccountBalance :: where ( 'account_id' , $account -> id ) -> where ( 'title' , $title ) -> update ([ 'balance' => '0' ]);
Log :: debug ( sprintf ( 'Set %d account balance(s) of account #%d to zero.' , $count , $account -> id ));
2024-05-12 18:24:38 +02:00
}
2024-05-16 07:14:44 +02:00
/**
2024-05-21 17:36:05 +02:00
* Als je alles opnieuw doet , verzamel je alle transactions en het bedrag en zet je dat neer als " balance after
* journal " . Dat betekent, netjes op volgorde van datum en doorrekenen.
2024-05-16 07:14:44 +02:00
*
* Zodra je een transaction journal verplaatst ( datum ) moet je dat journal en alle latere journals opnieuw doen .
* Maar dan moet je van de account wel een beginnetje hebben , namelijk de balance tot en met dat moment .
*
* 1. Dus dan search je eerst naar die SUM , som alle transactions eerder dan ( niet inclusief ) de journal .
* 2. En vanaf daar pak je alle journals op of na de journal ( dus ook de journal zelf ) en begin je door te tellen .
* 3. Elke voorbij gaande journal entry " balance_after_journal " geef je een update of voeg je toe .
*/
private function recalculateJournals ( ? Account $account , ? TransactionJournal $transactionJournal ) : void
2024-05-13 20:31:52 +02:00
{
2024-06-16 20:26:04 +02:00
$query = Transaction :: groupBy ([ 'transactions.account_id' , 'transaction_journals.id' , 'transactions.transaction_currency_id' , 'transactions.foreign_currency_id' ]);
2024-05-13 20:31:52 +02:00
$query -> leftJoin ( 'transaction_journals' , 'transaction_journals.id' , '=' , 'transactions.transaction_journal_id' );
$query -> orderBy ( 'transaction_journals.date' , 'asc' );
2024-05-16 07:14:44 +02:00
$amounts = [];
2024-05-13 20:31:52 +02:00
if ( null !== $account ) {
$query -> where ( 'transactions.account_id' , $account -> id );
}
2024-05-16 07:22:12 +02:00
if ( null !== $account && null !== $transactionJournal ) {
2024-05-16 07:14:44 +02:00
$query -> where ( 'transaction_journals.date' , '>=' , $transactionJournal -> date );
$amounts = $this -> getStartAmounts ( $account , $transactionJournal );
}
2024-06-16 20:26:04 +02:00
$result = $query -> get ([ 'transactions.account_id' , 'transaction_journals.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-13 20:31:52 +02:00
2024-05-16 05:10:41 +02:00
/** @var \stdClass $row */
2024-05-13 20:31:52 +02:00
foreach ( $result as $row ) {
2024-06-16 20:26:04 +02:00
$account = ( int ) $row -> account_id ;
$transactionCurrency = ( int ) $row -> transaction_currency_id ;
$foreignCurrency = ( int ) $row -> foreign_currency_id ;
$sumAmount = ( string ) $row -> sum_amount ;
$sumForeignAmount = ( string ) $row -> sum_foreign_amount ;
$journalId = ( int ) $row -> id ;
2024-05-21 17:36:05 +02:00
// check for empty strings
2024-06-16 20:26:04 +02:00
$sumAmount = '' === $sumAmount ? '0' : $sumAmount ;
$sumForeignAmount = '' === $sumForeignAmount ? '0' : $sumForeignAmount ;
2024-05-13 20:31:52 +02:00
// new amounts:
2024-05-21 17:36:05 +02:00
$amounts [ $account ][ $transactionCurrency ] = bcadd ( $amounts [ $account ][ $transactionCurrency ] ? ? '0' , $sumAmount );
$amounts [ $account ][ $foreignCurrency ] = bcadd ( $amounts [ $account ][ $foreignCurrency ] ? ? '0' , $sumForeignAmount );
2024-05-13 20:31:52 +02:00
// first create for normal currency:
2024-06-16 20:26:04 +02:00
$entry = self :: getAccountBalanceByJournal ( 'balance_after_journal' , $account , $journalId , $transactionCurrency );
$entry -> balance = $amounts [ $account ][ $transactionCurrency ];
2024-05-13 20:31:52 +02:00
$entry -> save ();
// then do foreign amount, if present:
if ( $foreignCurrency > 0 ) {
$entry = self :: getAccountBalanceByJournal ( 'balance_after_journal' , $account , $journalId , $foreignCurrency );
$entry -> balance = $amounts [ $account ][ $foreignCurrency ];
$entry -> save ();
}
}
// select transactions.account_id, transaction_journals.id, transactions.transaction_currency_id, transactions.foreign_currency_id, sum(transactions.amount), sum(transactions.foreign_amount)
//
2024-05-16 05:10:41 +02:00
// from transactions
2024-05-13 20:31:52 +02:00
//
2024-05-16 05:10:41 +02:00
// left join transaction_journals ON transaction_journals.id = transactions.transaction_journal_id
2024-05-13 20:31:52 +02:00
//
2024-05-16 05:10:41 +02:00
// group by account_id, transaction_journals.id, transaction_currency_id, foreign_currency_id
// order by transaction_journals.date desc
2024-05-13 20:31:52 +02:00
}
2024-05-16 07:14:44 +02:00
2024-05-16 07:22:12 +02:00
private function getStartAmounts ( Account $account , TransactionJournal $journal ) : array
{
exit ( 'here we are' );
2024-05-16 07:14:44 +02:00
return [];
}
2024-05-12 13:31:33 +02:00
}