mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2026-03-01 06:41:08 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bae8ab70a | ||
|
|
88030784a5 | ||
|
|
53b733fddb | ||
|
|
ca89159ccd | ||
|
|
a5db3dd2e9 | ||
|
|
b299465fb2 | ||
|
|
12a877d489 | ||
|
|
7f64251a55 | ||
|
|
c06d8263d8 | ||
|
|
5451328ea6 | ||
|
|
3e5ef0b431 | ||
|
|
87fb1fcc92 | ||
|
|
85da46243b | ||
|
|
c44711e9bd | ||
|
|
18161450e4 | ||
|
|
b48b2a411a | ||
|
|
453332eae0 | ||
|
|
4407456167 | ||
|
|
2842432204 | ||
|
|
842ec6da47 | ||
|
|
ceb5873ba7 | ||
|
|
6cfd8273fe | ||
|
|
0c0736d336 |
3
.ci/php-cs-fixer/composer.lock
generated
3
.ci/php-cs-fixer/composer.lock
generated
@@ -2687,5 +2687,8 @@
|
||||
"php": ">=8.5.0"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"platform-overrides": {
|
||||
"php": "8.5"
|
||||
},
|
||||
"plugin-api-version": "2.9.0"
|
||||
}
|
||||
|
||||
@@ -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,18 @@ 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, 'type' => $type] = $request->attributes->all();
|
||||
$types = $this->mapTransactionTypes($type ?? 'default');
|
||||
$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 +146,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);
|
||||
|
||||
@@ -74,8 +74,8 @@ class UpdateController extends Controller
|
||||
*/
|
||||
public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse
|
||||
{
|
||||
Log::debug('Now in update routine for transaction group');
|
||||
$data = $request->getAll();
|
||||
Log::debug('Now in update routine for transaction group', $data);
|
||||
$oldHash = $this->groupRepository->getCompareHash($transactionGroup);
|
||||
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($transactionGroup);
|
||||
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
|
||||
@@ -92,6 +92,7 @@ class UpdateController extends Controller
|
||||
$flags->applyRules = $applyRules;
|
||||
$flags->fireWebhooks = $fireWebhooks;
|
||||
$flags->recalculateCredit = $runRecalculations;
|
||||
$flags->batchSubmission = $data['batch_submission'] ?? false;
|
||||
event(new UpdatedSingleTransactionGroup($flags, $objects));
|
||||
event(new WebhookMessagesRequestSending());
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BatchController extends Controller
|
||||
{
|
||||
@@ -52,19 +53,22 @@ class BatchController extends Controller
|
||||
|
||||
public function finishBatch(Request $request): JsonResponse
|
||||
{
|
||||
$journals = $this->repository->getUncompletedJournals();
|
||||
Log::debug('Now in finishBatch.');
|
||||
$journals = $this->repository->getUncompletedJournals();
|
||||
if (0 === count($journals)) {
|
||||
Log::debug('Counted zero journals, return.');
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Counted %d journals.', count($journals)));
|
||||
/** @var TransactionJournal $first */
|
||||
$first = $journals->first();
|
||||
$group = $first?->transactionGroup;
|
||||
$first = $journals->first();
|
||||
$group = $first?->transactionGroup;
|
||||
if (null === $group) {
|
||||
Log::debug('First group is NULL.');
|
||||
return response()->json([], 204);
|
||||
}
|
||||
$flags = new TransactionGroupEventFlags();
|
||||
$flags->applyRules = 'true' === $request->get('apply_rules');
|
||||
$flags->applyRules = 'true' === $request->input('apply_rules');
|
||||
event(new UserRequestedBatchProcessing($flags));
|
||||
// event(new CreatedSingleTransactionGroup($group, $flags));
|
||||
|
||||
|
||||
@@ -30,9 +30,7 @@ class UserRequestedBatchProcessing extends Event
|
||||
{
|
||||
public TransactionGroupEventObjects $objects;
|
||||
|
||||
public function __construct(
|
||||
public TransactionGroupEventFlags $flags
|
||||
) {
|
||||
public function __construct(public TransactionGroupEventFlags $flags) {
|
||||
$this->objects = new TransactionGroupEventObjects();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -603,7 +603,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
public function setSearchWords(array $array): GroupCollectorInterface
|
||||
{
|
||||
if (0 === count($array)) {
|
||||
Log::debug('No words in array');
|
||||
// Log::debug('No words in array');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -34,10 +34,10 @@ use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Chart\Category\FrontpageChartGenerator;
|
||||
use FireflyIII\Support\Chart\Category\WholePeriodChartGenerator;
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Controllers\AugumentData;
|
||||
use FireflyIII\Support\Http\Controllers\ChartGeneration;
|
||||
use FireflyIII\Support\Http\Controllers\DateCalculation;
|
||||
use FireflyIII\Support\Http\Controllers\ResolvesJournalAmountAndCurrency;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
@@ -51,6 +51,7 @@ class CategoryController extends Controller
|
||||
use AugumentData;
|
||||
use ChartGeneration;
|
||||
use DateCalculation;
|
||||
use ResolvesJournalAmountAndCurrency;
|
||||
|
||||
protected GeneratorInterface $generator;
|
||||
|
||||
@@ -267,7 +268,8 @@ class CategoryController extends Controller
|
||||
// loop income and expenses for this category.:
|
||||
$outSet = $expenses[$currencyId]['categories'][$categoryId] ?? ['transaction_journals' => []];
|
||||
foreach ($outSet['transaction_journals'] as $journal) {
|
||||
$amount = Steam::positive($journal['amount']);
|
||||
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currencyInfo);
|
||||
$amount = $journalData['amount'];
|
||||
$date = $journal['date']->isoFormat($format);
|
||||
$chartData[$outKey]['entries'][$date] ??= '0';
|
||||
|
||||
@@ -276,7 +278,8 @@ class CategoryController extends Controller
|
||||
|
||||
$inSet = $income[$currencyId]['categories'][$categoryId] ?? ['transaction_journals' => []];
|
||||
foreach ($inSet['transaction_journals'] as $journal) {
|
||||
$amount = Steam::positive($journal['amount']);
|
||||
$journalData = $this->resolveJournalAmountAndCurrency($journal, $currencyInfo);
|
||||
$amount = $journalData['amount'];
|
||||
$date = $journal['date']->isoFormat($format);
|
||||
$chartData[$inKey]['entries'][$date] ??= '0';
|
||||
$chartData[$inKey]['entries'][$date] = bcadd($amount, $chartData[$inKey]['entries'][$date]);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -149,7 +151,7 @@ class ReportController extends Controller
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
// return response()->json($cache->get());
|
||||
}
|
||||
|
||||
Log::debug('Going to do operations for accounts ', $accounts->pluck('id')->toArray());
|
||||
@@ -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.
|
||||
@@ -226,7 +219,7 @@ class ReportController extends Controller
|
||||
|
||||
/** @var array $currency */
|
||||
foreach ($data as $currency) {
|
||||
Log::debug(sprintf('Now processing currency "%s"', $currency['currency_name']));
|
||||
Log::debug(sprintf('Now processing currency %s', $currency['currency_code']));
|
||||
$income = [
|
||||
'label' => (string) trans('firefly.box_earned_in_currency', ['currency' => $currency['currency_name']]),
|
||||
'type' => 'bar',
|
||||
@@ -254,7 +247,11 @@ class ReportController extends Controller
|
||||
if ('1Y' === $preferredRange) {
|
||||
$currentEnd = Navigation::endOfPeriod($currentEnd, $preferredRange);
|
||||
}
|
||||
Log::debug('Start of sub-loop');
|
||||
// 2026-03-01 similar fix for monthly ranges.
|
||||
if ('1M' === $preferredRange) {
|
||||
$currentEnd = Navigation::endOfPeriod($currentEnd, $preferredRange);
|
||||
}
|
||||
Log::debug(sprintf('Start of sub-loop, current end is %s', $currentEnd->toW3cString()));
|
||||
while ($currentStart <= $currentEnd) {
|
||||
Log::debug(sprintf('Current start: %s', $currentStart->toW3cString()));
|
||||
$key = $currentStart->format($format);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Http\Controllers\ResolvesJournalAmountAndCurrency;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
@@ -37,6 +38,8 @@ use Illuminate\Http\JsonResponse;
|
||||
*/
|
||||
class TransactionController extends Controller
|
||||
{
|
||||
use ResolvesJournalAmountAndCurrency;
|
||||
|
||||
/** @var GeneratorInterface Chart generation methods. */
|
||||
protected $generator;
|
||||
|
||||
@@ -57,6 +60,7 @@ class TransactionController extends Controller
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty('chart.transactions.budgets');
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
@@ -74,14 +78,15 @@ class TransactionController extends Controller
|
||||
// group by category.
|
||||
/** @var array $journal */
|
||||
foreach ($result as $journal) {
|
||||
$resolved = $this->resolveJournalAmountAndCurrency($journal);
|
||||
$budget = $journal['budget_name'] ?? (string) trans('firefly.no_budget');
|
||||
$title = sprintf('%s (%s)', $budget, $journal['currency_symbol']);
|
||||
$title = sprintf('%s (%s)', $budget, $resolved['currency_symbol']);
|
||||
$data[$title] ??= [
|
||||
'amount' => '0',
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_symbol' => $resolved['currency_symbol'],
|
||||
'currency_code' => $resolved['currency_code'],
|
||||
];
|
||||
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
|
||||
$data[$title]['amount'] = bcadd($data[$title]['amount'], $resolved['amount']);
|
||||
}
|
||||
$chart = $this->generator->multiCurrencyPieChart($data);
|
||||
$cache->store($chart);
|
||||
@@ -98,6 +103,7 @@ class TransactionController extends Controller
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($objectType);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty('chart.transactions.categories');
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
@@ -124,14 +130,15 @@ class TransactionController extends Controller
|
||||
// group by category.
|
||||
/** @var array $journal */
|
||||
foreach ($result as $journal) {
|
||||
$resolved = $this->resolveJournalAmountAndCurrency($journal);
|
||||
$category = $journal['category_name'] ?? (string) trans('firefly.no_category');
|
||||
$title = sprintf('%s (%s)', $category, $journal['currency_symbol']);
|
||||
$title = sprintf('%s (%s)', $category, $resolved['currency_symbol']);
|
||||
$data[$title] ??= [
|
||||
'amount' => '0',
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_symbol' => $resolved['currency_symbol'],
|
||||
'currency_code' => $resolved['currency_code'],
|
||||
];
|
||||
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
|
||||
$data[$title]['amount'] = bcadd($data[$title]['amount'], $resolved['amount']);
|
||||
}
|
||||
$chart = $this->generator->multiCurrencyPieChart($data);
|
||||
$cache->store($chart);
|
||||
@@ -148,6 +155,7 @@ class TransactionController extends Controller
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($objectType);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty('chart.transactions.destinations');
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
@@ -174,14 +182,15 @@ class TransactionController extends Controller
|
||||
// group by category.
|
||||
/** @var array $journal */
|
||||
foreach ($result as $journal) {
|
||||
$resolved = $this->resolveJournalAmountAndCurrency($journal);
|
||||
$name = $journal['destination_account_name'];
|
||||
$title = sprintf('%s (%s)', $name, $journal['currency_symbol']);
|
||||
$title = sprintf('%s (%s)', $name, $resolved['currency_symbol']);
|
||||
$data[$title] ??= [
|
||||
'amount' => '0',
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_symbol' => $resolved['currency_symbol'],
|
||||
'currency_code' => $resolved['currency_code'],
|
||||
];
|
||||
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
|
||||
$data[$title]['amount'] = bcadd($data[$title]['amount'], $resolved['amount']);
|
||||
}
|
||||
$chart = $this->generator->multiCurrencyPieChart($data);
|
||||
$cache->store($chart);
|
||||
@@ -198,6 +207,7 @@ class TransactionController extends Controller
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($objectType);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty('chart.transactions.sources');
|
||||
if ($cache->has()) {
|
||||
return response()->json($cache->get());
|
||||
@@ -224,14 +234,15 @@ class TransactionController extends Controller
|
||||
// group by category.
|
||||
/** @var array $journal */
|
||||
foreach ($result as $journal) {
|
||||
$resolved = $this->resolveJournalAmountAndCurrency($journal);
|
||||
$name = $journal['source_account_name'];
|
||||
$title = sprintf('%s (%s)', $name, $journal['currency_symbol']);
|
||||
$title = sprintf('%s (%s)', $name, $resolved['currency_symbol']);
|
||||
$data[$title] ??= [
|
||||
'amount' => '0',
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_symbol' => $resolved['currency_symbol'],
|
||||
'currency_code' => $resolved['currency_code'],
|
||||
];
|
||||
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
|
||||
$data[$title]['amount'] = bcadd($data[$title]['amount'], $resolved['amount']);
|
||||
}
|
||||
$chart = $this->generator->multiCurrencyPieChart($data);
|
||||
$cache->store($chart);
|
||||
|
||||
@@ -54,9 +54,13 @@ trait SupportsGroupProcessingTrait
|
||||
// create and fire rule engine.
|
||||
$newRuleEngine = app(RuleEngineInterface::class);
|
||||
$newRuleEngine->setUser($user);
|
||||
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
|
||||
$newRuleEngine->setRuleGroups($groups);
|
||||
$newRuleEngine->fire();
|
||||
foreach($array as $journalId) {
|
||||
$newRuleEngine->removeOperator('journal_id');
|
||||
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalId]);
|
||||
$newRuleEngine->fire();
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Done with processRules("%s") for %d journal(s)', $type, $set->count()));
|
||||
}
|
||||
|
||||
|
||||
@@ -78,8 +78,16 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
|
||||
// only a subset of the fields.
|
||||
$journalId = (int) $journal['transaction_journal_id'];
|
||||
$array[$currencyId]['categories'][0]['transaction_journals'][$journalId] = [
|
||||
'amount' => Steam::negative($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
'amount' => Steam::negative($journal['amount']),
|
||||
'currency_id' => (int) $journal['currency_id'],
|
||||
'currency_name' => (string) $journal['currency_name'],
|
||||
'currency_symbol' => (string) $journal['currency_symbol'],
|
||||
'currency_code' => (string) $journal['currency_code'],
|
||||
'currency_decimal_places' => (int) $journal['currency_decimal_places'],
|
||||
'foreign_currency_id' => (int) ($journal['foreign_currency_id'] ?? 0),
|
||||
'foreign_amount' => isset($journal['foreign_amount']) ? Steam::negative((string) $journal['foreign_amount']) : null,
|
||||
'pc_amount' => isset($journal['pc_amount']) ? Steam::negative((string) $journal['pc_amount']) : null,
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -124,8 +132,16 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
|
||||
// only a subset of the fields.
|
||||
$journalId = (int) $journal['transaction_journal_id'];
|
||||
$array[$currencyId]['categories'][0]['transaction_journals'][$journalId] = [
|
||||
'amount' => Steam::positive($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
'amount' => Steam::positive($journal['amount']),
|
||||
'currency_id' => (int) $journal['currency_id'],
|
||||
'currency_name' => (string) $journal['currency_name'],
|
||||
'currency_symbol' => (string) $journal['currency_symbol'],
|
||||
'currency_code' => (string) $journal['currency_code'],
|
||||
'currency_decimal_places' => (int) $journal['currency_decimal_places'],
|
||||
'foreign_currency_id' => (int) ($journal['foreign_currency_id'] ?? 0),
|
||||
'foreign_amount' => isset($journal['foreign_amount']) ? Steam::positive((string) $journal['foreign_amount']) : null,
|
||||
'pc_amount' => isset($journal['pc_amount']) ? Steam::positive((string) $journal['pc_amount']) : null,
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,14 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$journalId = (int) $journal['transaction_journal_id'];
|
||||
$array[$currencyId]['categories'][$categoryId]['transaction_journals'][$journalId] = [
|
||||
'amount' => Steam::negative($journal['amount']),
|
||||
'currency_id' => (int) $journal['currency_id'],
|
||||
'currency_name' => (string) $journal['currency_name'],
|
||||
'currency_symbol' => (string) $journal['currency_symbol'],
|
||||
'currency_code' => (string) $journal['currency_code'],
|
||||
'currency_decimal_places' => (int) $journal['currency_decimal_places'],
|
||||
'foreign_currency_id' => (int) ($journal['foreign_currency_id'] ?? 0),
|
||||
'foreign_amount' => isset($journal['foreign_amount']) ? Steam::negative((string) $journal['foreign_amount']) : null,
|
||||
'pc_amount' => isset($journal['pc_amount']) ? Steam::negative((string) $journal['pc_amount']) : null,
|
||||
'date' => $journal['date'],
|
||||
'source_account_id' => (string) $journal['source_account_id'],
|
||||
'budget_name' => $journal['budget_name'],
|
||||
@@ -223,6 +231,14 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$journalId = (int) $journal['transaction_journal_id'];
|
||||
$array[$currencyId]['categories'][$categoryId]['transaction_journals'][$journalId] = [
|
||||
'amount' => Steam::positive($journal['amount']),
|
||||
'currency_id' => (int) $journal['currency_id'],
|
||||
'currency_name' => (string) $journal['currency_name'],
|
||||
'currency_symbol' => (string) $journal['currency_symbol'],
|
||||
'currency_code' => (string) $journal['currency_code'],
|
||||
'currency_decimal_places' => (int) $journal['currency_decimal_places'],
|
||||
'foreign_currency_id' => (int) ($journal['foreign_currency_id'] ?? 0),
|
||||
'foreign_amount' => isset($journal['foreign_amount']) ? Steam::positive((string) $journal['foreign_amount']) : null,
|
||||
'pc_amount' => isset($journal['pc_amount']) ? Steam::positive((string) $journal['pc_amount']) : null,
|
||||
'date' => $journal['date'],
|
||||
'source_account_id' => (string) $journal['source_account_id'],
|
||||
'destination_account_id' => (string) $journal['destination_account_id'],
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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 = null): array
|
||||
{
|
||||
$currency ??= $journal;
|
||||
|
||||
$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),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -453,7 +453,7 @@ class Navigation
|
||||
$diff = $start->diffInMonths($end, true);
|
||||
// increment by month (for year)
|
||||
if ($diff >= 1.0001 && $diff < 12.001) {
|
||||
$increment = 'addMonth';
|
||||
$increment = 'addMonthsNoOverflow';
|
||||
$displayFormat = (string) trans('config.month_js');
|
||||
}
|
||||
|
||||
@@ -464,12 +464,15 @@ class Navigation
|
||||
}
|
||||
$begin = clone $start;
|
||||
$entries = [];
|
||||
Log::debug(sprintf('listOfPeriods start of loop (end: %s).', $end->format('Y-m-d H:i:s')));
|
||||
while ($begin < $end) {
|
||||
Log::debug(sprintf('Begin is now %s.', $begin->format('Y-m-d H:i:s')));
|
||||
$formatted = $begin->format($format);
|
||||
$displayed = $begin->isoFormat($displayFormat);
|
||||
$entries[$formatted] = $displayed;
|
||||
$begin->{$increment}(); // @phpstan-ignore-line
|
||||
}
|
||||
Log::debug('listOfPeriods end of loop.');
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
|
||||
/** @var QueryParserInterface $parser */
|
||||
$parser = app(QueryParserInterface::class);
|
||||
Log::debug(sprintf('Using %s as implementation for QueryParserInterface', $parser::class));
|
||||
// Log::debug(sprintf('Using %s as implementation for QueryParserInterface', $parser::class));
|
||||
|
||||
try {
|
||||
$parsedQuery = $parser->parse($query);
|
||||
@@ -189,7 +189,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
throw new FireflyException(sprintf('Invalid search value "%s". See the logs.', e($query)), 0, $e);
|
||||
}
|
||||
|
||||
Log::debug(sprintf('Found %d node(s) at top-level', count($parsedQuery->getNodes())));
|
||||
// Log::debug(sprintf('Found %d node(s) at top-level', count($parsedQuery->getNodes())));
|
||||
$this->handleSearchNode($parsedQuery, $parsedQuery->isProhibited(false));
|
||||
|
||||
// add missing information
|
||||
@@ -329,7 +329,7 @@ class OperatorQuerySearch implements SearchInterface
|
||||
*/
|
||||
private function handleSearchNode(Node $node, bool $flipProhibitedFlag): void
|
||||
{
|
||||
Log::debug(sprintf('Now in handleSearchNode(%s)', $node::class));
|
||||
// Log::debug(sprintf('Now in handleSearchNode(%s)', $node::class));
|
||||
|
||||
switch (true) {
|
||||
case $node instanceof StringNode:
|
||||
|
||||
@@ -145,7 +145,7 @@ class QueryParser implements QueryParserInterface
|
||||
$skipNext = true;
|
||||
}
|
||||
if ('' !== $tokenUnderConstruction && !$skipNext) { // @phpstan-ignore-line
|
||||
Log::debug(sprintf('Turns out that "%s" is a field name. Reset the token.', $tokenUnderConstruction));
|
||||
// Log::debug(sprintf('Turns out that "%s" is a field name. Reset the token.', $tokenUnderConstruction));
|
||||
// If we meet a colon with a left-hand side string, we know we're in a field and are about to set up the value
|
||||
$fieldName = $tokenUnderConstruction;
|
||||
$tokenUnderConstruction = '';
|
||||
|
||||
@@ -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')));
|
||||
|
||||
@@ -37,6 +37,9 @@ interface RuleEngineInterface
|
||||
*/
|
||||
public function addOperator(array $operator): void;
|
||||
|
||||
public function removeOperator(string $type): void;
|
||||
|
||||
|
||||
/**
|
||||
* Find all transactions only, dont apply anything.
|
||||
*/
|
||||
|
||||
@@ -45,12 +45,13 @@ use Illuminate\Support\Facades\Log;
|
||||
class SearchRuleEngine implements RuleEngineInterface
|
||||
{
|
||||
private readonly Collection $groups;
|
||||
private array $operators = [];
|
||||
|
||||
private array $operators = [];
|
||||
// always collect the triggers from the database, unless indicated otherwise.
|
||||
private bool $refreshTriggers = true;
|
||||
private array $resultCount = [];
|
||||
private bool $refreshTriggers = true;
|
||||
private array $resultCount = [];
|
||||
private readonly Collection $rules;
|
||||
private User $user;
|
||||
private User $user;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -69,7 +70,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
Log::debug('SearchRuleEngine::find()');
|
||||
$collection = new Collection();
|
||||
foreach ($this->rules as $rule) {
|
||||
$found = new Collection();
|
||||
$found = new Collection();
|
||||
if (true === $rule->strict) {
|
||||
$found = $this->findStrictRule($rule);
|
||||
}
|
||||
@@ -78,7 +79,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
}
|
||||
$collection = $collection->merge($found);
|
||||
}
|
||||
$result = $collection->unique();
|
||||
$result = $collection->unique();
|
||||
Log::debug(sprintf('SearchRuleEngine::find() returns %d unique transactions.', $result->count()));
|
||||
|
||||
return $result;
|
||||
@@ -101,9 +102,9 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
$result = $this->fireRule($rule);
|
||||
if ($result && true === $rule->stop_processing) {
|
||||
Log::debug(sprintf(
|
||||
'Rule #%d has triggered and executed, but calls to stop processing. Since not in the context of a group, do not stop.',
|
||||
$rule->id
|
||||
));
|
||||
'Rule #%d has triggered and executed, but calls to stop processing. Since not in the context of a group, do not stop.',
|
||||
$rule->id
|
||||
));
|
||||
}
|
||||
if (false === $result && true === $rule->stop_processing) {
|
||||
Log::debug(sprintf('Rule #%d has triggered and changed nothing, but calls to stop processing. Do not stop.', $rule->id));
|
||||
@@ -169,10 +170,9 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
private function addNotes(array $transaction): array
|
||||
{
|
||||
$transaction['notes'] = '';
|
||||
$dbNote = Note::where('noteable_id', (int) $transaction['transaction_journal_id'])
|
||||
->where('noteable_type', TransactionJournal::class)
|
||||
->first(['notes.*'])
|
||||
;
|
||||
$dbNote = Note::where('noteable_id', (int)$transaction['transaction_journal_id'])
|
||||
->where('noteable_type', TransactionJournal::class)
|
||||
->first(['notes.*']);
|
||||
if (null !== $dbNote) {
|
||||
$transaction['notes'] = $dbNote->text;
|
||||
}
|
||||
@@ -201,12 +201,12 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
/** @var RuleTrigger $ruleTrigger */
|
||||
foreach ($triggers as $ruleTrigger) {
|
||||
Log::debug(sprintf(
|
||||
'Now at rule trigger #%d: %s:"%s" (%s).',
|
||||
$ruleTrigger->id,
|
||||
$ruleTrigger->trigger_type,
|
||||
$ruleTrigger->trigger_value,
|
||||
var_export($ruleTrigger->stop_processing, true)
|
||||
));
|
||||
'Now at rule trigger #%d: %s:"%s" (%s).',
|
||||
$ruleTrigger->id,
|
||||
$ruleTrigger->trigger_type,
|
||||
$ruleTrigger->trigger_value,
|
||||
var_export($ruleTrigger->stop_processing, true)
|
||||
));
|
||||
if (false === $ruleTrigger->active) {
|
||||
Log::debug('Trigger is not active, continue.');
|
||||
|
||||
@@ -244,10 +244,10 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
$searchEngine->parseQuery(sprintf('%s:%s', $type, $value));
|
||||
}
|
||||
|
||||
$result = $searchEngine->searchTransactions();
|
||||
$collection = $result->getCollection();
|
||||
$result = $searchEngine->searchTransactions();
|
||||
$collection = $result->getCollection();
|
||||
Log::debug(sprintf('Found in this run, %d transactions', $collection->count()));
|
||||
$total = $total->merge($collection);
|
||||
$total = $total->merge($collection);
|
||||
Log::debug(sprintf('Total collection is now %d transactions', $total->count()));
|
||||
++$count;
|
||||
// if trigger says stop processing, do so.
|
||||
@@ -261,7 +261,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
Log::debug(sprintf('Done running %d trigger(s)', $count));
|
||||
|
||||
// make collection unique
|
||||
$unique = $total->unique(static function (array $group): string {
|
||||
$unique = $total->unique(static function (array $group): string {
|
||||
$str = '';
|
||||
foreach ($group['transactions'] as $transaction) {
|
||||
$str = sprintf('%s%d', $str, $transaction['transaction_journal_id']);
|
||||
@@ -283,8 +283,8 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
private function findStrictRule(Rule $rule): Collection
|
||||
{
|
||||
Log::debug(sprintf('Now in findStrictRule(#%d)', $rule->id ?? 0));
|
||||
$searchArray = [];
|
||||
$triggers = [];
|
||||
$searchArray = [];
|
||||
$triggers = [];
|
||||
if ($this->refreshTriggers) {
|
||||
$triggers = $rule->ruleTriggers()->orderBy('order', 'ASC')->get();
|
||||
}
|
||||
@@ -298,12 +298,12 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
continue;
|
||||
}
|
||||
$contextSearch = $ruleTrigger->trigger_type;
|
||||
if (str_starts_with((string) $ruleTrigger->trigger_type, '-')) {
|
||||
$contextSearch = substr((string) $ruleTrigger->trigger_type, 1);
|
||||
if (str_starts_with((string)$ruleTrigger->trigger_type, '-')) {
|
||||
$contextSearch = substr((string)$ruleTrigger->trigger_type, 1);
|
||||
}
|
||||
|
||||
// if the trigger needs no context, value is different:
|
||||
$needsContext = (bool) (config(sprintf('search.operators.%s.needs_context', $contextSearch)) ?? true);
|
||||
$needsContext = (bool)(config(sprintf('search.operators.%s.needs_context', $contextSearch)) ?? true);
|
||||
if (false === $needsContext) {
|
||||
Log::debug(sprintf('SearchRuleEngine:: add a rule trigger (no context): %s:true', $ruleTrigger->trigger_type));
|
||||
$searchArray[$ruleTrigger->trigger_type][] = 'true';
|
||||
@@ -319,7 +319,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
Log::debug(sprintf('SearchRuleEngine:: add local added operator: %s:"%s"', $operator['type'], $operator['value']));
|
||||
$searchArray[$operator['type']][] = sprintf('"%s"', $operator['value']);
|
||||
}
|
||||
$date = today(config('app.timezone'));
|
||||
$date = today(config('app.timezone'));
|
||||
if ($this->hasSpecificJournalTrigger($searchArray)) {
|
||||
$date = $this->setDateFromJournalTrigger($searchArray);
|
||||
}
|
||||
@@ -331,6 +331,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
$searchEngine->setLimit(31337);
|
||||
$searchEngine->setDate($date);
|
||||
Log::debug('Search array', $searchArray);
|
||||
|
||||
foreach ($searchArray as $type => $searches) {
|
||||
foreach ($searches as $value) {
|
||||
$query = sprintf('%s:%s', $type, $value);
|
||||
@@ -339,7 +340,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
}
|
||||
}
|
||||
|
||||
$result = $searchEngine->searchTransactions();
|
||||
$result = $searchEngine->searchTransactions();
|
||||
|
||||
return $result->getCollection();
|
||||
}
|
||||
@@ -359,12 +360,8 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
$rules = $group
|
||||
->rules()
|
||||
->orderBy('rules.order', 'ASC')
|
||||
// ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
|
||||
// ->where('rule_triggers.trigger_type', 'user_action')
|
||||
// ->where('rule_triggers.trigger_value', 'store-journal')
|
||||
->where('rules.active', true)
|
||||
->get(['rules.*'])
|
||||
;
|
||||
->get(['rules.*']);
|
||||
}
|
||||
Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $rules->count()));
|
||||
|
||||
@@ -432,7 +429,7 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
|
||||
$this->processResults($rule, $collection);
|
||||
|
||||
$result = $collection->count() > 0;
|
||||
$result = $collection->count() > 0;
|
||||
if ($result) {
|
||||
Log::debug(sprintf('SearchRuleEngine:: Done. Rule #%d was triggered (on %d transaction(s)).', $rule->id, $collection->count()));
|
||||
|
||||
@@ -458,12 +455,16 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
Log::debug('Found a journal_id trigger with 1 journal, true.');
|
||||
$journalTrigger = true;
|
||||
}
|
||||
if ('journal_id' === $triggerName && is_string($values) && !str_contains($values,',')) {
|
||||
Log::debug('Found a journal_id trigger with 1 journal, true.');
|
||||
$journalTrigger = true;
|
||||
}
|
||||
if (in_array($triggerName, ['date_is', 'date', 'on', 'date_before', 'before', 'date_after', 'after'], true)) {
|
||||
Log::debug('Found a date related trigger, set to true.');
|
||||
$dateTrigger = true;
|
||||
}
|
||||
}
|
||||
$result = $journalTrigger && $dateTrigger;
|
||||
$result = $journalTrigger && $dateTrigger;
|
||||
Log::debug(sprintf('Result of hasSpecificJournalTrigger is %s.', var_export($result, true)));
|
||||
|
||||
return $result;
|
||||
@@ -495,11 +496,11 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
if ($result) {
|
||||
$this->resultCount[$journalId] = array_key_exists($journalId, $this->resultCount) ? $this->resultCount[$journalId]++ : 1;
|
||||
Log::debug(sprintf(
|
||||
'Action "%s" on journal #%d was executed, so count a result. Updated transaction journal count is now %d.',
|
||||
$ruleAction->action_type,
|
||||
$transaction['transaction_journal_id'] ?? 0,
|
||||
count($this->resultCount)
|
||||
));
|
||||
'Action "%s" on journal #%d was executed, so count a result. Updated transaction journal count is now %d.',
|
||||
$ruleAction->action_type,
|
||||
$transaction['transaction_journal_id'] ?? 0,
|
||||
count($this->resultCount)
|
||||
));
|
||||
}
|
||||
if (false === $result) {
|
||||
Log::debug(sprintf('Action "%s" reports NO changes were made.', $ruleAction->action_type));
|
||||
@@ -557,14 +558,14 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
$journalId = 0;
|
||||
foreach ($array as $triggerName => $values) {
|
||||
if ('journal_id' === $triggerName && is_array($values) && 1 === count($values)) {
|
||||
$journalId = (int) trim($values[0] ?? '"0"', '"'); // follows format "123".
|
||||
$journalId = (int)trim($values[0] ?? '"0"', '"'); // follows format "123".
|
||||
Log::debug(sprintf('Found journal ID #%d', $journalId));
|
||||
}
|
||||
}
|
||||
if (0 !== $journalId) {
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
$repository->setUser($this->user);
|
||||
$journal = $repository->find($journalId);
|
||||
$journal = $repository->find($journalId);
|
||||
if (null !== $journal) {
|
||||
$date = $journal->date;
|
||||
Log::debug(sprintf('Found journal #%d with date %s.', $journal->id, $journal->date->format('Y-m-d')));
|
||||
@@ -576,4 +577,19 @@ class SearchRuleEngine implements RuleEngineInterface
|
||||
|
||||
return today(config('app.timezone'));
|
||||
}
|
||||
|
||||
|
||||
#[\Override]
|
||||
public function removeOperator(string $type): void
|
||||
{
|
||||
$new = [];
|
||||
foreach ($this->operators as $operator) {
|
||||
if ($type === $operator['type']) {
|
||||
Log::debug(sprintf('Removing operator "%s"', $type));
|
||||
continue;
|
||||
}
|
||||
$new[] = $operator;
|
||||
}
|
||||
$this->operators = $new;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
96
resources/views/budgets/partials/amount-left.twig
Normal file
96
resources/views/budgets/partials/amount-left.twig
Normal 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 %}
|
||||
#}
|
||||
42
resources/views/budgets/partials/amount-spent.twig
Normal file
42
resources/views/budgets/partials/amount-spent.twig
Normal 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 %}
|
||||
#}
|
||||
@@ -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']);
|
||||
|
||||
Reference in New Issue
Block a user