Fixes for transactions.

This commit is contained in:
James Cole 2016-05-11 17:17:43 +02:00
parent 529bf50c85
commit 037d84b810
6 changed files with 228 additions and 82 deletions

View File

@ -162,7 +162,6 @@ class CategoryController extends Controller
// list of ranges for list of periods: // list of ranges for list of periods:
// oldest transaction in category: // oldest transaction in category:
//$start = $repository->getFirstActivityDate($category);
$start = $repository->firstUseDate($category, new Collection); $start = $repository->firstUseDate($category, new Collection);
$range = Preferences::get('viewRange', '1M')->data; $range = Preferences::get('viewRange', '1M')->data;
$start = Navigation::startOfPeriod($start, $range); $start = Navigation::startOfPeriod($start, $range);

View File

@ -150,7 +150,7 @@ class BudgetController extends Controller
$cache->addProperty('budget'); $cache->addProperty('budget');
$cache->addProperty('all'); $cache->addProperty('all');
if ($cache->has()) { if ($cache->has()) {
return Response::json($cache->get()); //return Response::json($cache->get());
} }
$budgets = $repository->getActiveBudgets(); $budgets = $repository->getActiveBudgets();
$repetitions = $repository->getAllBudgetLimitRepetitions($start, $end); $repetitions = $repository->getAllBudgetLimitRepetitions($start, $end);
@ -203,6 +203,8 @@ class BudgetController extends Controller
$data = $this->generator->frontpage($allEntries); $data = $this->generator->frontpage($allEntries);
$cache->store($data); $cache->store($data);
return ' ' . json_encode($data);
return Response::json($data); return Response::json($data);
} }

View File

@ -9,7 +9,6 @@ use FireflyIII\Generator\Chart\Category\CategoryChartGeneratorInterface;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Category; use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI; use FireflyIII\Repositories\Category\CategoryRepositoryInterface as CRI;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log; use Log;
@ -148,10 +147,10 @@ class CategoryController extends Controller
} }
/** /**
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* @param Collection $accounts * @param Collection $accounts
* @param Collection $categories * @param Collection $categories
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
*/ */
@ -241,8 +240,8 @@ class CategoryController extends Controller
// return Response::json($cache->get()); // return Response::json($cache->get());
} }
/** @var CategoryRepositoryInterface $repository */ /** @var CRI $repository */
$repository = app(CategoryRepositoryInterface::class); $repository = app(CRI::class);
$categoryCollection = new Collection([$category]); $categoryCollection = new Collection([$category]);
// loop over period, add by users range: // loop over period, add by users range:
$current = clone $start; $current = clone $start;

View File

