Compare commits

..

13 Commits

Author SHA1 Message Date
James Cole
5451328ea6 Merge pull request #11835 from dakennguyen/convert-primary-currency-tag-report
Convert to primary currency for tag charts
2026-02-28 13:25:02 +01:00
Khoa Nguyen
3e5ef0b431 Convert to primary currency for tag charts 2026-02-28 13:19:58 +01:00
James Cole
87fb1fcc92 Merge pull request #11833 from dakennguyen/convert-primary-currency
Convert to primary currency for category charts
2026-02-28 12:36:13 +01:00
Khoa Nguyen
85da46243b Convert to primary currency for category charts 2026-02-28 11:04:53 +01:00
github-actions[bot]
c44711e9bd Merge pull request #11831 from firefly-iii/release-1772261367
🤖 Automatically merge the PR into the develop branch.
2026-02-28 07:49:34 +01:00
JC5
18161450e4 🤖 Auto commit for release 'develop' on 2026-02-28 2026-02-28 07:49:27 +01:00
James Cole
b48b2a411a Remove amount when nothing left. 2026-02-28 07:45:26 +01:00
github-actions[bot]
453332eae0 Merge pull request #11830 from firefly-iii/release-1772260516
🤖 Automatically merge the PR into the develop branch.
2026-02-28 07:35:25 +01:00
JC5
4407456167 🤖 Auto commit for release 'develop' on 2026-02-28 2026-02-28 07:35:16 +01:00
James Cole
2842432204 Clean up budget amounts. 2026-02-28 07:29:44 +01:00
James Cole
842ec6da47 Add new function to twig. 2026-02-28 07:20:22 +01:00
James Cole
ceb5873ba7 Do not break arrays 2026-02-28 07:17:53 +01:00
James Cole
6cfd8273fe Fix middleware for admin area. 2026-02-28 06:15:28 +01:00
18 changed files with 476 additions and 405 deletions

View File

