diff --git a/app/Models/Account.php b/app/Models/Account.php index e76ba418ee..82c6369aff 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -241,4 +241,12 @@ class Account extends Model get: static fn ($value) => (string)$value, ); } + + public function primaryPeriodStatistics(): MorphMany + + { + + return $this->morphMany(PeriodStatistic::class, 'primary_statable'); + + } } diff --git a/app/Models/PeriodStatistic.php b/app/Models/PeriodStatistic.php new file mode 100644 index 0000000000..0eb7d1f9b7 --- /dev/null +++ b/app/Models/PeriodStatistic.php @@ -0,0 +1,56 @@ + 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'date' => SeparateTimezoneCaster::class, + ]; + } + + protected function count(): Attribute + { + return Attribute::make( + get: static fn ($value) => (int)$value, + ); + } + + + public function primaryStatable(): MorphTo + { + + return $this->morphTo(); + + } + + public function secondaryStatable(): MorphTo + { + + return $this->morphTo(); + + } + public function tertiaryStatable(): MorphTo + { + + return $this->morphTo(); + + } + + // +} diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index 02ff3d729b..cff9567090 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -43,6 +43,8 @@ use FireflyIII\Repositories\AuditLogEntry\ALERepository; use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface; use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepository; use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface; +use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepository; +use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface; use FireflyIII\Repositories\TransactionType\TransactionTypeRepository; use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface; use FireflyIII\Repositories\User\UserRepository; @@ -161,6 +163,7 @@ class FireflyServiceProvider extends ServiceProvider $this->app->bind(AttachmentHelperInterface::class, AttachmentHelper::class); $this->app->bind(ALERepositoryInterface::class, ALERepository::class); + $this->app->bind(PeriodStatisticRepositoryInterface::class, PeriodStatisticRepository::class); $this->app->bind( static function (Application $app): ObjectGroupRepositoryInterface { diff --git a/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php b/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php new file mode 100644 index 0000000000..f7665f2b6d --- /dev/null +++ b/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php @@ -0,0 +1,65 @@ +. + */ + +namespace FireflyIII\Repositories\PeriodStatistic; + +use Carbon\Carbon; +use FireflyIII\Models\PeriodStatistic; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; + +class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface +{ + + public function findPeriodStatistics(Model $model, Carbon $start, Carbon $end, array $types): Collection + { + return $model->primaryPeriodStatistics() + ->where('start', $start) + ->where('end', $end) + ->whereIn('type', $types) + ->get(); + } + + public function findPeriodStatistic(Model $model, Carbon $start, Carbon $end, string $type): Collection + { + return $model->primaryPeriodStatistics() + ->where('start', $start) + ->where('end', $end) + ->where('type', $type) + ->get(); + } + + public function saveStatistic(Model $model, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic + { + $stat = new PeriodStatistic; + $stat->primaryStatable()->associate($model); + $stat->transaction_currency_id = $currencyId; + $stat->start = $start; + $stat->start_tz = $start->format('e'); + $stat->end = $end; + $stat->end_tz = $end->format('e'); + $stat->amount = $amount; + $stat->count = $count; + $stat->type = $type; + $stat->save(); + return $stat; + } +} diff --git a/app/Repositories/PeriodStatistic/PeriodStatisticRepositoryInterface.php b/app/Repositories/PeriodStatistic/PeriodStatisticRepositoryInterface.php new file mode 100644 index 0000000000..3d6245022a --- /dev/null +++ b/app/Repositories/PeriodStatistic/PeriodStatisticRepositoryInterface.php @@ -0,0 +1,38 @@ +. + */ + +namespace FireflyIII\Repositories\PeriodStatistic; + +use Carbon\Carbon; +use FireflyIII\Models\PeriodStatistic; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Collection; + +interface PeriodStatisticRepositoryInterface +{ + + public function findPeriodStatistics(Model $model, Carbon $start, Carbon $end, array $types): Collection; + + public function findPeriodStatistic(Model $model, Carbon $start, Carbon $end, string $type): Collection; + + public function saveStatistic(Model $model, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic; + +} diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php index afbc198ce2..6944eb5f26 100644 --- a/app/Support/Http/Controllers/PeriodOverview.php +++ b/app/Support/Http/Controllers/PeriodOverview.php @@ -30,11 +30,13 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; use FireflyIII\Models\Category; +use FireflyIII\Models\PeriodStatistic; use FireflyIII\Models\Tag; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface; use FireflyIII\Support\CacheProperties; -use FireflyIII\Support\Debug\Timer; +use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Facades\Navigation; use Illuminate\Support\Facades\Log; @@ -67,8 +69,9 @@ use Illuminate\Support\Facades\Log; */ trait PeriodOverview { - protected AccountRepositoryInterface $accountRepository; - protected JournalRepositoryInterface $journalRepos; + protected AccountRepositoryInterface $accountRepository; + protected JournalRepositoryInterface $journalRepos; + protected PeriodStatisticRepositoryInterface $periodStatisticRepo; /** * This method returns "period entries", so nov-2015, dec-2015, etc. (this depends on the users session range) @@ -79,71 +82,107 @@ trait PeriodOverview */ protected function getAccountPeriodOverview(Account $account, Carbon $start, Carbon $end): array { - Log::debug('Now in getAccountPeriodOverview()'); - $timer = Timer::getInstance(); - $timer->start('account-period-total'); - $this->accountRepository = app(AccountRepositoryInterface::class); - $range = Navigation::getViewRange(true); - [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; - - // properties for cache - $cache = new CacheProperties(); - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty('account-show-period-entries'); - $cache->addProperty($account->id); - if ($cache->has()) { - Log::debug('Return CACHED in getAccountPeriodOverview()'); - - return $cache->get(); - } - + Log::debug(sprintf('Now in getAccountPeriodOverview(#%d, %s %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d'))); + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class); + $range = Navigation::getViewRange(true); + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; /** @var array $dates */ - $dates = Navigation::blockPeriods($start, $end, $range); - $entries = []; - - // run a custom query because doing this with the collector is MEGA slow. - $timer->start('account-period-collect'); - $transactions = $this->accountRepository->periodCollection($account, $start, $end); - $timer->stop('account-period-collect'); - // loop dates + $dates = Navigation::blockPeriods($start, $end, $range); + $entries = []; Log::debug(sprintf('Count of loops: %d', count($dates))); - $loops = 0; - // stop after 10 loops for memory reasons. - $timer->start('account-period-loop'); foreach ($dates as $currentDate) { - $title = Navigation::periodShow($currentDate['start'], $currentDate['period']); - [$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']); - [$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']); - [$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']); - [$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']); - $entries[] - = [ - 'title' => $title, - 'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), - 'total_transactions' => count($spent) + count($earned) + count($transferredAway) + count($transferredIn), - 'spent' => $this->groupByCurrency($spent), - 'earned' => $this->groupByCurrency($earned), - 'transferred_away' => $this->groupByCurrency($transferredAway), - 'transferred_in' => $this->groupByCurrency($transferredIn), - ]; - ++$loops; + $entries[] = $this->getSingleAccountPeriod($account, $currentDate['start'], $currentDate['end']); } - $timer->stop('account-period-loop'); - $cache->store($entries); - $timer->stop('account-period-total'); Log::debug('End of getAccountPeriodOverview()'); return $entries; } + protected function getSingleAccountPeriod(Account $account, Carbon $start, Carbon $end): array + { + Log::debug(sprintf('Now in getSingleAccountPeriod(#%d, %s %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d'))); + $types = ['spent', 'earned', 'transferred_in', 'transferred_away']; + $return = [ + 'title' => Navigation::periodShow($start, $end), + 'route' => route('accounts.show', [$account->id, $start->format('Y-m-d'), $start->format('Y-m-d')]), + 'total_transactions' => 0, + ]; + foreach ($types as $type) { + $set = $this->getSingleAccountPeriodByType($account, $start, $end, $type); + $return['total_transactions'] += $set['count']; + unset($set['count']); + $return[$type] = $set; + } + return $return; + } + + protected function getSingleAccountPeriodByType(Account $account, Carbon $start, Carbon $end, string $type): array + { + Log::debug(sprintf('Now in getSingleAccountPeriodByType(#%d, %s %s, %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d'), $type)); + $statistics = $this->periodStatisticRepo->findPeriodStatistic($account, $start, $end, $type); + + // nothing found, regenerate them. + if (0 === $statistics->count()) { + $transactions = $this->accountRepository->periodCollection($account, $start, $end); + switch ($type) { + default: + throw new FireflyException(sprintf('Cannot deal with account period type %s', $type)); + case 'spent': + $result = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $start, $end); + break; + case 'earned': + $result = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $start, $end); + break; + case 'transferred_in': + $result = $this->filterTransfers('in', $transactions, $start, $end); + break; + case 'transferred_away': + $result = $this->filterTransfers('away', $transactions, $start, $end); + break; + } + // each result must be grouped by currency, then saved as period statistic. + $grouped = $this->groupByCurrency($result); + + // TODO save as statistic. + $this->saveGroupedAsStatistics($account, $start, $end, $type, $grouped); + return $grouped; + } + $grouped = [ + 'count' => 0, + ]; + /** @var PeriodStatistic $statistic */ + foreach($statistics as $statistic) { + $id = (int) $statistic->transaction_currency_id; + $currency = Amount::getTransactionCurrencyById($id); + $grouped[$id] = [ + 'amount' => (string) $statistic->amount, + 'count' => (int) $statistic->count, + 'currency_id' => $currency->id, + 'currency_name' => $currency->name, + 'currency_code' => $currency->code, + 'currency_symbol' => $currency->symbol, + 'currency_decimal_places' => $currency->decimal_places, + ]; + $grouped['count'] += (int) $statistic->count; + } + return $grouped; + } + + protected function saveGroupedAsStatistics(Account $account, Carbon $start, Carbon $end, string $type, array $array): void + { + unset($array['count']); + foreach ($array as $entry) { + $this->periodStatisticRepo->saveStatistic($account, $entry['currency_id'], $start, $end, $type, $entry['count'], $entry['amount']); + } + } + private function filterTransactionsByType(TransactionTypeEnum $type, array $transactions, Carbon $start, Carbon $end): array { - $result = []; - $filtered = []; + $result = []; /** - * @var int $index + * @var int $index * @var array $item */ foreach ($transactions as $index => $item) { @@ -153,25 +192,21 @@ trait PeriodOverview $result[] = $item; unset($transactions[$index]); } - if (!$fits) { - $filtered[] = $item; - } } - return [$filtered, $result]; + return $result; } private function filterTransfers(string $direction, array $transactions, Carbon $start, Carbon $end): array { - $result = []; - $filtered = []; + $result = []; /** - * @var int $index + * @var int $index * @var array $item */ foreach ($transactions as $index => $item) { - $date = Carbon::parse($item['date']); + $date = Carbon::parse($item['date']); if ($date >= $start && $date <= $end) { if ('away' === $direction && -1 === bccomp((string)$item['amount'], '0')) { $result[] = $item; @@ -184,25 +219,34 @@ trait PeriodOverview continue; } } - $filtered[] = $item; } - return [$filtered, $result]; + return $result; } private function groupByCurrency(array $journals): array { - $return = []; + Log::debug('groupByCurrency()'); + $return = [ + 'count' => 0, + ]; /** @var array $journal */ foreach ($journals as $journal) { - $currencyId = (int)$journal['currency_id']; - $currencyCode = $journal['currency_code']; - $currencyName = $journal['currency_name']; - $currencySymbol = $journal['currency_symbol']; - $currencyDecimalPlaces = $journal['currency_decimal_places']; - $foreignCurrencyId = $journal['foreign_currency_id']; - $amount = $journal['amount'] ?? '0'; + + if (!array_key_exists('currency_id', $journal)) { + Log::debug('very strange!'); + var_dump($journals); + exit; + } + + $currencyId = (int)$journal['currency_id']; + $currencyCode = $journal['currency_code']; + $currencyName = $journal['currency_name']; + $currencySymbol = $journal['currency_symbol']; + $currencyDecimalPlaces = $journal['currency_decimal_places']; + $foreignCurrencyId = $journal['foreign_currency_id']; + $amount = $journal['amount'] ?? '0'; if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId !== $this->primaryCurrency->id) { $amount = $journal['pc_amount'] ?? '0'; @@ -233,6 +277,7 @@ trait PeriodOverview $return[$currencyId]['amount'] = bcadd($return[$currencyId]['amount'], $amount); ++$return[$currencyId]['count']; + ++$return['count']; } return $return; @@ -245,11 +290,11 @@ trait PeriodOverview */ protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array { - $range = Navigation::getViewRange(true); + $range = Navigation::getViewRange(true); [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; // properties for entries with their amounts. - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty($range); @@ -261,32 +306,32 @@ trait PeriodOverview } /** @var array $dates */ - $dates = Navigation::blockPeriods($start, $end, $range); - $entries = []; + $dates = Navigation::blockPeriods($start, $end, $range); + $entries = []; // collect all expenses in this period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->setCategory($category); $collector->setRange($start, $end); $collector->setTypes([TransactionTypeEnum::DEPOSIT->value]); - $earnedSet = $collector->getExtractedJournals(); + $earnedSet = $collector->getExtractedJournals(); // collect all income in this period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->setCategory($category); $collector->setRange($start, $end); $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); - $spentSet = $collector->getExtractedJournals(); + $spentSet = $collector->getExtractedJournals(); // collect all transfers in this period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->setCategory($category); $collector->setRange($start, $end); $collector->setTypes([TransactionTypeEnum::TRANSFER->value]); - $transferSet = $collector->getExtractedJournals(); + $transferSet = $collector->getExtractedJournals(); foreach ($dates as $currentDate) { $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']); $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']); @@ -294,17 +339,17 @@ trait PeriodOverview $title = Navigation::periodShow($currentDate['end'], $currentDate['period']); $entries[] = [ - 'transactions' => 0, - 'title' => $title, - 'route' => route( - 'categories.show', - [$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')] - ), - 'total_transactions' => count($spent) + count($earned) + count($transferred), - 'spent' => $this->groupByCurrency($spent), - 'earned' => $this->groupByCurrency($earned), - 'transferred' => $this->groupByCurrency($transferred), - ]; + 'transactions' => 0, + 'title' => $title, + 'route' => route( + 'categories.show', + [$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')] + ), + 'total_transactions' => count($spent) + count($earned) + count($transferred), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred' => $this->groupByCurrency($transferred), + ]; } $cache->store($entries); @@ -337,11 +382,11 @@ trait PeriodOverview */ protected function getNoBudgetPeriodOverview(Carbon $start, Carbon $end): array { - $range = Navigation::getViewRange(true); + $range = Navigation::getViewRange(true); [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty($this->convertToPrimary); @@ -352,28 +397,28 @@ trait PeriodOverview } /** @var array $dates */ - $dates = Navigation::blockPeriods($start, $end, $range); - $entries = []; + $dates = Navigation::blockPeriods($start, $end, $range); + $entries = []; // get all expenses without a budget. /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->setRange($start, $end)->withoutBudget()->withAccountInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); - $journals = $collector->getExtractedJournals(); + $journals = $collector->getExtractedJournals(); foreach ($dates as $currentDate) { $set = $this->filterJournalsByDate($journals, $currentDate['start'], $currentDate['end']); $title = Navigation::periodShow($currentDate['end'], $currentDate['period']); $entries[] = [ - 'title' => $title, - 'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), - 'total_transactions' => count($set), - 'spent' => $this->groupByCurrency($set), - 'earned' => [], - 'transferred_away' => [], - 'transferred_in' => [], - ]; + 'title' => $title, + 'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($set), + 'spent' => $this->groupByCurrency($set), + 'earned' => [], + 'transferred_away' => [], + 'transferred_in' => [], + ]; } $cache->store($entries); @@ -390,38 +435,38 @@ trait PeriodOverview protected function getNoCategoryPeriodOverview(Carbon $theDate): array { app('log')->debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d'))); - $range = Navigation::getViewRange(true); - $first = $this->journalRepos->firstNull(); - $start = null === $first ? new Carbon() : $first->date; - $end = clone $theDate; - $end = Navigation::endOfPeriod($end, $range); + $range = Navigation::getViewRange(true); + $first = $this->journalRepos->firstNull(); + $start = null === $first ? new Carbon() : $first->date; + $end = clone $theDate; + $end = Navigation::endOfPeriod($end, $range); app('log')->debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d'))); app('log')->debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d'))); // properties for cache - $dates = Navigation::blockPeriods($start, $end, $range); - $entries = []; + $dates = Navigation::blockPeriods($start, $end, $range); + $entries = []; // collect all expenses in this period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->withoutCategory(); $collector->setRange($start, $end); $collector->setTypes([TransactionTypeEnum::DEPOSIT->value]); - $earnedSet = $collector->getExtractedJournals(); + $earnedSet = $collector->getExtractedJournals(); // collect all income in this period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->withoutCategory(); $collector->setRange($start, $end); $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); - $spentSet = $collector->getExtractedJournals(); + $spentSet = $collector->getExtractedJournals(); // collect all transfers in this period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->withoutCategory(); $collector->setRange($start, $end); $collector->setTypes([TransactionTypeEnum::TRANSFER->value]); @@ -435,13 +480,13 @@ trait PeriodOverview $title = Navigation::periodShow($currentDate['end'], $currentDate['period']); $entries[] = [ - 'title' => $title, - 'route' => route('categories.no-category', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), - 'total_transactions' => count($spent) + count($earned) + count($transferred), - 'spent' => $this->groupByCurrency($spent), - 'earned' => $this->groupByCurrency($earned), - 'transferred' => $this->groupByCurrency($transferred), - ]; + 'title' => $title, + 'route' => route('categories.no-category', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($spent) + count($earned) + count($transferred), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred' => $this->groupByCurrency($transferred), + ]; } app('log')->debug('End of loops'); @@ -455,11 +500,11 @@ trait PeriodOverview */ protected function getTagPeriodOverview(Tag $tag, Carbon $start, Carbon $end): array // period overview for tags. { - $range = Navigation::getViewRange(true); + $range = Navigation::getViewRange(true); [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; // properties for cache - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty('tag-period-entries'); @@ -469,37 +514,37 @@ trait PeriodOverview } /** @var array $dates */ - $dates = Navigation::blockPeriods($start, $end, $range); - $entries = []; + $dates = Navigation::blockPeriods($start, $end, $range); + $entries = []; // collect all expenses in this period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->setTag($tag); $collector->setRange($start, $end); $collector->setTypes([TransactionTypeEnum::DEPOSIT->value]); - $earnedSet = $collector->getExtractedJournals(); + $earnedSet = $collector->getExtractedJournals(); // collect all income in this period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->setTag($tag); $collector->setRange($start, $end); $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); - $spentSet = $collector->getExtractedJournals(); + $spentSet = $collector->getExtractedJournals(); // collect all transfers in this period: /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->setTag($tag); $collector->setRange($start, $end); $collector->setTypes([TransactionTypeEnum::TRANSFER->value]); - $transferSet = $collector->getExtractedJournals(); + $transferSet = $collector->getExtractedJournals(); // filer all of them: - $earnedSet = $this->filterJournalsByTag($earnedSet, $tag); - $spentSet = $this->filterJournalsByTag($spentSet, $tag); - $transferSet = $this->filterJournalsByTag($transferSet, $tag); + $earnedSet = $this->filterJournalsByTag($earnedSet, $tag); + $spentSet = $this->filterJournalsByTag($spentSet, $tag); + $transferSet = $this->filterJournalsByTag($transferSet, $tag); foreach ($dates as $currentDate) { $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']); @@ -508,17 +553,17 @@ trait PeriodOverview $title = Navigation::periodShow($currentDate['end'], $currentDate['period']); $entries[] = [ - 'transactions' => 0, - 'title' => $title, - 'route' => route( - 'tags.show', - [$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')] - ), - 'total_transactions' => count($spent) + count($earned) + count($transferred), - 'spent' => $this->groupByCurrency($spent), - 'earned' => $this->groupByCurrency($earned), - 'transferred' => $this->groupByCurrency($transferred), - ]; + 'transactions' => 0, + 'title' => $title, + 'route' => route( + 'tags.show', + [$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')] + ), + 'total_transactions' => count($spent) + count($earned) + count($transferred), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred' => $this->groupByCurrency($transferred), + ]; } return $entries; @@ -528,7 +573,7 @@ trait PeriodOverview { $return = []; foreach ($set as $entry) { - $found = false; + $found = false; /** @var array $localTag */ foreach ($entry['tags'] as $localTag) { @@ -550,12 +595,12 @@ trait PeriodOverview */ protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array { - $range = Navigation::getViewRange(true); - $types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType)); + $range = Navigation::getViewRange(true); + $types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType)); [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; // properties for cache - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty('transactions-period-entries'); @@ -565,16 +610,16 @@ trait PeriodOverview } /** @var array $dates */ - $dates = Navigation::blockPeriods($start, $end, $range); - $entries = []; - $spent = []; - $earned = []; - $transferred = []; + $dates = Navigation::blockPeriods($start, $end, $range); + $entries = []; + $spent = []; + $earned = []; + $transferred = []; // collect all journals in this period (regardless of type) - $collector = app(GroupCollectorInterface::class); + $collector = app(GroupCollectorInterface::class); $collector->setTypes($types)->setRange($start, $end); - $genericSet = $collector->getExtractedJournals(); - $loops = 0; + $genericSet = $collector->getExtractedJournals(); + $loops = 0; foreach ($dates as $currentDate) { $title = Navigation::periodShow($currentDate['end'], $currentDate['period']); @@ -592,14 +637,14 @@ trait PeriodOverview } } $entries[] - = [ - 'title' => $title, - 'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), - 'total_transactions' => count($spent) + count($earned) + count($transferred), - 'spent' => $this->groupByCurrency($spent), - 'earned' => $this->groupByCurrency($earned), - 'transferred' => $this->groupByCurrency($transferred), - ]; + = [ + 'title' => $title, + 'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($spent) + count($earned) + count($transferred), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred' => $this->groupByCurrency($transferred), + ]; ++$loops; } diff --git a/database/migrations/2025_09_25_175248_create_period_statistics.php b/database/migrations/2025_09_25_175248_create_period_statistics.php new file mode 100644 index 0000000000..e04afaa9de --- /dev/null +++ b/database/migrations/2025_09_25_175248_create_period_statistics.php @@ -0,0 +1,47 @@ +id(); + $table->timestamps(); + $table->softDeletes(); + $table->integer('primary_statable_id', false, true)->nullable(); + $table->string('primary_statable_type', 255)->nullable(); + + $table->integer('secondary_statable_id', false, true)->nullable(); + $table->string('secondary_statable_type', 255)->nullable(); + + $table->integer('tertiary_statable_id', false, true)->nullable(); + $table->string('tertiary_statable_type', 255)->nullable(); + + $table->integer('transaction_currency_id', false, true); + $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); + + $table->dateTime('start')->nullable(); + $table->string('start_tz', 50)->nullable(); + $table->dateTime('end')->nullable(); + $table->string('end_tz', 50)->nullable(); + $table->string('type',255); + $table->integer('count', false, true)->default(0); + $table->decimal('amount', 32, 12); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('period_statistics'); + } +};