@ -15,7 +15,6 @@ use FireflyIII\User;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
/** /**
* Class BudgetRepository * Class BudgetRepository
@ -78,7 +77,7 @@ class BudgetRepository implements BudgetRepositoryInterface
{ {
$oldest = Carbon::create()->startOfYear(); $oldest = Carbon::create()->startOfYear();
$journal = $budget->transactionjournals()->orderBy('date', 'ASC')->first(); $journal = $budget->transactionjournals()->orderBy('date', 'ASC')->first();
if ($journal) { if (!is_null($journal)) {
$oldest = $journal->date < $oldest ? $journal->date : $oldest; $oldest = $journal->date < $oldest ? $journal->date : $oldest;
} }
@ -86,8 +85,9 @@ class BudgetRepository implements BudgetRepositoryInterface
->transactions() ->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.id')
->orderBy('transaction_journals.date', 'ASC')->first(['transactions.*', 'transaction_journals.date']); ->orderBy('transaction_journals.date', 'ASC')->first(['transactions.*', 'transaction_journals.date']);
if ($transaction) { if (!is_null($transaction)) {
$oldest = $transaction->date < $oldest ? $transaction->date : $oldest; $carbon = new Carbon($transaction->date);
$oldest = $carbon < $oldest ? $carbon : $oldest;
} }
return $oldest; return $oldest;
@ -187,8 +187,8 @@ class BudgetRepository implements BudgetRepositoryInterface
// first get all journals for all budget(s): // first get all journals for all budget(s):
$journalQuery = $this->user->transactionjournals() $journalQuery = $this->user->transactionjournals()
->expanded() ->expanded()
->before($end)
->sortCorrectly() ->sortCorrectly()
->before($end)
->after($start) ->after($start)
->leftJoin( ->leftJoin(
'transactions as source', 'transactions as source',
@ -204,6 +204,7 @@ class BudgetRepository implements BudgetRepositoryInterface
} }
// get them: // get them:
$journals = $journalQuery->get(TransactionJournal::queryFields()); $journals = $journalQuery->get(TransactionJournal::queryFields());
//Log::debug('journalsInPeriod journal count is ' . $journals->count()); //Log::debug('journalsInPeriod journal count is ' . $journals->count());
// then get transactions themselves. // then get transactions themselves.
@ -302,15 +303,80 @@ class BudgetRepository implements BudgetRepositoryInterface
*/ */
public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end) : string public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end) : string
{ {
$set = $this->journalsInPeriod($budgets, $accounts, $start, $end); // first collect actual transaction journals (fairly easy)
//Log::debug('spentInPeriod set count is ' . $set->count()); $query = $this->user
$sum = '0'; ->transactionjournals()
/** @var TransactionJournal $journal */ ->distinct()
foreach ($set as $journal) { ->leftJoin(
$sum = bcadd($sum, TransactionJournal::amount($journal)); 'transactions as t', function (JoinClause $join) {
$join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0);
}
);
if ($end >= $start) {
$query->before($end)->after($start);
}
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$query->whereIn('t.account_id', $accountIds);
}
if ($budgets->count() > 0) {
$budgetIds = $budgets->pluck('id')->toArray();
$query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$query->whereIn('budget_transaction_journal.budget_id', $budgetIds);
} }
Log::debug('spentInPeriod between ' . $start->format('Y-m-d') . ' and ' . $end->format('Y-m-d') . ' is ' . $sum); // that should do it:
$first = strval($query->sum('t.amount'));
// then collection transactions (harder)
$query = $this->user->transactions()
->where('transactions.amount', '<', 0)
->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59'));
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$query->whereIn('transactions.account_id', $accountIds);
}
if ($budgets->count() > 0) {
$budgetIds = $budgets->pluck('id')->toArray();
$query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id');
$query->whereIn('budget_transaction.budget_id', $budgetIds);
}
$second = strval($query->sum('transactions.amount'));
return bcadd($first, $second);
}
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function spentInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): string
{
$query = $this->user->transactionjournals()
->distinct()
->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin(
'transactions as t', function (JoinClause $join) {
$join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0);
}
)
->leftJoin('budget_transaction', 't.id', '=', 'budget_transaction.transaction_id')
->whereNull('budget_transaction_journal.id')
->whereNull('budget_transaction.id')
->before($end)
->after($start);
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$query->whereIn('t.account_id', $accountIds);
}
$sum = strval($query->sum('t.amount'));
return $sum; return $sum;
} }
@ -402,25 +468,4 @@ class BudgetRepository implements BudgetRepositoryInterface
return $limit; return $limit;
} }
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function spentInPeriodWithoutBudget(Collection $accounts, Carbon $start, Carbon $end): string
{
$set = $this->journalsInPeriodWithoutBudget($accounts, $start, $end);
//Log::debug('spentInPeriod set count is ' . $set->count());
$sum = '0';
/** @var TransactionJournal $journal */
foreach ($set as $journal) {
$sum = bcadd($sum, TransactionJournal::amount($journal));
}
Log::debug('spentInPeriodWithoutBudget between ' . $start->format('Y-m-d') . ' and ' . $end->format('Y-m-d') . ' is ' . $sum);
return $sum;
}
} }

View File