@@ -25,7 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\Account;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Generic\PaginationDateRangeRequest;
use FireflyIII\Api\V1\Requests\Models\Transaction\ListRequest;
use FireflyIII\Api\V1\Requests\PaginationRequest;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Account;
@@ -126,17 +126,23 @@ class ListController extends Controller
/**
* Show all transaction groups related to the account.
*/
public function transactions(PaginationDateRangeRequest $request, Account $account): JsonResponse
public function transactions(ListRequest $request, Account $account): JsonResponse
{
['limit' => $limit, 'page' => $page, 'start' => $start, 'end' => $end, 'types' => $types] = $request->attributes->all();
$manager = $this->getManager();
[
'limit' => $limit,
'page' => $page,
'start' => $start,
'end' => $end,
'types' => $types,
] = $request->attributes->all();
$manager = $this->getManager();
/** @var User $admin */
$admin = auth()->user();
$admin = auth()->user();
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($admin)->setAccounts(new Collection()->push($account))->withAPIInformation()->setLimit($limit)->setPage($page)->setTypes($types);
if (null !== $start) {
$collector->setStart($start);
@@ -145,18 +151,18 @@ class ListController extends Controller
$collector->setEnd($end);
}
$paginator = $collector->getPaginatedGroups();
$paginator = $collector->getPaginatedGroups();
$paginator->setPath(route('api.v1.accounts.transactions', [$account->id]).$this->buildParams());
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$transactions = $enrichment->enrich($paginator->getCollection());
$transactions = $enrichment->enrich($paginator->getCollection());
/** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class);
$transformer = app(TransactionGroupTransformer::class);
$resource = new FractalCollection($transactions, $transformer, 'transactions');
$resource = new FractalCollection($transactions, $transformer, 'transactions');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);

View File

@@ -46,6 +46,9 @@ use Illuminate\Routing\Redirector;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Spatie\Period\Boundaries;
use Spatie\Period\Period;
use Spatie\Period\Precision;
/**
* Class BudgetLimitController
@@ -268,10 +271,16 @@ class BudgetLimitController extends Controller
$budgetLimit->transactionCurrency
);
$daysLeft = $this->activeDaysLeft($limit->start_date, $limit->end_date);
$limitPeriod = Period::make($limit->start_date, $limit->end_date, precision: Precision::DAY(), boundaries: Boundaries::EXCLUDE_NONE());
$inPast = $limitPeriod->startsBefore(now()) && $limitPeriod->endsBefore(now());
// create aray.
$array['spent'] = $spentArr[$budgetLimit->transactionCurrency->id]['sum'] ?? '0';
$array['left_formatted'] = Amount::formatAnything($limit->transactionCurrency, bcadd($array['spent'], (string) $array['amount']));
$array['amount_formatted'] = Amount::formatAnything($limit->transactionCurrency, $limit['amount']);
$array['days_left'] = (string) $daysLeft;
$array['in_past'] = $inPast;
$array['left_per_day'] = 0 === $daysLeft
? bcadd((string) $array['spent'], (string) $array['amount'])
: bcdiv(bcadd((string) $array['spent'], (string) $array['amount']), $array['days_left']);

View File

@@ -47,6 +47,9 @@ use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Spatie\Period\Boundaries;
use Spatie\Period\Period;
use Spatie\Period\Precision;
/**
* Class IndexController
@@ -117,6 +120,10 @@ final class IndexController extends Controller
$prevLoop = $this->getPreviousPeriods($start, $range);
$nextLoop = $this->getNextPeriods($start, $range);
// number of days for consistent budgeting.
$activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description.
$activeDaysLeft = $this->activeDaysLeft($start, $end); // see method description.
// get all available budgets:
$availableBudgets = $this->getAllAvailableBudgets($start, $end);
// get all active budgets:
@@ -133,9 +140,6 @@ final class IndexController extends Controller
$spent = $spentArr[$this->primaryCurrency->id]['sum'] ?? '0';
unset($spentArr);
}
// number of days for consistent budgeting.
$activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description.
$activeDaysLeft = $this->activeDaysLeft($start, $end); // see method description.
// get all inactive budgets, and simply list them:
$inactive = $this->repository->getInactiveBudgets();
@@ -221,44 +225,68 @@ final class IndexController extends Controller
// complement budget with budget limits in range, and expenses in currency X in range.
/** @var Budget $current */
foreach ($collection as $current) {
Log::debug(sprintf('Working on budget #%d ("%s")', $current->id, $current->name));
$array = $current->toArray();
foreach ($collection as $budget) {
Log::debug(sprintf('Working on budget #%d ("%s")', $budget->id, $budget->name));
$array = $budget->toArray();
$array['spent'] = [];
$array['spent_total'] = [];
$array['budgeted'] = [];
$array['attachments'] = $this->repository->getAttachments($current);
$array['auto_budget'] = $this->repository->getAutoBudget($current);
$budgetLimits = $this->blRepository->getBudgetLimits($current, $start, $end);
$array['attachments'] = $this->repository->getAttachments($budget);
$array['auto_budget'] = $this->repository->getAutoBudget($budget);
$budgetLimits = $this->blRepository->getBudgetLimits($budget, $start, $end);
$spentInLimits = [];
/** @var BudgetLimit $limit */
foreach ($budgetLimits as $limit) {
Log::debug(sprintf('Working on budget limit #%d', $limit->id));
$currency = $limit->transactionCurrency ?? $primaryCurrency;
$amount = Steam::bcround($limit->amount, $currency->decimal_places);
$array['budgeted'][] = [
// number of days for consistent budgeting.
$activeDaysPassed = $this->activeDaysPassed($limit->start_date, $limit->end_date); // see method description.
$activeDaysLeft = $this->activeDaysLeft($limit->start_date, $limit->end_date); // see method description.
$limitPeriod = Period::make($limit->start_date, $limit->end_date, precision: Precision::DAY(), boundaries: Boundaries::EXCLUDE_NONE());
$inPast = $limitPeriod->startsBefore(now()) && $limitPeriod->endsBefore(now());
$currency = $limit->transactionCurrency ?? $primaryCurrency;
$amount = Steam::bcround($limit->amount, $currency->decimal_places);
$spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
$spentAmount = $spent[$currency->id]['sum'] ?? '0';
$array['budgeted'][] = [
'id' => $limit->id,
'amount' => $amount,
'notes' => $this->blRepository->getNoteText($limit),
'start_date' => $limit->start_date->isoFormat($this->monthAndDayFormat),
'end_date' => $limit->end_date->isoFormat($this->monthAndDayFormat),
'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end),
'in_past' => $inPast,
'total_days' => $limit->start_date->diffInDays($limit->end_date) + 1,
'currency_id' => $currency->id,
'currency_symbol' => $currency->symbol,
'currency_name' => $currency->name,
'currency_decimal_places' => $currency->decimal_places,
'spent' => $spentAmount,
'left' => bcadd($amount, $spentAmount),
'active_days_passed' => $activeDaysPassed,
'active_days_left' => $activeDaysLeft,
];
$spentInLimits[$currency->id] = array_key_exists($currency->id, $spentInLimits)
? bcadd($spentInLimits[$currency->id], $spentAmount)
: $spentAmount;
Log::debug(sprintf('The amount budgeted for budget limit #%d is %s %s', $limit->id, $currency->code, $amount));
Log::debug(sprintf('spentInLimits[%s] is now %s', $currency->code, $spentInLimits[$currency->id]));
}
// #10463
Log::debug('Looping currencies');
/** @var TransactionCurrency $currency */
foreach ($currencies as $currency) {
$spentArr = $this->opsRepository->sumExpenses($start, $end, null, new Collection()->push($current), $currency);
$spentInLimits[$currency->id] = array_key_exists($currency->id, $spentInLimits) ? $spentInLimits[$currency->id] : '0';
$spentArr = $this->opsRepository->sumExpenses($start, $end, null, new Collection()->push($budget), $currency);
Log::debug(sprintf('Working on currency %s, spentInLimits is %s', $currency->code, $spentInLimits[$currency->id]));
if (array_key_exists($currency->id, $spentArr) && array_key_exists('sum', $spentArr[$currency->id])) {
$array['spent'][$currency->id]['spent'] = $spentArr[$currency->id]['sum'];
$array['spent'][$currency->id]['spent_outside'] = bcmul(
bcsub($spentInLimits[$currency->id], $spentArr[$currency->id]['sum']),
'-1'
);
$array['spent'][$currency->id]['currency_id'] = $currency->id;
$array['spent'][$currency->id]['currency_symbol'] = $currency->symbol;
$array['spent'][$currency->id]['currency_decimal_places'] = $currency->decimal_places;

View File

@@ -29,8 +29,8 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\OperationsRepositoryInterface;
use FireflyIII\Support\Facades\Navigation;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\AugumentData;
use FireflyIII\Support\Http\Controllers\ResolvesJournalAmountAndCurrency;
use FireflyIII\Support\Http\Controllers\TransactionCalculation;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
@@ -43,6 +43,7 @@ use Illuminate\Support\Collection;
class CategoryReportController extends Controller
{
use AugumentData;
use ResolvesJournalAmountAndCurrency;
use TransactionCalculation;
private GeneratorInterface $generator;
@@ -73,15 +74,15 @@ class CategoryReportController extends Controller
/** @var array $category */
foreach ($currency['categories'] as $category) {
foreach ($category['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['budget_name'] ?? trans('firefly.no_budget');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -100,15 +101,15 @@ class CategoryReportController extends Controller
foreach ($spent as $currency) {
/** @var array $category */
foreach ($currency['categories'] as $category) {
$title = sprintf('%s (%s)', $category['name'], $currency['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
];
foreach ($category['transaction_journals'] as $journal) {
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$title = sprintf('%s (%s)', $category['name'], $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -127,15 +128,15 @@ class CategoryReportController extends Controller
foreach ($earned as $currency) {
/** @var array $category */
foreach ($currency['categories'] as $category) {
$title = sprintf('%s (%s)', $category['name'], $currency['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
];
foreach ($category['transaction_journals'] as $journal) {
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$title = sprintf('%s (%s)', $category['name'], $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -155,15 +156,15 @@ class CategoryReportController extends Controller
/** @var array $category */
foreach ($currency['categories'] as $category) {
foreach ($category['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['destination_account_name'] ?? trans('firefly.empty');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -183,15 +184,15 @@ class CategoryReportController extends Controller
/** @var array $category */
foreach ($currency['categories'] as $category) {
foreach ($category['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['destination_account_name'] ?? trans('firefly.empty');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -210,26 +211,25 @@ class CategoryReportController extends Controller
// loop expenses.
foreach ($spent as $currency) {
// add things to chart Data for each currency:
$spentKey = sprintf('%d-spent', $currency['currency_id']);
$chartData[$spentKey] ??= [
'label' => sprintf(
'%s (%s)',
(string) trans('firefly.spent_in_specific_category', ['category' => $category->name]),
$currency['currency_name']
),
'type' => 'bar',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_id' => $currency['currency_id'],
'entries' => $this->makeEntries($start, $end),
];
foreach ($currency['categories'] as $currentCategory) {
foreach ($currentCategory['transaction_journals'] as $journal) {
$key = $journal['date']->isoFormat($format);
$amount = Steam::positive($journal['amount']);
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$spentKey = sprintf('%d-spent', $journalData['currency_id']);
$chartData[$spentKey] ??= [
'label' => sprintf(
'%s (%s)',
(string) trans('firefly.spent_in_specific_category', ['category' => $category->name]),
$journalData['currency_name']
),
'type' => 'bar',
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
'currency_id' => $journalData['currency_id'],
'entries' => $this->makeEntries($start, $end),
];
$key = $journal['date']->isoFormat($format);
$chartData[$spentKey]['entries'][$key] ??= '0';
$chartData[$spentKey]['entries'][$key] = bcadd($chartData[$spentKey]['entries'][$key], $amount);
$chartData[$spentKey]['entries'][$key] = bcadd($chartData[$spentKey]['entries'][$key], $journalData['amount']);
}
}
}
@@ -237,26 +237,25 @@ class CategoryReportController extends Controller
// loop income.
foreach ($earned as $currency) {
// add things to chart Data for each currency:
$spentKey = sprintf('%d-earned', $currency['currency_id']);
$chartData[$spentKey] ??= [
'label' => sprintf(
'%s (%s)',
(string) trans('firefly.earned_in_specific_category', ['category' => $category->name]),
$currency['currency_name']
),
'type' => 'bar',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_id' => $currency['currency_id'],
'entries' => $this->makeEntries($start, $end),
];
foreach ($currency['categories'] as $currentCategory) {
foreach ($currentCategory['transaction_journals'] as $journal) {
$key = $journal['date']->isoFormat($format);
$amount = Steam::positive($journal['amount']);
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$spentKey = sprintf('%d-earned', $journalData['currency_id']);
$chartData[$spentKey] ??= [
'label' => sprintf(
'%s (%s)',
(string) trans('firefly.earned_in_specific_category', ['category' => $category->name]),
$journalData['currency_name']
),
'type' => 'bar',
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
'currency_id' => $journalData['currency_id'],
'entries' => $this->makeEntries($start, $end),
];
$key = $journal['date']->isoFormat($format);
$chartData[$spentKey]['entries'][$key] ??= '0';
$chartData[$spentKey]['entries'][$key] = bcadd($chartData[$spentKey]['entries'][$key], $amount);
$chartData[$spentKey]['entries'][$key] = bcadd($chartData[$spentKey]['entries'][$key], $journalData['amount']);
}
}
}
@@ -276,15 +275,15 @@ class CategoryReportController extends Controller
/** @var array $category */
foreach ($currency['categories'] as $category) {
foreach ($category['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['source_account_name'] ?? trans('firefly.empty');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -304,15 +303,15 @@ class CategoryReportController extends Controller
/** @var array $category */
foreach ($currency['categories'] as $category) {
foreach ($category['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['source_account_name'] ?? trans('firefly.empty');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -341,4 +340,5 @@ class CategoryReportController extends Controller
return $return;
}
}

View File

@@ -36,6 +36,7 @@ use FireflyIII\Support\Facades\Navigation;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\BasicDataSupport;
use FireflyIII\Support\Http\Controllers\ChartGeneration;
use FireflyIII\Support\Http\Controllers\ResolvesJournalAmountAndCurrency;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -47,6 +48,7 @@ class ReportController extends Controller
{
use BasicDataSupport;
use ChartGeneration;
use ResolvesJournalAmountAndCurrency;
protected GeneratorInterface $generator;
@@ -176,22 +178,14 @@ class ReportController extends Controller
// loop. group by currency and by period.
/** @var array $journal */
foreach ($journals as $journal) {
$period = $journal['date']->format($format);
$currencyId = (int) $journal['currency_id'];
$currencySymbol = (string) $journal['currency_symbol'];
$currencyCode = (string) $journal['currency_code'];
$currencyName = (string) $journal['currency_name'];
$currencyDecimalPlaces = (int) $journal['currency_decimal_places'];
$amount = (string) $journal['amount'];
if ($this->convertToPrimary && null !== $this->primaryCurrency && $journal['currency_id'] !== $this->primaryCurrency->id) {
$currencyId = $this->primaryCurrency->id;
$currencySymbol = $this->primaryCurrency->symbol;
$currencyCode = $this->primaryCurrency->code;
$currencyName = $this->primaryCurrency->name;
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
$amount = $journal['foreign_currency_id'] === $this->primaryCurrency->id ? $journal['foreign_amount'] : $journal['pc_amount'];
}
$period = $journal['date']->format($format);
$journalData = $this->resolveJournalAmountAndCurrency($journal, $journal);
$currencyId = $journalData['currency_id'];
$currencySymbol = $journalData['currency_symbol'];
$currencyCode = $journalData['currency_code'];
$currencyName = $journalData['currency_name'];
$currencyDecimalPlaces = $journalData['currency_decimal_places'];
$amount = $journalData['amount'];
$data[$currencyId] ??= [
'currency_id' => $currencyId,
@@ -202,8 +196,7 @@ class ReportController extends Controller
];
$data[$currencyId][$period] ??= ['period' => $period, 'spent' => '0', 'earned' => '0'];
// in our outgoing?
$key = 'spent';
$amount = Steam::positive($amount);
$key = 'spent';
// deposit = incoming
// transfer or reconcile or opening balance, and these accounts are the destination.

View File

@@ -29,9 +29,8 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Tag\OperationsRepositoryInterface;
use FireflyIII\Support\Facades\Navigation;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Controllers\AugumentData;
use FireflyIII\Support\Http\Controllers\TransactionCalculation;
use FireflyIII\Support\Http\Controllers\ResolvesJournalAmountAndCurrency;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Collection;
@@ -41,13 +40,11 @@ use Illuminate\Support\Collection;
class TagReportController extends Controller
{
use AugumentData;
use TransactionCalculation;
use ResolvesJournalAmountAndCurrency;
/** @var GeneratorInterface Chart generation methods. */
protected $generator;
private GeneratorInterface $generator;
/** @var OperationsRepositoryInterface */
private $opsRepository;
private OperationsRepositoryInterface $opsRepository;
/**
* TagReportController constructor.
@@ -55,10 +52,8 @@ class TagReportController extends Controller
public function __construct()
{
parent::__construct();
// create chart generator:
$this->generator = app(GeneratorInterface::class);
$this->middleware(function ($request, $next) {
$this->generator = app(GeneratorInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
return $next($request);
@@ -75,15 +70,15 @@ class TagReportController extends Controller
/** @var array $tag */
foreach ($currency['tags'] as $tag) {
foreach ($tag['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['budget_name'] ?? trans('firefly.no_budget');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -103,15 +98,15 @@ class TagReportController extends Controller
/** @var array $tag */
foreach ($currency['tags'] as $tag) {
foreach ($tag['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['category_name'] ?? trans('firefly.no_category');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -131,15 +126,15 @@ class TagReportController extends Controller
/** @var array $tag */
foreach ($currency['tags'] as $tag) {
foreach ($tag['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['category_name'] ?? trans('firefly.no_category');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -159,15 +154,15 @@ class TagReportController extends Controller
/** @var array $tag */
foreach ($currency['tags'] as $tag) {
foreach ($tag['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['destination_account_name'] ?? trans('firefly.empty');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -187,15 +182,15 @@ class TagReportController extends Controller
/** @var array $tag */
foreach ($currency['tags'] as $tag) {
foreach ($tag['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['destination_account_name'] ?? trans('firefly.empty');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -218,22 +213,21 @@ class TagReportController extends Controller
// loop expenses.
foreach ($spent as $currency) {
// add things to chart Data for each currency:
$spentKey = sprintf('%d-spent', $currency['currency_id']);
$chartData[$spentKey] ??= [
'label' => sprintf('%s (%s)', (string) trans('firefly.spent_in_specific_tag', ['tag' => $tag->tag]), $currency['currency_name']),
'type' => 'bar',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_id' => $currency['currency_id'],
'entries' => $this->makeEntries($start, $end),
];
foreach ($currency['tags'] as $currentTag) {
foreach ($currentTag['transaction_journals'] as $journal) {
$key = $journal['date']->isoFormat($format);
$amount = Steam::positive($journal['amount']);
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$spentKey = sprintf('%d-spent', $journalData['currency_id']);
$chartData[$spentKey] ??= [
'label' => sprintf('%s (%s)', (string) trans('firefly.spent_in_specific_tag', ['tag' => $tag->tag]), $journalData['currency_name']),
'type' => 'bar',
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
'currency_id' => $journalData['currency_id'],
'entries' => $this->makeEntries($start, $end),
];
$key = $journal['date']->isoFormat($format);
$chartData[$spentKey]['entries'][$key] ??= '0';
$chartData[$spentKey]['entries'][$key] = bcadd($chartData[$spentKey]['entries'][$key], $amount);
$chartData[$spentKey]['entries'][$key] = bcadd($chartData[$spentKey]['entries'][$key], $journalData['amount']);
}
}
}
@@ -241,22 +235,21 @@ class TagReportController extends Controller
// loop income.
foreach ($earned as $currency) {
// add things to chart Data for each currency:
$spentKey = sprintf('%d-earned', $currency['currency_id']);
$chartData[$spentKey] ??= [
'label' => sprintf('%s (%s)', (string) trans('firefly.earned_in_specific_tag', ['tag' => $tag->tag]), $currency['currency_name']),
'type' => 'bar',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_id' => $currency['currency_id'],
'entries' => $this->makeEntries($start, $end),
];
foreach ($currency['tags'] as $currentTag) {
foreach ($currentTag['transaction_journals'] as $journal) {
$key = $journal['date']->isoFormat($format);
$amount = Steam::positive($journal['amount']);
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$spentKey = sprintf('%d-earned', $journalData['currency_id']);
$chartData[$spentKey] ??= [
'label' => sprintf('%s (%s)', (string) trans('firefly.earned_in_specific_tag', ['tag' => $tag->tag]), $journalData['currency_name']),
'type' => 'bar',
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
'currency_id' => $journalData['currency_id'],
'entries' => $this->makeEntries($start, $end),
];
$key = $journal['date']->isoFormat($format);
$chartData[$spentKey]['entries'][$key] ??= '0';
$chartData[$spentKey]['entries'][$key] = bcadd($chartData[$spentKey]['entries'][$key], $amount);
$chartData[$spentKey]['entries'][$key] = bcadd($chartData[$spentKey]['entries'][$key], $journalData['amount']);
}
}
}
@@ -276,15 +269,15 @@ class TagReportController extends Controller
/** @var array $tag */
foreach ($currency['tags'] as $tag) {
foreach ($tag['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['source_account_name'] ?? trans('firefly.empty');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -304,15 +297,15 @@ class TagReportController extends Controller
/** @var array $tag */
foreach ($currency['tags'] as $tag) {
foreach ($tag['transaction_journals'] as $journal) {
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$objectName = $journal['source_account_name'] ?? trans('firefly.empty');
$title = sprintf('%s (%s)', $objectName, $currency['currency_name']);
$title = sprintf('%s (%s)', $objectName, $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -331,15 +324,15 @@ class TagReportController extends Controller
foreach ($spent as $currency) {
/** @var array $tag */
foreach ($currency['tags'] as $tag) {
$title = sprintf('%s (%s)', $tag['name'], $currency['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
];
foreach ($tag['transaction_journals'] as $journal) {
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$title = sprintf('%s (%s)', $tag['name'], $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -357,15 +350,15 @@ class TagReportController extends Controller
foreach ($earned as $currency) {
/** @var array $tag */
foreach ($currency['tags'] as $tag) {
$title = sprintf('%s (%s)', $tag['name'], $currency['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
];
foreach ($tag['transaction_journals'] as $journal) {
$amount = Steam::positive($journal['amount']);
$result[$title]['amount'] = bcadd($result[$title]['amount'], $amount);
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currency);
$title = sprintf('%s (%s)', $tag['name'], $journalData['currency_name']);
$result[$title] ??= [
'amount' => '0',
'currency_symbol' => $journalData['currency_symbol'],
'currency_code' => $journalData['currency_code'],
];
$result[$title]['amount'] = bcadd($result[$title]['amount'], $journalData['amount']);
}
}
}
@@ -393,4 +386,5 @@ class TagReportController extends Controller
return $return;
}
}

View File

@@ -88,113 +88,7 @@ class ExportDataGenerator
private bool $exportTransactions = false;
private Carbon $start;
private User $user;
private UserGroup $userGroup; // @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private UserGroup $userGroup;
public function __construct()
{

View File

@@ -98,8 +98,10 @@ trait GetConfigurationData
$title = sprintf('%s - %s', $start->isoFormat($this->monthAndDayFormat), $end->isoFormat($this->monthAndDayFormat));
$isCustom = true === session('is_custom_range', false);
$today = today(config('app.timezone'));
$ranges = [// first range is the current range:
$title => [$start, $end]];
$ranges = [
// first range is the current range:
$title => [$start, $end],
];
Log::debug(sprintf('dateRange: the date range in the session is"%s" - "%s"', $start->format('Y-m-d'), $end->format('Y-m-d')));
// when current range is a custom range, add the current period as the next range.

View File

@@ -0,0 +1,68 @@
<?php
/**
* ResolvesJournalAmountAndCurrency.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Http\Controllers;
use FireflyIII\Support\Facades\Steam;
trait ResolvesJournalAmountAndCurrency
{
/**
* Normalize journal currency metadata and positive amount, honoring primary currency conversion.
*/
protected function resolveJournalAmountAndCurrency(array $journal, array $currency): array
{
$currencyId = (int) ($journal['currency_id'] ?? $currency['currency_id']);
$currencyName = (string) ($journal['currency_name'] ?? $currency['currency_name']);
$currencySymbol = (string) ($journal['currency_symbol'] ?? $currency['currency_symbol']);
$currencyCode = (string) ($journal['currency_code'] ?? $currency['currency_code']);
$currencyDecimalPlaces = (int) ($journal['currency_decimal_places'] ?? $currency['currency_decimal_places'] ?? 2);
$amount = (string) $journal['amount'];
if (
$this->convertToPrimary
&& null !== $this->primaryCurrency
&& $currencyId !== $this->primaryCurrency->id
) {
$currencyId = $this->primaryCurrency->id;
$currencyName = $this->primaryCurrency->name;
$currencySymbol = $this->primaryCurrency->symbol;
$currencyCode = $this->primaryCurrency->code;
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
$amount = (int) ($journal['foreign_currency_id'] ?? 0) === $this->primaryCurrency->id
? (string) ($journal['foreign_amount'] ?? '0')
: (string) ($journal['pc_amount'] ?? '0')
;
}
return [
'currency_id' => $currencyId,
'currency_name' => $currencyName,
'currency_symbol' => $currencySymbol,
'currency_code' => $currencyCode,
'currency_decimal_places' => $currencyDecimalPlaces,
'amount' => Steam::positive($amount),
];
}
}

View File

@@ -68,12 +68,13 @@ class General extends AbstractExtension
$this->getRootSearchOperator(),
$this->carbonize(),
$this->fireflyIIIConfig(),
$this->bccomp(),
];
}
/**
* Will return "active" when a part of the route matches the argument.
* ie. "accounts" will match "accounts.index".
* i.e. "accounts" will match "accounts.index".
*/
protected function activeRoutePartial(): TwigFunction
{
@@ -89,6 +90,10 @@ class General extends AbstractExtension
});
}
/**
* Will return "active" when a part of the route matches the argument.
* ie. "accounts" will match "accounts.index".
*/
/**
* This function will return "active" when the current route matches the first argument (even partly)
* but, the variable $objectType has been set and matches the second argument.
@@ -189,6 +194,13 @@ class General extends AbstractExtension
});
}
protected function bccomp(): TwigFunction
{
return new TwigFunction('bccomp', static function (string $left, string $right): int {
return bccomp($left, $right, 12);
});
}
protected function carbonize(): TwigFunction
{
return new TwigFunction('carbonize', static fn (string $date): Carbon => new Carbon($date, config('app.timezone')));

View File

@@ -156,8 +156,13 @@ $app = Application::configure(basePath: dirname(__DIR__))
]);
// This middleware is added to ensure that the user is not only logged in and
// authenticated (with MFA and everything), but also admin.
$middleware->appendToGroup('api-admin', [
IsAdmin::class,
]);
$middleware->appendToGroup('admin', [
IsAdmin::class,
Range::class,
InterestingMessage::class
]);
// if the user is not logged in, this group applies.

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => (bool)envNonEmpty('USE_RUNNING_BALANCE', true), // this is only the default value, is not used.
// see cer.php for exchange rates feature flag.
],
'version' => '6.5.1',
'build_time' => 1772225602,
'version' => 'develop/2026-02-28',
'build_time' => 1772261249,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.

View File

@@ -14,7 +14,7 @@ tab-width = 4
use-tabs = false
trailing-comma = false
method-chain-breaking-style = "same_line"
preserve-breaking-array-like = false
preserve-breaking-array-like = true
align-assignment-like = true
null-type-hint = "null_pipe"
sort-class-methods = true

View File

@@ -100,7 +100,7 @@ function updateBudgetedAmount(e) {
input.data('limit', data.id);
// update amount left.
$('.left_span[data-limit="0"][data-id="' + budgetId + '"]').html(data.left_formatted);
if (data.left_per_day > 0) {
if (data.left_per_day > 0 && !data.in_past) {
$('.left_span[data-limit="0"][data-id="' + budgetId + '"]').html(data.left_formatted + '(' + data.left_per_day_formatted + ')');
}
// update budgeted amount
@@ -117,7 +117,7 @@ function updateBudgetedAmount(e) {
input.prop('disabled', false);
input.data('limit', data.id);
$('.left_span[data-limit="' + budgetLimitId + '"]').html(data.left_formatted);
if (data.left_per_day > 0) {
if (data.left_per_day > 0 && !data.in_past) {
$('.left_span[data-limit="' + budgetLimitId + '"]').html(data.left_formatted + '(' + data.left_per_day_formatted + ')');
}
updateTotalBudgetedAmount(data.transaction_currency_id);

View File

@@ -324,89 +324,11 @@
{% endif %}
</td>
<td class="hidden-sm hidden-xs spent" data-id="{{ budget.id }}" style="text-align:right;">
{% for spentInfo in budget.spent %}
{{ formatAmountBySymbol(spentInfo.spent, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
{% if 0 == activeDaysPassed %}
({{ formatAmountBySymbol(spentInfo.spent, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol(spentInfo.spent / activeDaysPassed, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %}
<br/>
{% endfor %}
{% for budgetLimit in budget.budgeted %}
{% if null == budget.spent[budgetLimit.currency_id] %}
{{ formatAmountBySymbol(0, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}<br/>
{% endif %}
{% endfor %}
{% include('budgets.partials.amount-spent') %}
</td>
{# this cell displays the amount left in the budget, per budget limit. #}
<td class="left" data-id="{{ budget.id }}" style="text-align: right;">
{% for spentInfo in budget.spent %}
{% set countLimit = 0 %}
{% for budgetLimit in budget.budgeted %}
{# now looping a single budget limit. #}
{% if spentInfo.currency_id == budgetLimit.currency_id and budgetLimit.in_range %}
{# the code below is used for budget limits INSIDE the current view range. #}
{% set countLimit = countLimit + 1 %}
<span class="left_span" data-currency="{{ spentInfo.currency_id }}" data-limit="{{ budgetLimit.id }}"
data-value="{{ spentInfo.spent + budgetLimit.amount }}" class="amount_left">
{# the amount left is automatically calculated. #}
{{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
{% if spentInfo.spent + budgetLimit.amount > 0 %}
{% if 0 == activeDaysLeft %}
({{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol((spentInfo.spent + budgetLimit.amount) / activeDaysLeft, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %}
{% else %}
({{ formatAmountBySymbol(0, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %}
</span>
<br/>
{% endif %}
{% if spentInfo.currency_id == budgetLimit.currency_id and not budgetLimit.in_range and 0.0 == budgetLimit.total_days %}
<span class="left_span" data-currency="{{ spentInfo.currency_id }}" data-limit="{{ budgetLimit.id }}"
data-value="{{ spentInfo.spent + budgetLimit.amount }}" class="amount_left">
{{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
</span>
<span class="text-muted">({{ 'unknown'|_ }})</span>
{% endif %}
{% if spentInfo.currency_id == budgetLimit.currency_id and not budgetLimit.in_range and 0.0 != budgetLimit.total_days %}
<span class="left_span" data-currency="{{ spentInfo.currency_id }}" data-limit="{{ budgetLimit.id }}"
data-value="{{ spentInfo.spent + budgetLimit.amount }}" class="amount_left">
{{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
</span>
({{ formatAmountBySymbol((spentInfo.spent + budgetLimit.amount) / budgetLimit.total_days, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %}
{% endfor %}
{% if countLimit == 0 %}
{# display nothing #}
{% endif %}
{% endfor %}
{% for budgetLimit in budget.budgeted %}
{% if null == budget.spent[budgetLimit.currency_id] %}
<span class="left_span" data-currency="{{ spentInfo.currency_id }}" data-limit="{{ budgetLimit.id }}"
data-value="{{ spentInfo.spent + budgetLimit.amount }}" class="amount_left">
{{ formatAmountBySymbol(budgetLimit.amount, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}
{% if budgetLimit.in_range %}
{% if 0 == activeDaysLeft %}
({{ formatAmountBySymbol(budgetLimit.amount, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol(budgetLimit.amount / activeDaysLeft, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% endif %}
{% endif %}
{% if not budgetLimit.in_range %}
{# For issue #10441, add per day if the budget limit is out of range. #}
({{ formatAmountBySymbol(budgetLimit.amount / budgetLimit.total_days, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% endif %}
</span>
<br/>
{% endif %}
{% endfor %}
{% include('budgets.partials.amount-left') %}
</td>
</tr>
{% endfor %}

View File

@@ -0,0 +1,96 @@
{# The amount left can only be shown for actual budget limits. #}
{% for budgetLimit in budget.budgeted %}
<span class="left_span" data-currency="{{ budgetLimit.currency_id }}" data-limit="{{ budgetLimit.id }}" data-value="{{ budgetLimit.left }}" class="amount_left">
{# the amount left #}
{{ formatAmountBySymbol(budgetLimit.left, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}
{# if the budget limit is in the past, this is not interesting. #}
{# if there is nothing left, this is not interesting. #}
{% if not budgetLimit.in_past and -1 == bccomp('0',budgetLimit.left) %}
{% if 0 == budgetLimit.active_days_left %}
({{ formatAmountBySymbol(budgetLimit.left, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol(budgetLimit.left / budgetLimit.active_days_left, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% endif %}
{% endif %}
{# if there is nothing left, just format 0.00 #}
{% if not budgetLimit.in_past and -1 != bccomp('0',budgetLimit.left) %}
({{ formatAmountBySymbol('0', budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% endif %}
</span><br />
{% endfor %}
{#
{% for spentInfo in budget.spent %}
{% set countLimit = 0 %}
<!-- loop each budget limit collected for this budget in this period. -->
{% for budgetLimit in budget.budgeted %}
<!-- now looping a single budget limit. -->
{% if spentInfo.currency_id == budgetLimit.currency_id and budgetLimit.in_range %}
<!-- the code below is used for budget limits INSIDE the current view range. -->
{% set countLimit = countLimit + 1 %}
<span class="left_span" data-currency="{{ spentInfo.currency_id }}" data-limit="{{ budgetLimit.id }}"
data-value="{{ spentInfo.spent + budgetLimit.amount }}" class="amount_left">
<!--the amount left is automatically calculated. -->
{{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
{% if spentInfo.spent + budgetLimit.amount > 0 %}
{% if 0 == activeDaysLeft %}
({{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol((spentInfo.spent + budgetLimit.amount) / activeDaysLeft, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %}
{% else %}
({{ formatAmountBySymbol(0, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %}
</span>
<br/>
{% endif %}
{% if spentInfo.currency_id == budgetLimit.currency_id and not budgetLimit.in_range and 0.0 == budgetLimit.total_days %}
<span class="left_span" data-currency="{{ spentInfo.currency_id }}" data-limit="{{ budgetLimit.id }}"
data-value="{{ spentInfo.spent + budgetLimit.amount }}" class="amount_left">
{{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
</span>
<span class="text-muted">({{ 'unknown'|_ }})</span>
{% endif %}
{% if spentInfo.currency_id == budgetLimit.currency_id and not budgetLimit.in_range and 0.0 != budgetLimit.total_days %}
<span class="left_span" data-currency="{{ spentInfo.currency_id }}" data-limit="{{ budgetLimit.id }}"
data-value="{{ spentInfo.spent + budgetLimit.amount }}" class="amount_left">
{{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
</span>
({{ formatAmountBySymbol((spentInfo.spent + budgetLimit.amount) / budgetLimit.total_days, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %}
{% endfor %}
{% if countLimit == 0 %}
<!-- display nothing -->
{% endif %}
-->
{% endfor %}
{% for budgetLimit in budget.budgeted %}
{% if null == budget.spent[budgetLimit.currency_id] %}
<span class="left_span" data-currency="{{ spentInfo.currency_id }}" data-limit="{{ budgetLimit.id }}"
data-value="{{ spentInfo.spent + budgetLimit.amount }}" class="amount_left">
{{ formatAmountBySymbol(budgetLimit.amount, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}
{% if budgetLimit.in_range %}
{% if 0 == activeDaysLeft %}
({{ formatAmountBySymbol(budgetLimit.amount, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol(budgetLimit.amount / activeDaysLeft, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% endif %}
{% endif %}
{% if not budgetLimit.in_range %}
<!-- For issue #10441, add per day if the budget limit is out of range. -->
({{ formatAmountBySymbol(budgetLimit.amount / budgetLimit.total_days, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% endif %}
</span>
<br/>
{% endif %}
{% endfor %}
#}

View File

@@ -0,0 +1,42 @@
{# this is spent in budget limits: #}
{% for budgetLimit in budget.budgeted %}
{{ formatAmountBySymbol(budgetLimit.spent, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}
{% if 0 == budgetLimit.active_days_passed %}
({{ formatAmountBySymbol(budgetLimit.spent, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol(budgetLimit.spent / budgetLimit.active_days_passed, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% endif %}
<br />
{% endfor %}
{# this is spent NOT in budget limits: #}
{% for spent in budget.spent %}
{% if 0 != bccomp('0', spent.spent_outside) %}
{{ formatAmountBySymbol(spent.spent_outside, spent.currency_symbol, spent.currency_decimal_places) }}
{% if 0 == activeDaysPassed %}
({{ formatAmountBySymbol(spent.spent_outside, spent.currency_symbol, spent.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol(spent.spent_outside / activeDaysPassed, spent.currency_symbol, spent.currency_decimal_places) }})
{% endif %}
<br />
{% endif %}
{% endfor %}
{#
{% for spentInfo in budget.spent %}
{{ formatAmountBySymbol(spentInfo.spent, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
{% if 0 == activeDaysPassed %}
({{ formatAmountBySymbol(spentInfo.spent, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol(spentInfo.spent / activeDaysPassed, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %}
<br/>
{% endfor %}
{% for budgetLimit in budget.budgeted %}
{% if null == budget.spent[budgetLimit.currency_id] %}
{{ formatAmountBySymbol(0, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}<br/>
{% endif %}
{% endfor %}
#}

View File

@@ -347,7 +347,7 @@ Route::group(
'namespace' => 'FireflyIII\Api\V1\Controllers\Models\UserGroup',
'prefix' => 'v1/user-groups',
'as' => 'api.v1.user-groups.',
'middleware' => ['admin'],
'middleware' => ['api-admin'],
],
static function (): void {
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
@@ -724,7 +724,7 @@ Route::group(
'namespace' => 'FireflyIII\Api\V1\Controllers\System',
'prefix' => 'v1/configuration',
'as' => 'api.v1.configuration.',
'middleware' => ['admin'],
'middleware' => ['api-admin'],
],
static function (): void {
Route::get('', ['uses' => 'ConfigurationController@index', 'as' => 'index']);
@@ -738,7 +738,7 @@ Route::group(
'namespace' => 'FireflyIII\Api\V1\Controllers\System',
'prefix' => 'v1/users',
'as' => 'api.v1.users.',
'middleware' => ['admin'],
'middleware' => ['api-admin'],
],
static function (): void {
Route::get('', ['uses' => 'UserController@index', 'as' => 'index']);