From 286a29ca3e38d94e3a6b514ce96b499453a7f9b3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 26 Dec 2024 11:28:31 +0100 Subject: [PATCH] Display for bills. --- app/Http/Controllers/Chart/BillController.php | 34 ++-- app/Models/Transaction.php | 2 + app/Repositories/Bill/BillRepository.php | 168 +++++++++--------- resources/views/bills/show.twig | 17 +- resources/views/list/bills.twig | 2 +- 5 files changed, 127 insertions(+), 96 deletions(-) diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php index bb05755d05..e18b9a3678 100644 --- a/app/Http/Controllers/Chart/BillController.php +++ b/app/Http/Controllers/Chart/BillController.php @@ -109,8 +109,9 @@ class BillController extends Controller $cache = new CacheProperties(); $cache->addProperty('chart.bill.single'); $cache->addProperty($bill->id); + $cache->addProperty($this->convertToNative); if ($cache->has()) { - return response()->json($cache->get()); + // return response()->json($cache->get()); } $locale = app('steam')->getLocale(); @@ -132,42 +133,55 @@ class BillController extends Controller return 0; } ); + $currency = $bill->transactionCurrency; + if($this->convertToNative) { + $currency = $this->defaultCurrency; + } $chartData = [ [ 'type' => 'line', 'label' => (string) trans('firefly.min-amount'), - 'currency_symbol' => $bill->transactionCurrency->symbol, - 'currency_code' => $bill->transactionCurrency->code, + 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, 'entries' => [], ], [ 'type' => 'line', 'label' => (string) trans('firefly.max-amount'), - 'currency_symbol' => $bill->transactionCurrency->symbol, - 'currency_code' => $bill->transactionCurrency->code, + 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, 'entries' => [], ], [ 'type' => 'bar', 'label' => (string) trans('firefly.journal-amount'), - 'currency_symbol' => $bill->transactionCurrency->symbol, - 'currency_code' => $bill->transactionCurrency->code, + 'currency_symbol' => $currency->symbol, + 'currency_code' => $currency->code, 'entries' => [], ], ]; $currencyId = $bill->transaction_currency_id; + $amountMin = $bill->amount_min; + $amountMax = $bill->amount_max; + if($this->convertToNative) { + $amountMin = $bill->native_amount_min; + $amountMax = $bill->native_amount_max; + } foreach ($journals as $journal) { $date = $journal['date']->isoFormat((string) trans('config.month_and_day_js', [], $locale)); - $chartData[0]['entries'][$date] = $bill->amount_min; // minimum amount of bill - $chartData[1]['entries'][$date] = $bill->amount_max; // maximum amount of bill + $chartData[0]['entries'][$date] = $amountMin; // minimum amount of bill + $chartData[1]['entries'][$date] = $amountMax; // maximum amount of bill // append amount because there are more than one per moment: if (!array_key_exists($date, $chartData[2]['entries'])) { $chartData[2]['entries'][$date] = '0'; } $amount = bcmul($journal['amount'], '-1'); - if ($currencyId === $journal['foreign_currency_id']) { + if($this->convertToNative) { + $amount = bcmul($journal['native_amount'], '-1'); + } + if($this->convertToNative && $currencyId === $journal['foreign_currency_id']) { $amount = bcmul($journal['foreign_amount'], '-1'); } diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 85e4a70b8c..c6ee4cbcd9 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -59,6 +59,8 @@ class Transaction extends Model 'date' => 'datetime', 'amount' => 'string', 'foreign_amount' => 'string', + 'native_amount' => 'string', + 'native_foreign_amount' => 'string', ]; protected $fillable diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index ee74192f52..f0a5d623e1 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -61,8 +61,7 @@ class BillRepository implements BillRepositoryInterface $search->whereLike('name', sprintf('%%%s', $query)); } $search->orderBy('name', 'ASC') - ->where('active', true) - ; + ->where('active', true); return $search->take($limit)->get(); } @@ -74,8 +73,7 @@ class BillRepository implements BillRepositoryInterface $search->whereLike('name', sprintf('%s%%', $query)); } $search->orderBy('name', 'ASC') - ->where('active', true) - ; + ->where('active', true); return $search->take($limit)->get(); } @@ -158,7 +156,7 @@ class BillRepository implements BillRepositoryInterface */ public function getAttachments(Bill $bill): Collection { - $set = $bill->attachments()->get(); + $set = $bill->attachments()->get(); /** @var \Storage $disk */ $disk = \Storage::disk('upload'); @@ -177,10 +175,9 @@ class BillRepository implements BillRepositoryInterface public function getBills(): Collection { return $this->user->bills() - ->orderBy('order', 'ASC') - ->orderBy('active', 'DESC') - ->orderBy('name', 'ASC')->get() - ; + ->orderBy('order', 'ASC') + ->orderBy('active', 'DESC') + ->orderBy('name', 'ASC')->get(); } public function getBillsForAccounts(Collection $accounts): Collection @@ -204,25 +201,24 @@ class BillRepository implements BillRepositoryInterface $ids = $accounts->pluck('id')->toArray(); return $this->user->bills() - ->leftJoin( - 'transaction_journals', - static function (JoinClause $join): void { - $join->on('transaction_journals.bill_id', '=', 'bills.id')->whereNull('transaction_journals.deleted_at'); - } - ) - ->leftJoin( - 'transactions', - static function (JoinClause $join): void { - $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); - } - ) - ->whereIn('transactions.account_id', $ids) - ->whereNull('transaction_journals.deleted_at') - ->orderBy('bills.active', 'DESC') - ->orderBy('bills.name', 'ASC') - ->groupBy($fields) - ->get($fields) - ; + ->leftJoin( + 'transaction_journals', + static function (JoinClause $join): void { + $join->on('transaction_journals.bill_id', '=', 'bills.id')->whereNull('transaction_journals.deleted_at'); + } + ) + ->leftJoin( + 'transactions', + static function (JoinClause $join): void { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); + } + ) + ->whereIn('transactions.account_id', $ids) + ->whereNull('transaction_journals.deleted_at') + ->orderBy('bills.active', 'DESC') + ->orderBy('bills.name', 'ASC') + ->groupBy($fields) + ->get($fields); } /** @@ -247,7 +243,7 @@ class BillRepository implements BillRepositoryInterface public function getOverallAverage(Bill $bill): array { /** @var JournalRepositoryInterface $repos */ - $repos = app(JournalRepositoryInterface::class); + $repos = app(JournalRepositoryInterface::class); $repos->setUser($this->user); // get and sort on currency @@ -260,16 +256,22 @@ class BillRepository implements BillRepositoryInterface $transaction = $journal->transactions()->where('amount', '<', 0)->first(); $currencyId = (int) $journal->transaction_currency_id; $currency = $journal->transactionCurrency; - $result[$currencyId] ??= [ + $result[$currencyId] ??= [ 'sum' => '0', + 'native_sum' => '0', 'count' => 0, 'avg' => '0', + 'native_avg' => '0', 'currency_id' => $currency->id, 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, ]; $result[$currencyId]['sum'] = bcadd($result[$currencyId]['sum'], $transaction->amount); + $result[$currencyId]['native_sum'] = bcadd($result[$currencyId]['native_sum'], $transaction->native_amount); + if ($journal->foreign_currency_id === Amount::getDefaultCurrency()->id) { + $result[$currencyId]['native_sum'] = bcadd($result[$currencyId]['native_sum'], $transaction->amount); + } ++$result[$currencyId]['count']; } @@ -280,12 +282,13 @@ class BillRepository implements BillRepositoryInterface */ foreach ($result as $currencyId => $arr) { $result[$currencyId]['avg'] = bcdiv($arr['sum'], (string) $arr['count']); + $result[$currencyId]['native_avg'] = bcdiv($arr['native_sum'], (string) $arr['count']); } return $result; } - public function setUser(null|Authenticatable|User $user): void + public function setUser(null | Authenticatable | User $user): void { if ($user instanceof User) { $this->user = $user; @@ -295,9 +298,8 @@ class BillRepository implements BillRepositoryInterface public function getPaginator(int $size): LengthAwarePaginator { return $this->user->bills() - ->orderBy('active', 'DESC') - ->orderBy('name', 'ASC')->paginate($size) - ; + ->orderBy('active', 'DESC') + ->orderBy('name', 'ASC')->paginate($size); } /** @@ -310,14 +312,13 @@ class BillRepository implements BillRepositoryInterface Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString())); return $bill->transactionJournals() - ->before($end)->after($start)->get( + ->before($end)->after($start)->get( [ 'transaction_journals.id', 'transaction_journals.date', 'transaction_journals.transaction_group_id', ] - ) - ; + ); } /** @@ -326,11 +327,10 @@ class BillRepository implements BillRepositoryInterface public function getRulesForBill(Bill $bill): Collection { return $this->user->rules() - ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id') - ->where('rule_actions.action_type', 'link_to_bill') - ->where('rule_actions.action_value', $bill->name) - ->get(['rules.*']) - ; + ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id') + ->where('rule_actions.action_type', 'link_to_bill') + ->where('rule_actions.action_value', $bill->name) + ->get(['rules.*']); } /** @@ -341,16 +341,15 @@ class BillRepository implements BillRepositoryInterface */ public function getRulesForBills(Collection $collection): array { - $rules = $this->user->rules() - ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id') - ->where('rule_actions.action_type', 'link_to_bill') - ->get(['rules.id', 'rules.title', 'rule_actions.action_value', 'rules.active']) - ; - $array = []; + $rules = $this->user->rules() + ->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id') + ->where('rule_actions.action_type', 'link_to_bill') + ->get(['rules.id', 'rules.title', 'rule_actions.action_value', 'rules.active']); + $array = []; /** @var Rule $rule */ foreach ($rules as $rule) { - $array[$rule->action_value] ??= []; + $array[$rule->action_value] ??= []; $array[$rule->action_value][] = ['id' => $rule->id, 'title' => $rule->title, 'active' => $rule->active]; } $return = []; @@ -364,29 +363,29 @@ class BillRepository implements BillRepositoryInterface public function getYearAverage(Bill $bill, Carbon $date): array { /** @var JournalRepositoryInterface $repos */ - $repos = app(JournalRepositoryInterface::class); + $repos = app(JournalRepositoryInterface::class); $repos->setUser($this->user); // get and sort on currency - $result = []; + $result = []; $journals = $bill->transactionJournals() - ->where('date', '>=', $date->year.'-01-01 00:00:00') - ->where('date', '<=', $date->year.'-12-31 23:59:59') - ->get() - ; + ->where('date', '>=', $date->year . '-01-01 00:00:00') + ->where('date', '<=', $date->year . '-12-31 23:59:59') + ->get(); /** @var TransactionJournal $journal */ foreach ($journals as $journal) { /** @var null|Transaction $transaction */ - $transaction = $journal->transactions()->where('amount', '<', 0)->first(); + $transaction = $journal->transactions()->where('amount', '<', 0)->first(); if (null === $transaction) { continue; } - $currencyId = (int) $journal->transaction_currency_id; - $currency = $journal->transactionCurrency; - $result[$currencyId] ??= [ + $currencyId = (int) $journal->transaction_currency_id; + $currency = $journal->transactionCurrency; + $result[$currencyId] ??= [ 'sum' => '0', + 'native_sum' => '0', 'count' => 0, 'avg' => '0', 'currency_id' => $currency->id, @@ -394,7 +393,11 @@ class BillRepository implements BillRepositoryInterface 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, ]; - $result[$currencyId]['sum'] = bcadd($result[$currencyId]['sum'], $transaction->amount); + $result[$currencyId]['sum'] = bcadd($result[$currencyId]['sum'], $transaction->amount); + $result[$currencyId]['native_sum'] = bcadd($result[$currencyId]['native_sum'], $transaction->native_amount); + if ($journal->foreign_currency_id === Amount::getDefaultCurrency()->id) { + $result[$currencyId]['native_sum'] = bcadd($result[$currencyId]['native_sum'], $transaction->amount); + } ++$result[$currencyId]['count']; } @@ -404,7 +407,8 @@ class BillRepository implements BillRepositoryInterface * @var array $arr */ foreach ($result as $currencyId => $arr) { - $result[$currencyId]['avg'] = bcdiv($arr['sum'], (string) $arr['count']); + $result[$currencyId]['avg'] = bcdiv($arr['sum'], (string) $arr['count']); + $result[$currencyId]['native_avg'] = bcdiv($arr['native_sum'], (string) $arr['count']); } return $result; @@ -429,7 +433,7 @@ class BillRepository implements BillRepositoryInterface */ public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon { - $cache = new CacheProperties(); + $cache = new CacheProperties(); $cache->addProperty($bill->id); $cache->addProperty('nextExpectedMatch'); $cache->addProperty($date); @@ -437,17 +441,17 @@ class BillRepository implements BillRepositoryInterface return $cache->get(); } // find the most recent date for this bill NOT in the future. Cache this date: - $start = clone $bill->date; + $start = clone $bill->date; $start->startOfDay(); - app('log')->debug('nextExpectedMatch: Start is '.$start->format('Y-m-d')); + app('log')->debug('nextExpectedMatch: Start is ' . $start->format('Y-m-d')); while ($start < $date) { app('log')->debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d H:i:s'), $date->format('Y-m-d H:i:s'))); $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); - app('log')->debug('Start is now '.$start->format('Y-m-d H:i:s')); + app('log')->debug('Start is now ' . $start->format('Y-m-d H:i:s')); } - $end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); + $end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); $end->endOfDay(); // see if the bill was paid in this period. @@ -459,8 +463,8 @@ class BillRepository implements BillRepositoryInterface $start = clone $end; $end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); } - app('log')->debug('nextExpectedMatch: Final start is '.$start->format('Y-m-d')); - app('log')->debug('nextExpectedMatch: Matching end is '.$end->format('Y-m-d')); + app('log')->debug('nextExpectedMatch: Final start is ' . $start->format('Y-m-d')); + app('log')->debug('nextExpectedMatch: Matching end is ' . $end->format('Y-m-d')); $cache->store($start); @@ -521,8 +525,8 @@ class BillRepository implements BillRepositoryInterface foreach ($bills as $bill) { /** @var Collection $set */ - $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']); - $currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency; + $set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']); + $currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency; $return[(int) $currency->id] ??= [ 'id' => (string) $currency->id, 'name' => $currency->name, @@ -531,7 +535,7 @@ class BillRepository implements BillRepositoryInterface 'decimal_places' => $currency->decimal_places, 'sum' => '0', ]; - $setAmount = '0'; + $setAmount = '0'; /** @var TransactionJournal $transactionJournal */ foreach ($set as $transactionJournal) { @@ -548,10 +552,10 @@ class BillRepository implements BillRepositoryInterface public function getActiveBills(): Collection { return $this->user->bills() - ->where('active', true) - ->orderBy('bills.name', 'ASC') - ->get(['bills.*', \DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount')]) // @phpstan-ignore-line - ; + ->where('active', true) + ->orderBy('bills.name', 'ASC') + ->get(['bills.*', \DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount')]) // @phpstan-ignore-line + ; } public function sumUnpaidInRange(Carbon $start, Carbon $end): array @@ -565,9 +569,9 @@ class BillRepository implements BillRepositoryInterface /** @var Bill $bill */ foreach ($bills as $bill) { // app('log')->debug(sprintf('Processing bill #%d ("%s")', $bill->id, $bill->name)); - $dates = $this->getPayDatesInRange($bill, $start, $end); - $count = $bill->transactionJournals()->after($start)->before($end)->count(); - $total = $dates->count() - $count; + $dates = $this->getPayDatesInRange($bill, $start, $end); + $count = $bill->transactionJournals()->after($start)->before($end)->count(); + $total = $dates->count() - $count; // app('log')->debug(sprintf('Pay dates: %d, count: %d, left: %d', $dates->count(), $count, $total)); // app('log')->debug('dates', $dates->toArray()); @@ -576,10 +580,10 @@ class BillRepository implements BillRepositoryInterface // Log::debug(sprintf('min field is %s, max field is %s', $minField, $maxField)); if ($total > 0) { - $currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency; - $average = bcdiv(bcadd($bill->{$maxField}, $bill->{$minField}), '2'); + $currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency; + $average = bcdiv(bcadd($bill->{$maxField}, $bill->{$minField}), '2'); Log::debug(sprintf('Amount to pay is %s %s (%d times)', $currency->code, $average, $total)); - $return[$currency->id] ??= [ + $return[$currency->id] ??= [ 'id' => (string) $currency->id, 'name' => $currency->name, 'symbol' => $currency->symbol, @@ -617,7 +621,7 @@ class BillRepository implements BillRepositoryInterface // app('log')->debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d'))); - $currentStart = clone $nextExpectedMatch; + $currentStart = clone $nextExpectedMatch; } return $set; diff --git a/resources/views/bills/show.twig b/resources/views/bills/show.twig index 3df9b2cfc5..219d03e81d 100644 --- a/resources/views/bills/show.twig +++ b/resources/views/bills/show.twig @@ -66,7 +66,13 @@ {{ trans('firefly.average_bill_amount_year', {year: year}) }} {% for avg in yearAverage %} - {{ formatAmountBySymbol(avg.avg, avg.currency_symbol, avg.currency_decimal_places, true) }}
+ {{ formatAmountBySymbol(avg.avg, avg.currency_symbol, avg.currency_decimal_places, true) }} + {% if convertToNative %} + ({{ formatAmountBySymbol(avg.native_avg, + defaultCurrency.symbol, defaultCurrency.decimal_places, true) }}) + {% endif %} +
+ {% endfor %} @@ -74,7 +80,12 @@ {{ 'average_bill_amount_overall'|_ }} {% for avg in overallAverage %} - {{ formatAmountBySymbol(avg.avg, avg.currency_symbol, avg.currency_decimal_places, true) }}
+ {{ formatAmountBySymbol(avg.avg, avg.currency_symbol, avg.currency_decimal_places, true) }} + {% if convertToNative %} + ({{ formatAmountBySymbol(avg.native_avg, + defaultCurrency.symbol, defaultCurrency.decimal_places, true) }}) + {% endif %} +
{% endfor %} @@ -174,7 +185,7 @@ {% block scripts %} diff --git a/resources/views/list/bills.twig b/resources/views/list/bills.twig index 0dc7719635..fc92333ab6 100644 --- a/resources/views/list/bills.twig +++ b/resources/views/list/bills.twig @@ -69,7 +69,7 @@ ~ {{ formatAmountBySymbol((entry.amount_max + entry.amount_min)/2, entry.currency_symbol, entry.currency_decimal_places) }} {% if '0' != entry.native_amount_max %} - ({{ formatAmountBySymbol((entry.native_amount_max + entry.native_amount_min)/2, defaultCurrency.symbol, defaultCurrency.decimal_places) }}) + (~ {{ formatAmountBySymbol((entry.native_amount_max + entry.native_amount_min)/2, defaultCurrency.symbol, defaultCurrency.decimal_places) }}) {% endif %}