2015-02-23 20:25:48 +01:00
< ? php
namespace FireflyIII\Helpers\Report ;
use Auth ;
use Carbon\Carbon ;
2015-03-31 22:46:11 +02:00
use Crypt ;
2015-02-23 20:25:48 +01:00
use DB ;
2015-02-23 21:19:16 +01:00
use FireflyIII\Models\Account ;
2015-05-16 15:43:58 +02:00
use FireflyIII\Models\Budget ;
2015-02-23 20:25:48 +01:00
use FireflyIII\Models\TransactionJournal ;
use Illuminate\Database\Eloquent\Builder ;
2015-03-31 22:46:11 +02:00
use Illuminate\Database\Eloquent\Model ;
2015-02-23 20:25:48 +01:00
use Illuminate\Database\Query\JoinClause ;
use Illuminate\Support\Collection ;
2015-02-23 21:19:16 +01:00
use Steam ;
2015-02-23 20:25:48 +01:00
/**
* Class ReportQuery
*
* @ package FireflyIII\Helpers\Report
*/
class ReportQuery implements ReportQueryInterface
{
2015-05-16 14:51:23 +02:00
/**
* This method returns all " expense " journals in a certain period , which are both transfers to a shared account
* and " ordinary " withdrawals . The query used is almost equal to ReportQueryInterface :: journalsByRevenueAccount but it does
* not group and returns different fields .
*
* @ param Carbon $start
* @ param Carbon $end
* @ param bool $includeShared
*
* @ return Collection
*
*/
public function expenseInPeriod ( Carbon $start , Carbon $end , $includeShared = false )
{
$query = $this -> queryJournalsWithTransactions ( $start , $end );
if ( $includeShared === false ) {
$query -> where (
function ( Builder $query ) {
$query -> where (
2015-05-17 10:30:18 +02:00
function ( Builder $q ) { // only get withdrawals not from a shared account
2015-05-16 14:51:23 +02:00
$q -> where ( 'transaction_types.type' , 'Withdrawal' );
$q -> where ( 'acm_from.data' , '!=' , '"sharedAsset"' );
}
);
$query -> orWhere (
2015-05-17 10:30:18 +02:00
function ( Builder $q ) { // and transfers from a shared account.
2015-05-16 14:51:23 +02:00
$q -> where ( 'transaction_types.type' , 'Transfer' );
$q -> where ( 'acm_to.data' , '=' , '"sharedAsset"' );
}
);
}
);
} else {
2015-05-17 10:30:18 +02:00
$query -> where ( 'transaction_types.type' , 'Withdrawal' ); // any withdrawal is fine.
2015-05-16 14:51:23 +02:00
}
$query -> groupBy ( 'transaction_journals.id' ) -> orderBy ( 'transaction_journals.date' );
// get everything, decrypt and return
2015-05-20 06:48:52 +02:00
$data = $query -> get (
[ 'transaction_journals.id' , 'transaction_journals.description' , 'transaction_journals.encrypted' , 'transaction_types.type' ,
2015-05-16 14:51:23 +02:00
DB :: Raw ( 'SUM(`t_from`.`amount`) as `queryAmount`' ),
2015-05-20 06:48:52 +02:00
'transaction_journals.date' , 't_to.account_id as account_id' , 'ac_to.name as name' , 'ac_to.encrypted as account_encrypted' ]
);
2015-05-16 14:51:23 +02:00
$data -> each (
function ( Model $object ) {
$object -> name = intval ( $object -> account_encrypted ) == 1 ? Crypt :: decrypt ( $object -> name ) : $object -> name ;
}
);
$data -> sortByDesc ( 'queryAmount' );
return $data ;
}
2015-05-20 06:48:52 +02:00
/**
* See ReportQueryInterface :: incomeInPeriodCorrected
*
* @ param Carbon $start
* @ param Carbon $end
* @ param bool $includeShared
*
* @ return Collection
*
*/
public function expenseInPeriodCorrected ( Carbon $start , Carbon $end , $includeShared = false )
{
$query = $this -> queryJournalsWithTransactions ( $start , $end );
if ( $includeShared === false ) {
$query -> where (
function ( Builder $query ) {
$query -> where (
function ( Builder $q ) { // only get withdrawals not from a shared account
$q -> where ( 'transaction_types.type' , 'Withdrawal' );
$q -> where ( 'acm_from.data' , '!=' , '"sharedAsset"' );
}
);
$query -> orWhere (
function ( Builder $q ) { // and transfers from a shared account.
$q -> where ( 'transaction_types.type' , 'Transfer' );
$q -> where ( 'acm_to.data' , '=' , '"sharedAsset"' );
}
);
}
);
} else {
$query -> where ( 'transaction_types.type' , 'Withdrawal' ); // any withdrawal is fine.
}
$query -> orderBy ( 'transaction_journals.date' );
// get everything
2015-05-20 18:18:05 +02:00
$data = $query -> get ([ 'transaction_journals.*' , 'transaction_types.type' , 'ac_to.name as name' , 'ac_to.id as account_id' , 'ac_to.encrypted as account_encrypted' ]);
2015-05-20 07:20:02 +02:00
$data -> each (
function ( TransactionJournal $journal ) {
if ( intval ( $journal -> account_encrypted ) == 1 ) {
$journal -> name = Crypt :: decrypt ( $journal -> name );
}
}
);
$data = $data -> filter (
function ( TransactionJournal $journal ) {
if ( $journal -> amount != 0 ) {
return $journal ;
}
}
);
2015-05-20 06:48:52 +02:00
return $data ;
}
2015-02-23 21:19:16 +01:00
/**
* Get a users accounts combined with various meta - data related to the start and end date .
*
* @ param Carbon $start
* @ param Carbon $end
2015-05-15 20:07:51 +02:00
* @ param bool $includeShared
2015-02-23 21:19:16 +01:00
*
* @ return Collection
*/
2015-05-15 20:07:51 +02:00
public function getAllAccounts ( Carbon $start , Carbon $end , $includeShared = false )
2015-02-23 21:19:16 +01:00
{
2015-03-10 19:51:24 +01:00
$query = Auth :: user () -> accounts () -> orderBy ( 'accounts.name' , 'ASC' )
-> accountTypeIn ([ 'Default account' , 'Asset account' , 'Cash account' ]);
2015-05-15 20:07:51 +02:00
if ( $includeShared === false ) {
2015-03-10 19:51:24 +01:00
$query -> leftJoin (
'account_meta' , function ( JoinClause $join ) {
$join -> on ( 'account_meta.account_id' , '=' , 'accounts.id' ) -> where ( 'account_meta.name' , '=' , 'accountRole' );
}
)
-> orderBy ( 'accounts.name' , 'ASC' )
-> where (
2015-03-29 08:03:53 +02:00
function ( Builder $query ) {
2015-03-10 19:51:24 +01:00
$query -> where ( 'account_meta.data' , '!=' , '"sharedAsset"' );
$query -> orWhereNull ( 'account_meta.data' );
}
);
}
$set = $query -> get ([ 'accounts.*' ]);
2015-02-23 21:19:16 +01:00
$set -> each (
function ( Account $account ) use ( $start , $end ) {
2015-05-03 10:07:18 +02:00
/**
* The balance for today always incorporates transactions
* made on today . So to get todays " start " balance , we sub one
* day .
*/
$yesterday = clone $start ;
$yesterday -> subDay ();
2015-02-23 21:19:16 +01:00
/** @noinspection PhpParamsInspection */
2015-05-03 10:07:18 +02:00
$account -> startBalance = Steam :: balance ( $account , $yesterday );
2015-02-23 21:19:16 +01:00
$account -> endBalance = Steam :: balance ( $account , $end );
}
);
return $set ;
}
2015-02-23 20:25:48 +01:00
/**
* This method returns all " income " journals in a certain period , which are both transfers from a shared account
* and " ordinary " deposits . The query used is almost equal to ReportQueryInterface :: journalsByRevenueAccount but it does
* not group and returns different fields .
*
* @ param Carbon $start
* @ param Carbon $end
2015-05-15 20:07:51 +02:00
* @ param bool $includeShared
2015-02-23 20:25:48 +01:00
*
* @ return Collection
*/
2015-05-15 20:07:51 +02:00
public function incomeInPeriod ( Carbon $start , Carbon $end , $includeShared = false )
2015-02-23 20:25:48 +01:00
{
2015-03-29 12:12:06 +02:00
$query = $this -> queryJournalsWithTransactions ( $start , $end );
2015-05-15 20:07:51 +02:00
if ( $includeShared === false ) {
2015-03-10 20:05:27 +01:00
// only get deposits not to a shared account
// and transfers to a shared account.
2015-03-10 19:51:24 +01:00
$query -> where (
2015-03-29 07:43:20 +02:00
function ( Builder $query ) {
2015-03-10 19:51:24 +01:00
$query -> where (
2015-03-29 07:43:20 +02:00
function ( Builder $q ) {
2015-03-10 19:51:24 +01:00
$q -> where ( 'transaction_types.type' , 'Deposit' );
$q -> where ( 'acm_to.data' , '!=' , '"sharedAsset"' );
}
);
$query -> orWhere (
2015-03-29 07:43:20 +02:00
function ( Builder $q ) {
2015-03-10 19:51:24 +01:00
$q -> where ( 'transaction_types.type' , 'Transfer' );
$q -> where ( 'acm_from.data' , '=' , '"sharedAsset"' );
}
);
}
);
2015-03-10 20:05:27 +01:00
} else {
// any deposit is fine.
$query -> where ( 'transaction_types.type' , 'Deposit' );
2015-03-10 19:51:24 +01:00
}
2015-04-20 20:54:55 +02:00
$query -> groupBy ( 'transaction_journals.id' ) -> orderBy ( 'transaction_journals.date' );
2015-03-10 19:51:24 +01:00
2015-03-31 22:46:11 +02:00
// get everything, decrypt and return
$data = $query -> get (
2015-03-10 19:51:24 +01:00
[ 'transaction_journals.id' ,
'transaction_journals.description' ,
'transaction_journals.encrypted' ,
'transaction_types.type' ,
2015-04-09 21:14:15 +02:00
DB :: Raw ( 'SUM(`t_to`.`amount`) as `queryAmount`' ),
2015-03-10 19:51:24 +01:00
'transaction_journals.date' ,
't_from.account_id as account_id' ,
2015-03-31 21:18:22 +02:00
'ac_from.name as name' ,
'ac_from.encrypted as account_encrypted'
]
2015-03-10 19:51:24 +01:00
);
2015-03-31 22:46:11 +02:00
$data -> each (
function ( Model $object ) {
$object -> name = intval ( $object -> account_encrypted ) == 1 ? Crypt :: decrypt ( $object -> name ) : $object -> name ;
}
);
2015-05-15 22:00:00 +02:00
$data -> sortByDesc ( 'queryAmount' );
2015-03-31 22:46:11 +02:00
return $data ;
2015-02-23 20:25:48 +01:00
}
2015-05-20 06:48:52 +02:00
/**
* This method works the same way as ReportQueryInterface :: incomeInPeriod does , but instead of returning results
* will simply list the transaction journals only . This should allow any follow up counting to be accurate with
* regards to tags .
*
* @ param Carbon $start
* @ param Carbon $end
* @ param bool $includeShared
*
* @ return Collection
*/
public function incomeInPeriodCorrected ( Carbon $start , Carbon $end , $includeShared = false )
{
$query = $this -> queryJournalsWithTransactions ( $start , $end );
if ( $includeShared === false ) {
// only get deposits not to a shared account
// and transfers to a shared account.
$query -> where (
function ( Builder $query ) {
$query -> where (
function ( Builder $q ) {
$q -> where ( 'transaction_types.type' , 'Deposit' );
$q -> where ( 'acm_to.data' , '!=' , '"sharedAsset"' );
}
);
$query -> orWhere (
function ( Builder $q ) {
$q -> where ( 'transaction_types.type' , 'Transfer' );
$q -> where ( 'acm_from.data' , '=' , '"sharedAsset"' );
}
);
}
);
} else {
// any deposit is fine.
$query -> where ( 'transaction_types.type' , 'Deposit' );
}
$query -> orderBy ( 'transaction_journals.date' );
// get everything
2015-05-20 18:18:05 +02:00
$data = $query -> get ([ 'transaction_journals.*' , 'transaction_types.type' , 'ac_from.name as name' , 'ac_from.id as account_id' , 'ac_from.encrypted as account_encrypted' ]);
2015-05-20 07:20:02 +02:00
$data -> each (
function ( TransactionJournal $journal ) {
if ( intval ( $journal -> account_encrypted ) == 1 ) {
$journal -> name = Crypt :: decrypt ( $journal -> name );
}
}
);
$data = $data -> filter (
function ( TransactionJournal $journal ) {
if ( $journal -> amount != 0 ) {
return $journal ;
}
}
);
2015-03-10 19:51:24 +01:00
2015-05-20 06:48:52 +02:00
return $data ;
}
2015-02-23 20:25:48 +01:00
2015-03-29 11:51:26 +02:00
/**
* @ param Account $account
2015-05-16 15:43:58 +02:00
* @ param Budget $budget
2015-03-29 11:51:26 +02:00
* @ param Carbon $start
* @ param Carbon $end
*
2015-05-16 15:43:58 +02:00
* @ return float
2015-03-29 11:51:26 +02:00
*/
2015-05-17 12:49:09 +02:00
public function spentInBudget ( Account $account , Budget $budget , Carbon $start , Carbon $end )
2015-03-29 11:51:26 +02:00
{
2015-05-16 15:43:58 +02:00
return floatval (
Auth :: user () -> transactionjournals ()
2015-05-16 16:47:52 +02:00
-> leftJoin ( 'transactions' , 'transactions.transaction_journal_id' , '=' , 'transaction_journals.id' )
2015-05-16 15:43:58 +02:00
-> leftJoin ( 'budget_transaction_journal' , 'budget_transaction_journal.transaction_journal_id' , '=' , 'transaction_journals.id' )
2015-05-16 16:47:52 +02:00
-> transactionTypes ([ 'Withdrawal' ])
2015-05-16 15:43:58 +02:00
-> where ( 'transactions.amount' , '<' , 0 )
-> where ( 'transactions.account_id' , $account -> id )
-> before ( $end )
-> after ( $start )
-> where ( 'budget_transaction_journal.budget_id' , $budget -> id )
-> sum ( 'transactions.amount' )
2015-05-16 16:47:52 +02:00
);
}
2015-05-20 07:07:46 +02:00
/**
* Covers tags
*
* @ param Account $account
* @ param Budget $budget
* @ param Carbon $start
* @ param Carbon $end
*
* @ return float
*/
public function spentInBudgetCorrected ( Account $account , Budget $budget , Carbon $start , Carbon $end )
{
return floatval (
2015-05-20 07:20:02 +02:00
Auth :: user () -> transactionjournals ()
-> leftJoin ( 'transactions' , 'transactions.transaction_journal_id' , '=' , 'transaction_journals.id' )
-> leftJoin ( 'budget_transaction_journal' , 'budget_transaction_journal.transaction_journal_id' , '=' , 'transaction_journals.id' )
-> transactionTypes ([ 'Withdrawal' ])
-> where ( 'transactions.account_id' , $account -> id )
-> before ( $end )
-> after ( $start )
-> where ( 'budget_transaction_journal.budget_id' , $budget -> id )
-> get ([ 'transaction_journals.*' ]) -> sum ( 'amount' )
) * - 1 ;
2015-05-20 07:07:46 +02:00
}
2015-05-16 16:47:52 +02:00
/**
* @ param Account $account
* @ param Carbon $start
* @ param Carbon $end
* @ param bool $shared
*
* @ return float
*/
public function spentNoBudget ( Account $account , Carbon $start , Carbon $end , $shared = false )
{
return floatval (
Auth :: user () -> transactionjournals ()
-> leftJoin ( 'transactions' , 'transactions.transaction_journal_id' , '=' , 'transaction_journals.id' )
-> leftJoin ( 'budget_transaction_journal' , 'budget_transaction_journal.transaction_journal_id' , '=' , 'transaction_journals.id' )
-> where ( 'transactions.amount' , '<' , 0 )
-> transactionTypes ([ 'Withdrawal' ])
-> where ( 'transactions.account_id' , $account -> id )
-> before ( $end )
-> after ( $start )
-> whereNull ( 'budget_transaction_journal.budget_id' )
-> sum ( 'transactions.amount' )
2015-05-16 15:43:58 +02:00
);
2015-03-29 11:51:26 +02:00
}
2015-03-29 12:12:06 +02:00
/**
* @ param Carbon $start
* @ param Carbon $end
*
* @ return Builder
*/
protected function queryJournalsWithTransactions ( Carbon $start , Carbon $end )
{
$query = TransactionJournal ::
leftJoin (
'transactions as t_from' , function ( JoinClause $join ) {
$join -> on ( 't_from.transaction_journal_id' , '=' , 'transaction_journals.id' ) -> where ( 't_from.amount' , '<' , 0 );
}
)
-> leftJoin ( 'accounts as ac_from' , 't_from.account_id' , '=' , 'ac_from.id' )
-> leftJoin (
'account_meta as acm_from' , function ( JoinClause $join ) {
$join -> on ( 'ac_from.id' , '=' , 'acm_from.account_id' ) -> where ( 'acm_from.name' , '=' , 'accountRole' );
}
)
-> leftJoin (
'transactions as t_to' , function ( JoinClause $join ) {
$join -> on ( 't_to.transaction_journal_id' , '=' , 'transaction_journals.id' ) -> where ( 't_to.amount' , '>' , 0 );
}
)
-> leftJoin ( 'accounts as ac_to' , 't_to.account_id' , '=' , 'ac_to.id' )
-> leftJoin (
'account_meta as acm_to' , function ( JoinClause $join ) {
$join -> on ( 'ac_to.id' , '=' , 'acm_to.account_id' ) -> where ( 'acm_to.name' , '=' , 'accountRole' );
}
)
-> leftJoin ( 'transaction_types' , 'transaction_types.id' , '=' , 'transaction_journals.transaction_type_id' );
$query -> before ( $end ) -> after ( $start ) -> where ( 'transaction_journals.user_id' , Auth :: user () -> id );
return $query ;
}
2015-03-29 08:14:32 +02:00
}