@ -8,8 +8,10 @@ use FireflyIII\Models\Category;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
/** /**
* Class CategoryRepository * Class CategoryRepository
@ -53,12 +55,24 @@ class CategoryRepository implements CategoryRepositoryInterface
*/ */
public function earnedInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string public function earnedInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string
{ {
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
$journals = $this->journalsInPeriod($categories, $accounts, $types, $start, $end); $sum = bcmul($this->sumInPeriod($categories, $accounts, $types, $start, $end), '-1');
$sum = '0';
foreach ($journals as $journal) { return $sum;
$sum = bcadd(TransactionJournal::amount($journal), $sum);
} }
/**
* @param Collection $accounts
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function earnedInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) :string
{
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
$sum = $this->sumInPeriodWithoutCategory($accounts, $types, $start, $end);
return $sum; return $sum;
} }
@ -80,7 +94,6 @@ class CategoryRepository implements CategoryRepositoryInterface
return $category; return $category;
} }
/** /**
* @param Category $category * @param Category $category
* @param Collection $accounts * @param Collection $accounts
@ -118,10 +131,13 @@ class CategoryRepository implements CategoryRepositoryInterface
$firstTransactionQuery->whereIn('transactions.account_id', $ids); $firstTransactionQuery->whereIn('transactions.account_id', $ids);
} }
$firstTransaction = $firstJournalQuery->first(['transaction_journals.*']); $firstTransaction = $firstTransactionQuery->first(['transaction_journals.*']);
if (!is_null($firstTransaction) && !is_null($first) && $firstTransaction->date < $first) { if (!is_null($firstTransaction) && ((!is_null($first) && $firstTransaction->date < $first) || is_null($first))) {
$first = $firstTransaction->date; $first = new Carbon($firstTransaction->date);
}
if (is_null($first)) {
return new Carbon('1900-01-01');
} }
return $first; return $first;
@ -162,25 +178,30 @@ class CategoryRepository implements CategoryRepositoryInterface
$first = $query->get(TransactionJournal::queryFields()); $first = $query->get(TransactionJournal::queryFields());
// then collection transactions (harder) // then collection transactions (harder)
$query = $this->user->transactions(); $query = $this->user->transactionjournals()->distinct()
$query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id'); ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
$query->where('category_transaction.category_id', $category->id); ->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id')
->where('category_transaction.category_id', $category->id);
$second = $query->get(['transaction_journals.*']); $second = $query->get(['transaction_journals.*']);
$complete = $complete->merge($first); $complete = $complete->merge($first);
$complete = $complete->merge($second); $complete = $complete->merge($second);
// sort: // sort:
/** @var Collection $complete */
$complete = $complete->sortByDesc( $complete = $complete->sortByDesc(
function (TransactionJournal $journal) { function ($model) {
return $journal->date->format('Ymd'); $date = new Carbon($model->date);
return intval($date->format('U'));
} }
); );
// create paginator // create paginator
$offset = ($page - 1) * $pageSize; $offset = ($page - 1) * $pageSize;
$subSet = $complete->slice($offset, $pageSize); Log::debug('Page is ' . $page);
Log::debug('Offset is ' . $offset);
Log::debug('pagesize is ' . $pageSize);
$subSet = $complete->slice($offset, $pageSize)->all();
$paginator = new LengthAwarePaginator($subSet, $complete->count(), $pageSize, $page); $paginator = new LengthAwarePaginator($subSet, $complete->count(), $pageSize, $page);
return $paginator; return $paginator;
@ -322,6 +343,7 @@ class CategoryRepository implements CategoryRepositoryInterface
/** @var TransactionJournal $first */ /** @var TransactionJournal $first */
$lastJournalQuery = $category->transactionjournals()->orderBy('date', 'DESC'); $lastJournalQuery = $category->transactionjournals()->orderBy('date', 'DESC');
Log::debug('lastUseDate ' . $category->name . ' (' . $category->id . ')');
if ($accounts->count() > 0) { if ($accounts->count() > 0) {
// filter journals: // filter journals:
@ -334,6 +356,7 @@ class CategoryRepository implements CategoryRepositoryInterface
if ($lastJournal) { if ($lastJournal) {
$last = $lastJournal->date; $last = $lastJournal->date;
Log::debug('last is now ' . $last);
} }
// check transactions: // check transactions:
@ -347,10 +370,15 @@ class CategoryRepository implements CategoryRepositoryInterface
$lastTransactionQuery->whereIn('transactions.account_id', $ids); $lastTransactionQuery->whereIn('transactions.account_id', $ids);
} }
$lastTransaction = $lastJournalQuery->first(['transaction_journals.*']); $lastTransaction = $lastTransactionQuery->first(['transaction_journals.*']);
if (!is_null($lastTransaction)) {
}
if (!is_null($lastTransaction) && ((!is_null($last) && $lastTransaction->date < $last) || is_null($last))) {
$last = new Carbon($lastTransaction->date);
}
if (!is_null($lastTransaction) && !is_null($last) && $lastTransaction->date < $last) { if (is_null($last)) {
$last = $lastTransaction->date; return new Carbon('1900-01-01');
} }
return $last; return $last;
@ -366,12 +394,8 @@ class CategoryRepository implements CategoryRepositoryInterface
*/ */
public function spentInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string public function spentInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string
{ {
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
$journals = $this->journalsInPeriod($categories, $accounts, $types, $start, $end); $sum = $this->sumInPeriod($categories, $accounts, $types, $start, $end);
$sum = '0';
foreach ($journals as $journal) {
$sum = bcadd(TransactionJournal::amount($journal), $sum);
}
return $sum; return $sum;
} }
@ -385,12 +409,8 @@ class CategoryRepository implements CategoryRepositoryInterface
*/ */
public function spentInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : string public function spentInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) : string
{ {
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
$journals = $this->journalsInPeriodWithoutCategory($accounts, $types, $start, $end); $sum = $this->sumInPeriodWithoutCategory($accounts, $types, $start, $end);
$sum = '0';
foreach ($journals as $journal) {
$sum = bcadd(TransactionJournal::amount($journal), $sum);
}
return $sum; return $sum;
} }
@ -429,21 +449,100 @@ class CategoryRepository implements CategoryRepositoryInterface
} }
/** /**
* @param Collection $categories
* @param Collection $accounts * @param Collection $accounts
* @param array $types
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return string * @return string
*/ */
public function earnedInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end) :string private function sumInPeriod(Collection $categories, Collection $accounts, array $types, Carbon $start, Carbon $end): string
{ {
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; // first collect actual transaction journals (fairly easy)
$journals = $this->journalsInPeriodWithoutCategory($accounts, $types, $start, $end); $query = $this->user
$sum = '0'; ->transactionjournals()
foreach ($journals as $journal) { ->distinct()
$sum = bcadd(TransactionJournal::amount($journal), $sum); ->transactionTypes($types)
->leftJoin(
'transactions as t', function (JoinClause $join) {
$join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0);
}
);
if ($end >= $start) {
$query->before($end)->after($start);
}
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$query->whereIn('t.account_id', $accountIds);
}
if ($categories->count() > 0) {
$categoryIds = $categories->pluck('id')->toArray();
$query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$query->whereIn('category_transaction_journal.category_id', $categoryIds);
} }
// that should do it:
$first = strval($query->sum('t.amount'));
// then collection transactions (harder)
$query = $this->user->transactions()
->where('transactions.amount', '<', 0)
->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d 23:59:59'));
if (count($types) > 0) {
$query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id');
$query->whereIn('transaction_types.type', $types);
}
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$query->whereIn('transactions.account_id', $accountIds);
}
if ($categories->count() > 0) {
$categoryIds = $categories->pluck('id')->toArray();
$query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id');
$query->whereIn('category_transaction.category_id', $categoryIds);
}
$second = strval($query->sum('transactions.amount'));
return bcadd($first, $second);
}
/**
* @param Collection $accounts
* @param array $types
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
private function sumInPeriodWithoutCategory(Collection $accounts, array $types, Carbon $start, Carbon $end): string
{
$query = $this->user->transactionjournals()
->distinct()
->transactionTypes($types)
->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin(
'transactions as t', function (JoinClause $join) {
$join->on('t.transaction_journal_id', '=', 'transaction_journals.id')->where('amount', '<', 0);
}
)
->leftJoin('category_transaction', 't.id', '=', 'category_transaction.transaction_id')
->whereNull('category_transaction_journal.id')
->whereNull('category_transaction.id')
->before($end)
->after($start);
if ($accounts->count() > 0) {
$accountIds = $accounts->pluck('id')->toArray();
$query->whereIn('t.account_id', $accountIds);
}
$sum = strval($query->sum('t.amount'));
return $sum; return $sum;
} }
} }

View File

@ -43,6 +43,7 @@ class Journal extends Twig_Extension
} }
$array[] = '<a title="' . e($entry->name) . '" href="' . route('accounts.show', $entry->id) . '">' . e($entry->name) . '</a>'; $array[] = '<a title="' . e($entry->name) . '" href="' . route('accounts.show', $entry->id) . '">' . e($entry->name) . '</a>';
} }
$array = array_unique($array);
$result = join(', ', $array); $result = join(', ', $array);
$cache->store($result); $cache->store($result);
@ -111,6 +112,7 @@ class Journal extends Twig_Extension
} }
$array[] = '<a title="' . e($entry->name) . '" href="' . route('accounts.show', $entry->id) . '">' . e($entry->name) . '</a>'; $array[] = '<a title="' . e($entry->name) . '" href="' . route('accounts.show', $entry->id) . '">' . e($entry->name) . '</a>';
} }
$array = array_unique($array);
$result = join(', ', $array); $result = join(', ', $array);
$cache->store($result); $cache->store($result);