make sure reports work as expected.

This commit is contained in:
James Cole
2019-05-30 06:23:25 +02:00
parent bdf48227bb
commit 10a6ff9bf8
13 changed files with 446 additions and 391 deletions

View File

@@ -24,11 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Helpers\Chart;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\TransactionCollectorInterface;
use FireflyIII\Helpers\Filter\NegativeAmountFilter;
use FireflyIII\Helpers\Filter\OpposingAccountFilter;
use FireflyIII\Helpers\Filter\PositiveAmountFilter;
use FireflyIII\Helpers\Filter\TransferFilter;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionType;
@@ -48,9 +44,9 @@ class MetaPieChart implements MetaPieChartInterface
/** @var array The ways to group transactions, given the type of chart. */
static protected $grouping
= [
'account' => ['opposing_account_id'],
'budget' => ['transaction_journal_budget_id', 'transaction_budget_id'],
'category' => ['transaction_journal_category_id', 'transaction_category_id'],
'account' => ['destination_account_id'],
'budget' => ['budget_id'],
'category' => ['category_id'],
'tag' => [],
];
/** @var Collection Involved accounts. */
@@ -114,25 +110,29 @@ class MetaPieChart implements MetaPieChartInterface
// also collect all other transactions
if ($this->collectOtherObjects && 'expense' === $direction) {
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::WITHDRAWAL]);
$journals = $collector->getTransactions();
$sum = (string)$journals->sum('transaction_amount');
$journals = $collector->getExtractedJournals();
$sum = $this->sumJournals($journals);
$sum = bcmul($sum, '-1');
$sum = bcsub($sum, $this->total);
$chartData[$key] = $sum;
}
if ($this->collectOtherObjects && 'income' === $direction) {
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user);
$collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::DEPOSIT]);
$journals = $collector->getTransactions();
$sum = (string)$journals->sum('transaction_amount');
$journals = $collector->getExtractedJournals();
$sum = $this->sumJournals($journals);
$sum = bcsub($sum, $this->total);
$chartData[$key] = $sum;
}
@@ -140,6 +140,152 @@ class MetaPieChart implements MetaPieChartInterface
return $chartData;
}
/**
* Get all transactions.
*
* @param string $direction
*
* @return array
*/
protected function getTransactions(string $direction): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
if ('expense' === $direction) {
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
}
$collector->setUser($this->user);
$collector->setAccounts($this->accounts);
$collector->setRange($this->start, $this->end);
$collector->setTypes($types);
$collector->withAccountInformation();
$collector->setBudgets($this->budgets);
$collector->setCategories($this->categories);
$collector->withCategoryInformation();
$collector->withBudgetInformation();
// @codeCoverageIgnoreStart
if ($this->tags->count() > 0) {
$collector->setTags($this->tags);
}
// @codeCoverageIgnoreEnd
return $collector->getExtractedJournals();
}
/**
* Group by a specific field.
*
* @param array $array
* @param array $fields
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*
*/
protected function groupByFields(array $array, array $fields): array
{
$grouped = [];
if (0 === count($fields) && $this->tags->count() > 0) {
// do a special group on tags:
$grouped = $this->groupByTag($array); // @codeCoverageIgnore
}
if (0 !== count($fields) || $this->tags->count() <= 0) {
$grouped = [];
/** @var array $journal */
foreach ($array as $journal) {
$values = [];
foreach ($fields as $field) {
$values[] = (int)$journal[$field];
}
$value = max($values);
$grouped[$value] = $grouped[$value] ?? '0';
$grouped[$value] = bcadd($journal['amount'], $grouped[$value]);
}
}
return $grouped;
}
/**
* Group by tag (slightly different).
*
* @codeCoverageIgnore
*
* @param Collection $set
*
* @return array
*/
private function groupByTag(Collection $set): array
{
$grouped = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$journal = $transaction->transactionJournal;
$tags = $journal->tags;
/** @var Tag $tag */
foreach ($tags as $tag) {
$tagId = $tag->id;
$grouped[$tagId] = $grouped[$tagId] ?? '0';
$grouped[$tagId] = bcadd($transaction->transaction_amount, $grouped[$tagId]);
}
}
return $grouped;
}
/**
* Organise by certain type.
*
* @param string $type
* @param array $array
*
* @return array
*/
protected function organizeByType(string $type, array $array): array
{
$chartData = [];
$names = [];
$repository = app($this->repositories[$type]);
$repository->setUser($this->user);
foreach ($array as $objectId => $amount) {
if (!isset($names[$objectId])) {
$object = $repository->findNull((int)$objectId);
$name = null === $object ? '(no name)' : $object->name;
$names[$objectId] = $name ?? $object->tag;
}
$amount = app('steam')->positive($amount);
$this->total = bcadd($this->total, $amount);
$chartData[$names[$objectId]] = $amount;
}
return $chartData;
}
/**
* @param array $journals
* @return string
*/
private function sumJournals(array $journals): string
{
$sum = '0';
/** @var array $journal */
foreach ($journals as $journal) {
$amount = (string)$journal['amount'];
$sum = bcadd($sum, $amount);
}
return $sum;
}
/**
* Accounts setter.
*
@@ -267,140 +413,4 @@ class MetaPieChart implements MetaPieChartInterface
return $this;
}
/**
* Get all transactions.
*
* @param string $direction
*
* @return Collection
*/
protected function getTransactions(string $direction): Collection
{
/** @var TransactionCollectorInterface $collector */
$collector = app(TransactionCollectorInterface::class);
$types = [TransactionType::DEPOSIT, TransactionType::TRANSFER];
$collector->addFilter(NegativeAmountFilter::class);
if ('expense' === $direction) {
$types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER];
$collector->addFilter(PositiveAmountFilter::class);
$collector->removeFilter(NegativeAmountFilter::class);
}
$collector->setUser($this->user);
$collector->setAccounts($this->accounts);
$collector->setRange($this->start, $this->end);
$collector->setTypes($types);
$collector->withOpposingAccount();
$collector->addFilter(OpposingAccountFilter::class);
if ('income' === $direction) {
$collector->removeFilter(TransferFilter::class);
}
$collector->setBudgets($this->budgets);
$collector->setCategories($this->categories);
// @codeCoverageIgnoreStart
if ($this->tags->count() > 0) {
$collector->setTags($this->tags);
$collector->withCategoryInformation();
$collector->withBudgetInformation();
}
// @codeCoverageIgnoreEnd
return $collector->getTransactions();
}
/**
* Group by a specific field.
*
* @param Collection $set
* @param array $fields
*
* @return array
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*
*/
protected function groupByFields(Collection $set, array $fields): array
{
$grouped = [];
if (0 === \count($fields) && $this->tags->count() > 0) {
// do a special group on tags:
$grouped = $this->groupByTag($set); // @codeCoverageIgnore
}
if (0 !== \count($fields) || $this->tags->count() <= 0) {
$grouped = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$values = [];
foreach ($fields as $field) {
$values[] = (int)$transaction->$field;
}
$value = max($values);
$grouped[$value] = $grouped[$value] ?? '0';
$grouped[$value] = bcadd($transaction->transaction_amount, $grouped[$value]);
}
}
return $grouped;
}
/**
* Organise by certain type.
*
* @param string $type
* @param array $array
*
* @return array
*/
protected function organizeByType(string $type, array $array): array
{
$chartData = [];
$names = [];
$repository = app($this->repositories[$type]);
$repository->setUser($this->user);
foreach ($array as $objectId => $amount) {
if (!isset($names[$objectId])) {
$object = $repository->findNull((int)$objectId);
$name = null === $object ? '(no name)' : $object->name;
$names[$objectId] = $name ?? $object->tag;
}
$amount = app('steam')->positive($amount);
$this->total = bcadd($this->total, $amount);
$chartData[$names[$objectId]] = $amount;
}
return $chartData;
}
/**
* Group by tag (slightly different).
*
* @codeCoverageIgnore
*
* @param Collection $set
*
* @return array
*/
private function groupByTag(Collection $set): array
{
$grouped = [];
/** @var Transaction $transaction */
foreach ($set as $transaction) {
$journal = $transaction->transactionJournal;
$tags = $journal->tags;
/** @var Tag $tag */
foreach ($tags as $tag) {
$tagId = $tag->id;
$grouped[$tagId] = $grouped[$tagId] ?? '0';
$grouped[$tagId] = bcadd($transaction->transaction_amount, $grouped[$tagId]);
}
}
return $grouped;
}
}

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Helpers\Collector;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidDateException;
use Exception;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
@@ -37,8 +38,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use function count;
use function get_class;
use Log;
/**
* Class GroupCollector
@@ -226,16 +226,28 @@ class GroupCollector implements GroupCollectorInterface
{
$result = $augmentedGroup->toArray();
$result['tags'] = [];
$result['tag_ids'] = [];
$result['date'] = new Carbon($result['date']);
$result['created_at'] = new Carbon($result['created_at']);
$result['updated_at'] = new Carbon($result['updated_at']);
$result['reconciled'] = 1 === (int)$result['reconciled'];
if (isset($augmentedGroup['tag'])) {
$result['tags'][] = $augmentedGroup['tag'];
}
if (isset($augmentedGroup['tag_id'])) {
$result['tag_ids'][] = $augmentedGroup['tag_id'];
if (isset($augmentedGroup['tag_id'])) { // assume the other fields are present as well.
$tagId = (int)$augmentedGroup['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($augmentedGroup['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$result['tags'][$tagId] = [
'id' => (int)$result['tag_id'],
'name' => $result['tag_name'],
'date' => $tagDate,
'description' => $result['tag_description'],
'latitude' => $result['tag_latitude'],
'longitude' => $result['tag_longitude'],
'zoom_level' => $result['tag_zoom_level'],
];
}
return $result;
@@ -249,15 +261,26 @@ class GroupCollector implements GroupCollectorInterface
private function mergeTags(array $existingJournal, TransactionGroup $newGroup): array
{
$newArray = $newGroup->toArray();
if (isset($newArray['tag_id'])) {
$existingJournal['tag_ids'][] = (int)$newArray['tag_id'];
}
if (isset($newArray['tag'])) {
$existingJournal['tags'][] = $newArray['tag'];
if (isset($newArray['tag_id'])) { // assume the other fields are present as well.
$tagId = (int)$newGroup['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($newArray['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$existingJournal['tags'][$tagId] = [
'id' => (int)$newArray['tag_id'],
'name' => $newArray['tag_name'],
'date' => $tagDate,
'description' => $newArray['tag_description'],
'latitude' => $newArray['tag_latitude'],
'longitude' => $newArray['tag_longitude'],
'zoom_level' => $newArray['tag_zoom_level'],
];
}
$existingJournal['tags'] = array_unique($existingJournal['tags']);
$existingJournal['tag_ids'] = array_unique($existingJournal['tag_ids']);
return $existingJournal;
}
@@ -403,8 +426,10 @@ class GroupCollector implements GroupCollectorInterface
*/
public function setBudgets(Collection $budgets): GroupCollectorInterface
{
$this->withBudgetInformation();
$this->query->whereIn('budgets.id', $budgets->pluck('id')->toArray());
if ($budgets->count() > 0) {
$this->withBudgetInformation();
$this->query->whereIn('budgets.id', $budgets->pluck('id')->toArray());
}
return $this;
}
@@ -560,7 +585,12 @@ class GroupCollector implements GroupCollectorInterface
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
$this->fields[] = 'tags.id as tag_id';
$this->fields[] = 'tags.tag as tag';
$this->fields[] = 'tags.tag as tag_name';
$this->fields[] = 'tags.date as tag_date';
$this->fields[] = 'tags.description as tag_description';
$this->fields[] = 'tags.latitude as tag_latitude';
$this->fields[] = 'tags.longitude as tag_longitude';
$this->fields[] = 'tags.zoomLevel as tag_zoom_level';
}
}
@@ -726,8 +756,10 @@ class GroupCollector implements GroupCollectorInterface
*/
public function setCategories(Collection $categories): GroupCollectorInterface
{
$this->withCategoryInformation();
$this->query->where('categories.id', $categories->pluck('id')->toArray());
if ($categories->count() > 0) {
$this->withCategoryInformation();
$this->query->whereIn('categories.id', $categories->pluck('id')->toArray());
}
return $this;
}