2015-05-16 09:41:14 +02:00
< ? php
2022-11-04 05:11:05 +01:00
2016-05-20 12:41:23 +02:00
/**
* AccountController . php
2020-01-31 07:32:04 +01:00
* Copyright ( c ) 2019 james @ firefly - iii . org
2016-05-20 12:41:23 +02:00
*
2019-10-02 06:37:26 +02:00
* This file is part of Firefly III ( https :// github . com / firefly - iii ) .
2016-10-05 06:52:15 +02:00
*
2019-10-02 06:37:26 +02:00
* 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 .
2017-10-21 08:40:00 +02:00
*
2019-10-02 06:37:26 +02:00
* This program is distributed in the hope that it will be useful ,
2017-10-21 08:40:00 +02:00
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
2019-10-02 06:37:26 +02:00
* GNU Affero General Public License for more details .
2017-10-21 08:40:00 +02:00
*
2019-10-02 06:37:26 +02:00
* 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 />.
2016-05-20 12:41:23 +02:00
*/
2017-04-09 07:44:22 +02:00
declare ( strict_types = 1 );
2015-05-16 09:41:14 +02:00
namespace FireflyIII\Http\Controllers\Chart ;
use Carbon\Carbon ;
2024-12-22 16:41:55 +01:00
use FireflyIII\Enums\AccountTypeEnum ;
2025-01-03 09:05:19 +01:00
use FireflyIII\Enums\TransactionTypeEnum ;
2021-09-18 10:26:12 +02:00
use FireflyIII\Exceptions\FireflyException ;
2016-12-11 16:02:04 +01:00
use FireflyIII\Generator\Chart\Basic\GeneratorInterface ;
2019-05-30 12:31:19 +02:00
use FireflyIII\Helpers\Collector\GroupCollectorInterface ;
2015-05-16 09:41:14 +02:00
use FireflyIII\Http\Controllers\Controller ;
use FireflyIII\Models\Account ;
2018-08-27 18:59:30 +02:00
use FireflyIII\Models\TransactionCurrency ;
2016-10-10 07:25:27 +02:00
use FireflyIII\Repositories\Account\AccountRepositoryInterface ;
2023-10-28 06:58:33 +02:00
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface ;
2016-05-13 17:22:24 +02:00
use FireflyIII\Support\CacheProperties ;
2025-02-04 21:26:40 +01:00
use FireflyIII\Support\Facades\Amount ;
2024-12-22 19:42:06 +01:00
use FireflyIII\Support\Facades\Steam ;
2018-08-11 06:39:29 +02:00
use FireflyIII\Support\Http\Controllers\AugumentData ;
2018-12-31 07:58:13 +01:00
use FireflyIII\Support\Http\Controllers\ChartGeneration ;
2018-07-14 23:22:08 +02:00
use FireflyIII\Support\Http\Controllers\DateCalculation ;
2018-07-08 12:08:53 +02:00
use Illuminate\Http\JsonResponse ;
2015-05-17 18:03:16 +02:00
use Illuminate\Support\Collection ;
2024-11-19 06:31:49 +01:00
use Illuminate\Support\Facades\Log ;
2015-05-16 09:41:14 +02:00
2018-07-14 23:22:08 +02:00
/**
2017-11-15 12:25:49 +01:00
* Class AccountController .
2015-05-16 09:41:14 +02:00
*/
class AccountController extends Controller
{
2022-10-30 14:24:19 +01:00
use AugumentData ;
use ChartGeneration ;
2023-11-04 14:18:49 +01:00
use DateCalculation ;
2018-07-14 23:22:08 +02:00
2022-03-28 12:24:16 +02:00
protected GeneratorInterface $generator ;
private AccountRepositoryInterface $accountRepository ;
private CurrencyRepositoryInterface $currencyRepository ;
2018-08-27 18:59:30 +02:00
2015-06-27 08:18:47 +02:00
/**
2018-07-21 08:06:24 +02:00
* AccountController constructor .
2015-06-27 08:18:47 +02:00
*/
public function __construct ()
{
parent :: __construct ();
2018-08-27 18:59:30 +02:00
$this -> middleware (
function ( $request , $next ) {
$this -> generator = app ( GeneratorInterface :: class );
$this -> accountRepository = app ( AccountRepositoryInterface :: class );
$this -> currencyRepository = app ( CurrencyRepositoryInterface :: class );
return $next ( $request );
}
);
2015-06-27 08:18:47 +02:00
}
2021-03-28 11:46:23 +02:00
2015-12-12 10:41:51 +01:00
/**
2018-08-27 08:08:51 +02:00
* Shows the balances for all the user ' s expense accounts ( on the front page ) .
2015-12-28 07:38:02 +01:00
*
2018-08-27 18:59:30 +02:00
* This chart is ( multi ) currency aware .
2023-12-22 20:12:38 +01:00
*/
2018-08-27 18:59:30 +02:00
public function expenseAccounts () : JsonResponse
2015-12-12 10:41:51 +01:00
{
2024-12-24 16:56:31 +01:00
Log :: debug ( 'RevenueAccounts' );
2024-12-25 07:13:41 +01:00
2018-07-27 04:46:21 +02:00
/** @var Carbon $start */
2025-02-02 16:06:22 +01:00
$start = clone session ( 'start' , today ( config ( 'app.timezone' )) -> startOfMonth ());
2023-12-20 19:35:52 +01:00
2018-07-27 04:46:21 +02:00
/** @var Carbon $end */
2025-02-02 16:06:22 +01:00
$end = clone session ( 'end' , today ( config ( 'app.timezone' )) -> endOfMonth ());
$cache = new CacheProperties ();
2015-12-12 10:41:51 +01:00
$cache -> addProperty ( $start );
$cache -> addProperty ( $end );
2024-12-24 19:03:47 +01:00
$cache -> addProperty ( $this -> convertToNative );
2016-12-11 17:05:48 +01:00
$cache -> addProperty ( 'chart.account.expense-accounts' );
2015-12-12 10:41:51 +01:00
if ( $cache -> has ()) {
2024-12-25 07:13:41 +01:00
return response () -> json ( $cache -> get ());
2015-12-12 10:41:51 +01:00
}
2016-05-13 17:22:24 +02:00
$start -> subDay ();
2016-12-11 17:05:48 +01:00
2018-08-27 18:59:30 +02:00
// prep some vars:
2025-02-02 16:06:22 +01:00
$currencies = [];
$chartData = [];
$tempData = [];
2018-08-27 18:59:30 +02:00
// grab all accounts and names
2025-02-02 16:06:22 +01:00
$accounts = $this -> accountRepository -> getAccountsByType ([ AccountTypeEnum :: EXPENSE -> value ]);
$accountNames = $this -> extractNames ( $accounts );
2018-08-27 18:59:30 +02:00
// grab all balances
2025-02-04 21:26:40 +01:00
$startBalances = Steam :: finalAccountsBalance ( $accounts , $start );
$endBalances = Steam :: finalAccountsBalance ( $accounts , $end );
2018-08-27 08:08:51 +02:00
2024-12-24 10:29:07 +01:00
// loop the accounts, then check for balance and currency info.
2024-12-25 07:13:41 +01:00
foreach ( $accounts as $account ) {
2024-12-31 08:18:40 +01:00
// Log::debug(sprintf('[a] Now in account #%d ("%s")', $account->id, $account->name));
2024-12-24 10:29:07 +01:00
$expenses = $endBalances [ $account -> id ] ? ? false ;
2024-12-25 07:13:41 +01:00
if ( false === $expenses ) {
Log :: error ( sprintf ( 'Found no end balance for account #%d' , $account -> id ));
2024-12-24 10:29:07 +01:00
continue ;
}
2024-12-25 07:13:41 +01:00
2024-12-24 10:29:07 +01:00
/**
* @ var string $key
* @ var string $endBalance
*/
foreach ( $expenses as $key => $endBalance ) {
2024-12-25 07:13:41 +01:00
if ( ! $this -> convertToNative && 'native_balance' === $key ) {
2024-12-24 10:29:07 +01:00
Log :: debug ( sprintf ( '[a] Will skip expense array "%s"' , $key ));
2024-12-25 07:13:41 +01:00
2024-12-24 10:29:07 +01:00
continue ;
}
2024-12-25 07:13:41 +01:00
if ( $this -> convertToNative && 'native_balance' !== $key ) {
2024-12-24 10:29:07 +01:00
Log :: debug ( sprintf ( '[b] Will skip expense array "%s"' , $key ));
2024-12-25 07:13:41 +01:00
2024-12-23 06:55:14 +01:00
continue ;
}
2024-12-31 08:09:51 +01:00
// Log::debug(sprintf('Will process expense array "%s" with amount %s', $key, $endBalance));
2025-02-02 16:06:22 +01:00
$searchCode = $this -> convertToNative ? $this -> defaultCurrency -> code : $key ;
2024-12-31 08:09:51 +01:00
// Log::debug(sprintf('Search code is %s', $searchCode));
2018-08-27 18:59:30 +02:00
// see if there is an accompanying start amount.
// grab the difference and find the currency.
2024-12-24 10:29:07 +01:00
$startBalance = ( $startBalances [ $account -> id ][ $key ] ? ? '0' );
2024-12-31 08:09:51 +01:00
// Log::debug(sprintf('Start balance is %s', $startBalance));
2025-02-02 16:06:22 +01:00
$diff = bcsub ( $endBalance , $startBalance );
2024-12-24 10:29:07 +01:00
$currencies [ $searchCode ] ? ? = $this -> currencyRepository -> findByCode ( $searchCode );
2018-08-27 18:59:30 +02:00
if ( 0 !== bccomp ( $diff , '0' )) {
// store the values in a temporary array.
$tempData [] = [
2024-12-24 10:29:07 +01:00
'name' => $accountNames [ $account -> id ],
2018-08-27 18:59:30 +02:00
'difference' => $diff ,
2024-12-22 08:43:12 +01:00
'diff_float' => ( float ) $diff , // intentional float
2024-12-24 10:29:07 +01:00
'currency_id' => $currencies [ $searchCode ] -> id ,
2018-08-27 18:59:30 +02:00
];
}
2016-05-13 17:22:24 +02:00
}
2016-12-11 16:38:21 +01:00
}
2024-12-23 07:13:59 +01:00
// recreate currencies, but on ID instead of code.
$newCurrencies = [];
2024-12-23 08:40:29 +01:00
foreach ( $currencies as $currency ) {
2024-12-23 07:13:59 +01:00
$newCurrencies [ $currency -> id ] = $currency ;
}
2025-02-02 16:06:22 +01:00
$currencies = $newCurrencies ;
2024-12-23 07:13:59 +01:00
2018-08-27 18:59:30 +02:00
// sort temp array by amount.
2025-02-02 16:06:22 +01:00
$amounts = array_column ( $tempData , 'diff_float' );
2018-08-27 18:59:30 +02:00
array_multisort ( $amounts , SORT_DESC , $tempData );
// loop all found currencies and build the data array for the chart.
/**
2023-06-21 12:34:58 +02:00
* @ var int $currencyId
2018-08-27 18:59:30 +02:00
* @ var TransactionCurrency $currency
*/
foreach ( $currencies as $currencyId => $currency ) {
$dataSet
= [
2025-02-02 16:06:22 +01:00
'label' => ( string ) trans ( 'firefly.spent' ),
'type' => 'bar' ,
'currency_symbol' => $currency -> symbol ,
'currency_code' => $currency -> code ,
'entries' => $this -> expandNames ( $tempData ),
];
2018-08-27 18:59:30 +02:00
$chartData [ $currencyId ] = $dataSet ;
2018-08-27 08:08:51 +02:00
}
2018-08-27 18:59:30 +02:00
// loop temp data and place data in correct array:
foreach ( $tempData as $entry ) {
$currencyId = $entry [ 'currency_id' ];
$name = $entry [ 'name' ];
2024-12-23 07:13:59 +01:00
$chartData [ $currencyId ][ 'entries' ][ $name ] = ( float ) $entry [ 'difference' ];
2018-08-27 08:08:51 +02:00
}
2018-08-27 18:59:30 +02:00
2025-02-02 16:06:22 +01:00
$data = $this -> generator -> multiSet ( $chartData );
2015-12-12 10:41:51 +01:00
$cache -> store ( $data );
2015-06-02 17:44:50 +02:00
2018-03-10 20:30:09 +01:00
return response () -> json ( $data );
2015-05-17 18:03:16 +02:00
}
2023-06-21 12:34:58 +02:00
/**
* Expenses per budget for all time , as shown on account overview .
*/
public function expenseBudgetAll ( AccountRepositoryInterface $repository , Account $account ) : JsonResponse
{
$start = $repository -> oldestJournalDate ( $account ) ? ? today ( config ( 'app.timezone' )) -> startOfMonth ();
$end = today ( config ( 'app.timezone' ));
return $this -> expenseBudget ( $account , $start , $end );
}
2016-11-20 18:31:29 +01:00
/**
2018-07-21 08:06:24 +02:00
* Expenses per budget , as shown on account overview .
2016-11-20 18:31:29 +01:00
*/
2018-07-08 12:08:53 +02:00
public function expenseBudget ( Account $account , Carbon $start , Carbon $end ) : JsonResponse
2016-11-20 18:31:29 +01:00
{
2025-02-02 16:06:22 +01:00
$cache = new CacheProperties ();
2016-11-20 18:31:29 +01:00
$cache -> addProperty ( $account -> id );
$cache -> addProperty ( $start );
$cache -> addProperty ( $end );
2016-12-11 17:05:48 +01:00
$cache -> addProperty ( 'chart.account.expense-budget' );
2016-11-20 18:31:29 +01:00
if ( $cache -> has ()) {
2021-09-18 10:26:12 +02:00
return response () -> json ( $cache -> get ());
2016-11-20 18:31:29 +01:00
}
2023-12-20 19:35:52 +01:00
2019-05-30 12:31:19 +02:00
/** @var GroupCollectorInterface $collector */
$collector = app ( GroupCollectorInterface :: class );
2025-01-03 09:05:19 +01:00
$collector -> setAccounts ( new Collection ([ $account ])) -> setRange ( $start , $end ) -> withBudgetInformation () -> setTypes ([ TransactionTypeEnum :: WITHDRAWAL -> value ]);
2019-05-30 12:31:19 +02:00
$journals = $collector -> getExtractedJournals ();
$chartData = [];
$result = [];
$budgetIds = [];
2023-12-20 19:35:52 +01:00
2019-05-30 12:31:19 +02:00
/** @var array $journal */
foreach ( $journals as $journal ) {
2025-02-02 16:06:22 +01:00
$budgetId = ( int ) $journal [ 'budget_id' ];
$key = sprintf ( '%d-%d' , $budgetId , $journal [ 'currency_id' ]);
$budgetIds [] = $budgetId ;
2021-04-07 07:28:43 +02:00
if ( ! array_key_exists ( $key , $result )) {
2020-01-02 08:34:34 +01:00
$result [ $key ] = [
2018-09-10 17:57:20 +02:00
'total' => '0' ,
'budget_id' => $budgetId ,
2020-01-02 08:34:34 +01:00
'currency_name' => $journal [ 'currency_name' ],
2019-05-30 12:31:19 +02:00
'currency_symbol' => $journal [ 'currency_symbol' ],
2020-07-28 06:25:14 +02:00
'currency_code' => $journal [ 'currency_code' ],
2018-08-27 18:59:30 +02:00
];
}
2020-01-02 08:34:34 +01:00
$result [ $key ][ 'total' ] = bcadd ( $journal [ 'amount' ], $result [ $key ][ 'total' ]);
2016-11-20 18:31:29 +01:00
}
2016-12-11 17:05:48 +01:00
2025-02-02 16:06:22 +01:00
$names = $this -> getBudgetNames ( $budgetIds );
2018-09-10 17:57:20 +02:00
2018-08-27 18:59:30 +02:00
foreach ( $result as $row ) {
$budgetId = $row [ 'budget_id' ];
$name = $names [ $budgetId ];
2024-12-22 08:43:12 +01:00
$label = ( string ) trans ( 'firefly.name_in_currency' , [ 'name' => $name , 'currency' => $row [ 'currency_name' ]]);
2020-07-28 06:25:14 +02:00
$chartData [ $label ] = [ 'amount' => $row [ 'total' ], 'currency_symbol' => $row [ 'currency_symbol' ], 'currency_code' => $row [ 'currency_code' ]];
2016-12-11 17:05:48 +01:00
}
2025-02-02 16:06:22 +01:00
$data = $this -> generator -> multiCurrencyPieChart ( $chartData );
2016-11-20 18:31:29 +01:00
$cache -> store ( $data );
2018-03-10 20:30:09 +01:00
return response () -> json ( $data );
2016-11-20 18:31:29 +01:00
}
/**
2023-06-21 12:34:58 +02:00
* Expenses grouped by category for account .
2016-11-20 18:31:29 +01:00
*/
2023-06-21 12:34:58 +02:00
public function expenseCategoryAll ( AccountRepositoryInterface $repository , Account $account ) : JsonResponse
2017-02-25 13:19:42 +01:00
{
2023-02-11 07:36:45 +01:00
$start = $repository -> oldestJournalDate ( $account ) ? ? today ( config ( 'app.timezone' )) -> startOfMonth ();
$end = today ( config ( 'app.timezone' ));
2017-02-25 13:19:42 +01:00
2023-06-21 12:34:58 +02:00
return $this -> expenseCategory ( $account , $start , $end );
2017-02-25 13:19:42 +01:00
}
/**
2018-07-21 08:06:24 +02:00
* Expenses per category for one single account .
2017-02-25 13:19:42 +01:00
*/
2018-07-08 12:08:53 +02:00
public function expenseCategory ( Account $account , Carbon $start , Carbon $end ) : JsonResponse
2016-11-20 18:31:29 +01:00
{
2025-02-02 16:06:22 +01:00
$cache = new CacheProperties ();
2016-11-20 18:31:29 +01:00
$cache -> addProperty ( $account -> id );
$cache -> addProperty ( $start );
$cache -> addProperty ( $end );
2016-12-11 17:05:48 +01:00
$cache -> addProperty ( 'chart.account.expense-category' );
2016-11-20 18:31:29 +01:00
if ( $cache -> has ()) {
2021-09-18 10:26:12 +02:00
return response () -> json ( $cache -> get ());
2016-11-20 18:31:29 +01:00
}
2019-05-30 12:31:19 +02:00
/** @var GroupCollectorInterface $collector */
$collector = app ( GroupCollectorInterface :: class );
2025-01-03 09:05:19 +01:00
$collector -> setAccounts ( new Collection ([ $account ])) -> setRange ( $start , $end ) -> withCategoryInformation () -> setTypes ([ TransactionTypeEnum :: WITHDRAWAL -> value ]);
2020-03-17 15:01:00 +01:00
$journals = $collector -> getExtractedJournals ();
$result = [];
$chartData = [];
2019-05-30 12:31:19 +02:00
/** @var array $journal */
foreach ( $journals as $journal ) {
2025-02-02 16:06:22 +01:00
$key = sprintf ( '%d-%d' , $journal [ 'category_id' ], $journal [ 'currency_id' ]);
2021-04-07 07:28:43 +02:00
if ( ! array_key_exists ( $key , $result )) {
2020-01-02 08:34:34 +01:00
$result [ $key ] = [
2018-09-10 17:57:20 +02:00
'total' => '0' ,
2024-12-22 08:43:12 +01:00
'category_id' => ( int ) $journal [ 'category_id' ],
2020-01-02 08:34:34 +01:00
'currency_name' => $journal [ 'currency_name' ],
2019-05-30 12:31:19 +02:00
'currency_symbol' => $journal [ 'currency_symbol' ],
2020-07-28 06:25:14 +02:00
'currency_code' => $journal [ 'currency_code' ],
2018-08-27 18:59:30 +02:00
];
}
2020-01-02 08:34:34 +01:00
$result [ $key ][ 'total' ] = bcadd ( $journal [ 'amount' ], $result [ $key ][ 'total' ]);
2016-11-20 18:31:29 +01:00
}
2025-02-02 16:06:22 +01:00
$names = $this -> getCategoryNames ( array_keys ( $result ));
2018-08-27 18:59:30 +02:00
foreach ( $result as $row ) {
$categoryId = $row [ 'category_id' ];
2018-08-28 14:20:04 +02:00
$name = $names [ $categoryId ] ? ? '(unknown)' ;
2024-12-22 08:43:12 +01:00
$label = ( string ) trans ( 'firefly.name_in_currency' , [ 'name' => $name , 'currency' => $row [ 'currency_name' ]]);
2020-07-28 06:25:14 +02:00
$chartData [ $label ] = [ 'amount' => $row [ 'total' ], 'currency_symbol' => $row [ 'currency_symbol' ], 'currency_code' => $row [ 'currency_code' ]];
2016-12-11 17:05:48 +01:00
}
2025-02-02 16:06:22 +01:00
$data = $this -> generator -> multiCurrencyPieChart ( $chartData );
2016-11-20 18:31:29 +01:00
$cache -> store ( $data );
2018-03-10 20:30:09 +01:00
return response () -> json ( $data );
2016-11-20 18:31:29 +01:00
}
2015-08-01 07:04:41 +02:00
/**
2016-05-13 15:53:39 +02:00
* Shows the balances for all the user ' s frontpage accounts .
2015-08-01 07:04:41 +02:00
*
2021-09-18 10:26:12 +02:00
* @ throws FireflyException
2023-12-22 07:58:35 +01:00
* */
2018-07-08 12:08:53 +02:00
public function frontpage ( AccountRepositoryInterface $repository ) : JsonResponse
2015-08-01 07:04:41 +02:00
{
2025-02-02 16:06:22 +01:00
$start = clone session ( 'start' , today ( config ( 'app.timezone' )) -> startOfMonth ());
$end = clone session ( 'end' , today ( config ( 'app.timezone' )) -> endOfMonth ());
$defaultSet = $repository -> getAccountsByType ([ AccountTypeEnum :: DEFAULT -> value , AccountTypeEnum :: ASSET -> value ]) -> pluck ( 'id' ) -> toArray ();
2024-11-19 06:31:49 +01:00
Log :: debug ( 'Default set is ' , $defaultSet );
2024-04-01 20:26:02 +02:00
$frontpage = app ( 'preferences' ) -> get ( 'frontpageAccounts' , $defaultSet );
$frontpageArray = ! is_array ( $frontpage -> data ) ? [] : $frontpage -> data ;
2024-11-19 06:31:49 +01:00
Log :: debug ( 'Frontpage preference set is ' , $frontpageArray );
2024-04-01 20:26:02 +02:00
if ( 0 === count ( $frontpageArray )) {
app ( 'preferences' ) -> set ( 'frontpageAccounts' , $defaultSet );
2024-11-19 06:31:49 +01:00
Log :: debug ( 'frontpage set is empty!' );
2016-12-11 17:30:55 +01:00
}
2025-02-02 16:06:22 +01:00
$accounts = $repository -> getAccountsById ( $frontpageArray );
2016-05-13 17:22:24 +02:00
2018-03-10 20:30:09 +01:00
return response () -> json ( $this -> accountBalanceChart ( $accounts , $start , $end ));
2015-08-01 07:04:41 +02:00
}
2023-06-21 12:34:58 +02:00
/**
* Shows the income grouped by category for an account , in all time .
*/
public function incomeCategoryAll ( AccountRepositoryInterface $repository , Account $account ) : JsonResponse
{
$start = $repository -> oldestJournalDate ( $account ) ? ? today ( config ( 'app.timezone' )) -> startOfMonth ();
$end = today ( config ( 'app.timezone' ));
return $this -> incomeCategory ( $account , $start , $end );
}
2016-11-20 18:31:29 +01:00
/**
2018-07-21 08:06:24 +02:00
* Shows all income per account for each category .
2016-11-20 18:31:29 +01:00
*/
2018-07-08 12:08:53 +02:00
public function incomeCategory ( Account $account , Carbon $start , Carbon $end ) : JsonResponse
2016-11-20 18:31:29 +01:00
{
2025-02-02 16:06:22 +01:00
$cache = new CacheProperties ();
2016-11-20 18:31:29 +01:00
$cache -> addProperty ( $account -> id );
$cache -> addProperty ( $start );
$cache -> addProperty ( $end );
2016-12-11 17:05:48 +01:00
$cache -> addProperty ( 'chart.account.income-category' );
2016-11-20 18:31:29 +01:00
if ( $cache -> has ()) {
2021-09-18 10:26:12 +02:00
return response () -> json ( $cache -> get ());
2016-11-20 18:31:29 +01:00
}
// grab all journals:
2019-05-30 12:31:19 +02:00
/** @var GroupCollectorInterface $collector */
$collector = app ( GroupCollectorInterface :: class );
2025-01-03 09:09:15 +01:00
$collector -> setAccounts ( new Collection ([ $account ])) -> setRange ( $start , $end ) -> withCategoryInformation () -> setTypes ([ TransactionTypeEnum :: DEPOSIT -> value ]);
2020-03-17 15:01:00 +01:00
$journals = $collector -> getExtractedJournals ();
$result = [];
$chartData = [];
2023-12-20 19:35:52 +01:00
2019-05-30 12:31:19 +02:00
/** @var array $journal */
foreach ( $journals as $journal ) {
2025-02-02 16:06:22 +01:00
$key = sprintf ( '%d-%d' , $journal [ 'category_id' ], $journal [ 'currency_id' ]);
2021-04-07 07:28:43 +02:00
if ( ! array_key_exists ( $key , $result )) {
2020-01-02 08:34:34 +01:00
$result [ $key ] = [
2018-09-10 17:57:20 +02:00
'total' => '0' ,
2020-01-02 08:34:34 +01:00
'category_id' => $journal [ 'category_id' ],
'currency_name' => $journal [ 'currency_name' ],
2019-05-30 12:31:19 +02:00
'currency_symbol' => $journal [ 'currency_symbol' ],
2021-03-28 11:46:23 +02:00
'currency_code' => $journal [ 'currency_code' ],
2018-08-27 18:59:30 +02:00
];
}
2020-01-02 08:34:34 +01:00
$result [ $key ][ 'total' ] = bcadd ( $journal [ 'amount' ], $result [ $key ][ 'total' ]);
2016-11-20 18:31:29 +01:00
}
2016-12-11 17:05:48 +01:00
2025-02-02 16:06:22 +01:00
$names = $this -> getCategoryNames ( array_keys ( $result ));
2018-08-27 18:59:30 +02:00
foreach ( $result as $row ) {
$categoryId = $row [ 'category_id' ];
2018-08-28 04:29:16 +02:00
$name = $names [ $categoryId ] ? ? '(unknown)' ;
2024-12-22 08:43:12 +01:00
$label = ( string ) trans ( 'firefly.name_in_currency' , [ 'name' => $name , 'currency' => $row [ 'currency_name' ]]);
2020-07-28 06:25:14 +02:00
$chartData [ $label ] = [ 'amount' => $row [ 'total' ], 'currency_symbol' => $row [ 'currency_symbol' ], 'currency_code' => $row [ 'currency_code' ]];
2016-12-11 17:05:48 +01:00
}
2025-02-02 16:06:22 +01:00
$data = $this -> generator -> multiCurrencyPieChart ( $chartData );
2016-11-20 18:31:29 +01:00
$cache -> store ( $data );
2018-03-10 20:30:09 +01:00
return response () -> json ( $data );
2016-11-20 18:31:29 +01:00
}
2016-12-11 11:15:19 +01:00
/**
2018-07-21 08:06:24 +02:00
* Shows overview of account during a single period .
*
2021-09-18 10:26:12 +02:00
* @ throws FireflyException
2023-12-22 20:12:38 +01:00
*/
2018-07-08 12:08:53 +02:00
public function period ( Account $account , Carbon $start , Carbon $end ) : JsonResponse
2016-12-11 11:15:19 +01:00
{
2025-02-02 05:38:47 +01:00
$start -> startOfDay ();
$end -> endOfDay ();
2025-02-02 06:24:10 +01:00
Log :: debug ( sprintf ( 'Now in period("%s", "%s")' , $start -> format ( 'Y-m-d H:i:s' ), $end -> format ( 'Y-m-d H:i:s' )));
2025-02-02 16:06:22 +01:00
$chartData = [];
$cache = new CacheProperties ();
2018-02-09 16:47:01 +01:00
$cache -> addProperty ( 'chart.account.period' );
2016-12-11 11:15:19 +01:00
$cache -> addProperty ( $start );
$cache -> addProperty ( $end );
2024-12-26 05:11:32 +01:00
$cache -> addProperty ( $this -> convertToNative );
2016-12-11 11:15:19 +01:00
$cache -> addProperty ( $account -> id );
if ( $cache -> has ()) {
2025-02-02 16:06:22 +01:00
// return response()->json($cache->get());
2016-12-11 11:15:19 +01:00
}
2020-04-12 06:23:35 +02:00
2024-12-26 05:11:32 +01:00
// collect and filter balances for the entire period.
2025-02-02 16:06:22 +01:00
$step = $this -> calculateStep ( $start , $end );
2024-12-26 05:11:32 +01:00
Log :: debug ( sprintf ( 'Step is %s' , $step ));
2025-02-04 21:26:40 +01:00
$locale = Steam :: getLocale ();
2025-02-02 16:06:22 +01:00
$return = [];
2024-12-26 09:19:04 +01:00
2024-12-26 05:11:32 +01:00
// fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
// have to make sure this chart is always based on the balance at the END of the period.
// This period depends on the size of the chart
2024-12-26 10:24:39 +01:00
$current = clone $start ;
$current = app ( 'navigation' ) -> endOfX ( $current , $step , null );
$format = ( string ) trans ( 'config.month_and_day_js' , [], $locale );
$accountCurrency = $this -> accountRepository -> getAccountCurrency ( $account );
2024-12-26 05:11:32 +01:00
2025-02-02 16:06:22 +01:00
$range = Steam :: finalAccountBalanceInRange ( $account , $start , $end , $this -> convertToNative );
$range = Steam :: filterAccountBalances ( $range , $account , $this -> convertToNative , $accountCurrency );
2024-12-26 08:53:16 +01:00
// temp, get end balance.
Log :: debug ( 'temp get end balance' );
Steam :: finalAccountBalance ( $account , $end );
Log :: debug ( 'END temp get end balance done' );
$previous = array_values ( $range )[ 0 ];
2024-12-26 10:24:39 +01:00
$accountCurrency ? ? = $this -> defaultCurrency ; // do this AFTER getting the balances.
2024-12-26 08:53:16 +01:00
Log :: debug ( 'Start chart loop.' );
2025-02-02 16:06:22 +01:00
$newRange = [];
$expectedIndex = 0 ;
2024-12-26 08:53:16 +01:00
Log :: debug ( 'Balances exist at:' );
foreach ( $range as $key => $value ) {
$newRange [] = [ 'date' => $key , 'info' => $value ];
2025-02-02 06:24:10 +01:00
Log :: debug ( sprintf ( '%d - %s (%s)' , count ( $newRange ) - 1 , $key , json_encode ( $value )));
2024-12-26 08:53:16 +01:00
}
2025-02-02 16:06:22 +01:00
$carbon = Carbon :: createFromFormat ( 'Y-m-d' , $newRange [ 0 ][ 'date' ]) -> endOfDay ();
2025-02-02 06:24:10 +01:00
Log :: debug ( sprintf ( 'Start of loop, $carbon is %s' , $carbon -> format ( 'Y-m-d H:i:s' )));
2024-12-26 08:53:16 +01:00
while ( $end -> gte ( $current )) {
$momentBalance = $previous ;
2025-02-02 06:24:10 +01:00
// $theDate = $current->format('Y-m-d');
Log :: debug ( sprintf ( 'Now at %s, with momentBalance %s' , $current -> format ( 'Y-m-d H:i:s' ), json_encode ( $momentBalance )));
// loop over the array with balances, find one that is earlier or on the same day.
2024-12-26 08:53:16 +01:00
while ( $carbon -> lte ( $current ) && array_key_exists ( $expectedIndex , $newRange )) {
2025-02-02 06:24:10 +01:00
Log :: debug ( sprintf ( '[a] Expected index is %d, $carbon is %s, current is %s' , $expectedIndex , $carbon -> format ( 'Y-m-d H:i:s' ), $current -> format ( 'Y-m-d H:i:s' )));
// grab the balance from that particular $expectedIndex
2024-12-26 08:53:16 +01:00
$momentBalance = $newRange [ $expectedIndex ][ 'info' ];
2024-12-26 10:24:39 +01:00
++ $expectedIndex ;
2024-12-26 05:11:32 +01:00
2025-02-02 06:24:10 +01:00
// make new carbon based on the next found date. this should stop the loop.
if ( array_key_exists ( $expectedIndex , $newRange )) {
$carbon = Carbon :: createFromFormat ( 'Y-m-d' , $newRange [ $expectedIndex ][ 'date' ]) -> endOfDay ();
}
}
Log :: debug ( sprintf ( 'momentBalance is now %s' , json_encode ( $momentBalance )));
2025-02-02 16:06:22 +01:00
$return = $this -> updateChartKeys ( $return , $momentBalance );
$previous = $momentBalance ;
2024-12-26 05:11:32 +01:00
// process each balance thing.
2024-12-26 08:53:16 +01:00
foreach ( $momentBalance as $key => $amount ) {
$label = $current -> isoFormat ( $format );
2024-12-26 05:11:32 +01:00
$return [ $key ][ 'entries' ][ $label ] = $amount ;
}
2025-02-02 16:06:22 +01:00
$current = app ( 'navigation' ) -> addPeriod ( $current , $step , 0 );
2024-12-26 05:11:32 +01:00
// here too, to fix #8041, the data is corrected to the end of the period.
2025-02-02 16:06:22 +01:00
$current = app ( 'navigation' ) -> endOfX ( $current , $step , null );
2020-04-12 06:23:35 +02:00
}
2024-12-26 08:53:16 +01:00
Log :: debug ( 'End of chart loop.' );
2024-12-26 05:11:32 +01:00
// second loop (yes) to create nice array with info! Yay!
2025-02-02 16:06:22 +01:00
$chartData = [];
2024-12-26 09:19:04 +01:00
2024-12-26 08:53:16 +01:00
foreach ( $return as $key => $info ) {
if ( 3 === strlen ( $key )) {
2024-12-26 05:11:32 +01:00
// assume it's a currency:
2024-12-26 08:53:16 +01:00
$setCurrency = $this -> currencyRepository -> findByCode ( $key );
2024-12-26 05:11:32 +01:00
$info [ 'currency_symbol' ] = $setCurrency -> symbol ;
$info [ 'currency_code' ] = $setCurrency -> code ;
$info [ 'label' ] = sprintf ( '%s (%s)' , $account -> name , $setCurrency -> symbol );
}
2024-12-26 08:53:16 +01:00
if ( 'balance' === $key ) {
2024-12-26 05:11:32 +01:00
$info [ 'currency_symbol' ] = $accountCurrency -> symbol ;
$info [ 'currency_code' ] = $accountCurrency -> code ;
$info [ 'label' ] = sprintf ( '%s (%s)' , $account -> name , $accountCurrency -> symbol );
}
2024-12-26 08:53:16 +01:00
if ( 'native_balance' === $key ) {
2024-12-26 05:11:32 +01:00
$info [ 'currency_symbol' ] = $this -> defaultCurrency -> symbol ;
$info [ 'currency_code' ] = $this -> defaultCurrency -> code ;
2024-12-26 08:53:16 +01:00
$info [ 'label' ] = sprintf ( '%s (%s) (%s)' , $account -> name , ( string ) trans ( 'firefly.sum' ), $this -> defaultCurrency -> symbol );
2024-12-26 05:11:32 +01:00
}
$chartData [] = $info ;
}
2025-02-02 16:06:22 +01:00
$data = $this -> generator -> multiSet ( $chartData );
2024-12-26 05:11:32 +01:00
$cache -> store ( $data );
return response () -> json ( $data );
2024-02-22 20:11:09 +01:00
}
2015-05-16 09:41:14 +02:00
/**
2016-05-13 15:53:39 +02:00
* Shows the balances for a given set of dates and accounts .
2015-05-16 09:41:14 +02:00
*
2022-10-30 11:43:17 +01:00
* TODO this chart is not multi currency aware .
2018-08-27 18:59:30 +02:00
*
2022-03-29 15:10:05 +02:00
* @ throws FireflyException
2023-12-22 20:12:38 +01:00
*/
2018-07-08 12:08:53 +02:00
public function report ( Collection $accounts , Carbon $start , Carbon $end ) : JsonResponse
2015-05-16 09:41:14 +02:00
{
2018-03-10 20:30:09 +01:00
return response () -> json ( $this -> accountBalanceChart ( $accounts , $start , $end ));
2015-05-16 09:41:14 +02:00
}
2021-03-28 11:46:23 +02:00
2016-10-14 19:59:10 +02:00
/**
* Shows the balances for all the user ' s revenue accounts .
*
2018-08-27 18:59:30 +02:00
* This chart is multi - currency aware .
2023-12-22 20:12:38 +01:00
*/
2018-08-27 18:59:30 +02:00
public function revenueAccounts () : JsonResponse
2016-10-14 19:59:10 +02:00
{
2018-08-27 18:59:30 +02:00
/** @var Carbon $start */
2025-02-02 16:06:22 +01:00
$start = clone session ( 'start' , today ( config ( 'app.timezone' )) -> startOfMonth ());
2023-12-20 19:35:52 +01:00
2018-08-27 18:59:30 +02:00
/** @var Carbon $end */
2025-02-02 16:06:22 +01:00
$end = clone session ( 'end' , today ( config ( 'app.timezone' )) -> endOfMonth ());
$cache = new CacheProperties ();
2016-10-14 19:59:10 +02:00
$cache -> addProperty ( $start );
$cache -> addProperty ( $end );
2024-12-24 19:03:47 +01:00
$cache -> addProperty ( $this -> convertToNative );
2016-12-11 17:05:48 +01:00
$cache -> addProperty ( 'chart.account.revenue-accounts' );
2016-10-14 19:59:10 +02:00
if ( $cache -> has ()) {
2024-12-25 07:13:41 +01:00
return response () -> json ( $cache -> get ());
2016-10-14 19:59:10 +02:00
}
$start -> subDay ();
2018-08-27 18:59:30 +02:00
// prep some vars:
2025-02-02 16:06:22 +01:00
$currencies = [];
$chartData = [];
$tempData = [];
2018-08-27 18:59:30 +02:00
// grab all accounts and names
2025-02-02 16:06:22 +01:00
$accounts = $this -> accountRepository -> getAccountsByType ([ AccountTypeEnum :: REVENUE -> value ]);
$accountNames = $this -> extractNames ( $accounts );
2018-08-27 18:59:30 +02:00
// grab all balances
2025-02-04 21:26:40 +01:00
$startBalances = Steam :: finalAccountsBalance ( $accounts , $start );
$endBalances = Steam :: finalAccountsBalance ( $accounts , $end );
2018-08-27 18:59:30 +02:00
2024-12-23 07:13:59 +01:00
2024-12-25 07:13:41 +01:00
// loop the accounts, then check for balance and currency info.
foreach ( $accounts as $account ) {
2024-12-31 08:18:40 +01:00
// Log::debug(sprintf('[b] Now in account #%d ("%s")', $account->id, $account->name));
2024-12-24 16:56:31 +01:00
$expenses = $endBalances [ $account -> id ] ? ? false ;
2024-12-25 07:13:41 +01:00
if ( false === $expenses ) {
Log :: error ( sprintf ( 'Found no end balance for account #%d' , $account -> id ));
2024-12-24 16:56:31 +01:00
continue ;
}
2024-12-25 07:13:41 +01:00
2024-12-24 16:56:31 +01:00
/**
* @ var string $key
* @ var string $endBalance
*/
foreach ( $expenses as $key => $endBalance ) {
2024-12-25 07:13:41 +01:00
if ( ! $this -> convertToNative && 'native_balance' === $key ) {
2024-12-24 16:56:31 +01:00
Log :: debug ( sprintf ( '[a] Will skip expense array "%s"' , $key ));
2024-12-25 07:13:41 +01:00
2024-12-23 06:55:14 +01:00
continue ;
}
2024-12-25 07:13:41 +01:00
if ( $this -> convertToNative && 'native_balance' !== $key ) {
2024-12-24 16:56:31 +01:00
Log :: debug ( sprintf ( '[b] Will skip expense array "%s"' , $key ));
2024-12-25 07:13:41 +01:00
2024-12-24 16:56:31 +01:00
continue ;
}
2024-12-31 08:09:51 +01:00
// Log::debug(sprintf('Will process expense array "%s" with amount %s', $key, $endBalance));
2025-02-02 16:06:22 +01:00
$searchCode = $this -> convertToNative ? $this -> defaultCurrency -> code : $key ;
2024-12-31 08:09:51 +01:00
// Log::debug(sprintf('Search code is %s', $searchCode));
2018-08-27 18:59:30 +02:00
// see if there is an accompanying start amount.
// grab the difference and find the currency.
2024-12-24 16:56:31 +01:00
$startBalance = ( $startBalances [ $account -> id ][ $key ] ? ? '0' );
2024-12-31 08:09:51 +01:00
// Log::debug(sprintf('Start balance is %s', $startBalance));
2025-02-02 16:06:22 +01:00
$diff = bcsub ( $endBalance , $startBalance );
2024-12-24 16:56:31 +01:00
$currencies [ $searchCode ] ? ? = $this -> currencyRepository -> findByCode ( $searchCode );
2018-08-27 18:59:30 +02:00
if ( 0 !== bccomp ( $diff , '0' )) {
// store the values in a temporary array.
$tempData [] = [
2024-12-24 16:56:31 +01:00
'name' => $accountNames [ $account -> id ],
2018-08-27 18:59:30 +02:00
'difference' => $diff ,
2024-12-22 08:43:12 +01:00
'diff_float' => ( float ) $diff , // intentional float
2024-12-24 16:56:31 +01:00
'currency_id' => $currencies [ $searchCode ] -> id ,
2018-08-27 18:59:30 +02:00
];
}
2016-10-14 19:59:10 +02:00
}
2016-12-11 16:38:21 +01:00
}
2016-10-14 19:59:10 +02:00
2024-12-24 16:56:31 +01:00
2024-12-23 07:13:59 +01:00
// recreate currencies, but on ID instead of code.
$newCurrencies = [];
2024-12-23 08:40:29 +01:00
foreach ( $currencies as $currency ) {
2024-12-23 07:13:59 +01:00
$newCurrencies [ $currency -> id ] = $currency ;
}
2025-02-02 16:06:22 +01:00
$currencies = $newCurrencies ;
2024-12-23 07:13:59 +01:00
2018-08-27 18:59:30 +02:00
// sort temp array by amount.
2025-02-02 16:06:22 +01:00
$amounts = array_column ( $tempData , 'diff_float' );
2020-07-26 14:18:25 +02:00
array_multisort ( $amounts , SORT_ASC , $tempData );
2018-08-27 18:59:30 +02:00
// loop all found currencies and build the data array for the chart.
/**
2023-06-21 12:34:58 +02:00
* @ var int $currencyId
2018-08-27 18:59:30 +02:00
* @ var TransactionCurrency $currency
*/
foreach ( $currencies as $currencyId => $currency ) {
$dataSet
= [
2025-02-02 16:06:22 +01:00
'label' => ( string ) trans ( 'firefly.earned' ),
'type' => 'bar' ,
'currency_symbol' => $currency -> symbol ,
'currency_code' => $currency -> code ,
'entries' => $this -> expandNames ( $tempData ),
];
2018-08-27 18:59:30 +02:00
$chartData [ $currencyId ] = $dataSet ;
}
// loop temp data and place data in correct array:
foreach ( $tempData as $entry ) {
$currencyId = $entry [ 'currency_id' ];
$name = $entry [ 'name' ];
$chartData [ $currencyId ][ 'entries' ][ $name ] = bcmul ( $entry [ 'difference' ], '-1' );
}
2025-02-02 16:06:22 +01:00
$data = $this -> generator -> multiSet ( $chartData );
2016-10-14 19:59:10 +02:00
$cache -> store ( $data );
2018-03-10 20:30:09 +01:00
return response () -> json ( $data );
2016-10-14 19:59:10 +02:00
}
2024-12-26 05:11:32 +01:00
private function updateChartKeys ( array $array , array $balances ) : array
{
foreach ( array_keys ( $balances ) as $key ) {
$array [ $key ] ? ? = [
'key' => $key ,
];
}
2024-12-26 05:25:46 +01:00
2024-12-26 05:11:32 +01:00
return $array ;
}
2015-05-20 19:56:14 +02:00
}