Compare commits

...

36 Commits

Author SHA1 Message Date
github-actions[bot]
aaffc125e7 Merge pull request #10724 from firefly-iii/release-1754630264
🤖 Automatically merge the PR into the develop branch.
2025-08-08 07:17:53 +02:00
JC5
41a48c39a0 🤖 Auto commit for release 'develop' on 2025-08-08 2025-08-08 07:17:44 +02:00
James Cole
2d96bd84b5 Fix #9640 2025-08-08 06:43:05 +02:00
James Cole
ad1c1d2254 Fix #10071 2025-08-08 06:42:54 +02:00
github-actions[bot]
813206766d Merge pull request #10723 from firefly-iii/release-1754591858
🤖 Automatically merge the PR into the develop branch.
2025-08-07 20:37:46 +02:00
JC5
bb25d4a82a 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 20:37:38 +02:00
James Cole
f3b78beecc Fix another null 2025-08-07 20:33:24 +02:00
github-actions[bot]
64073768fe Merge pull request #10722 from firefly-iii/release-1754590638
🤖 Automatically merge the PR into the develop branch.
2025-08-07 20:17:25 +02:00
JC5
fe6dd0f901 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 20:17:19 +02:00
James Cole
aac8d11ff6 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-08-07 20:13:35 +02:00
James Cole
afa99a35b5 Fix NULLs 2025-08-07 20:13:29 +02:00
James Cole
e9cb0a51d7 Catch NULL. 2025-08-07 20:11:46 +02:00
github-actions[bot]
9fbcccfd02 Merge pull request #10721 from firefly-iii/release-1754589876
🤖 Automatically merge the PR into the develop branch.
2025-08-07 20:04:43 +02:00
JC5
468c9c9d56 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 20:04:36 +02:00
James Cole
f76b27a73d Fix #10565 and fix #10600 2025-08-07 19:59:02 +02:00
James Cole
579fe81616 Fix #10656 2025-08-07 19:52:12 +02:00
James Cole
ec9ba53690 Fix #10678 2025-08-07 19:48:00 +02:00
James Cole
85337c53d4 Fix currency collection. 2025-08-07 19:37:36 +02:00
James Cole
eb6d585bb2 Fix a variety of code. 2025-08-07 19:09:25 +02:00
James Cole
378ffbc609 Fix #10720 2025-08-07 17:55:25 +02:00
James Cole
3b3c8e5bcd Add object group to various items. 2025-08-07 07:46:49 +02:00
github-actions[bot]
75cbdb6a57 Merge pull request #10719 from firefly-iii/release-1754540665
🤖 Automatically merge the PR into the develop branch.
2025-08-07 06:24:32 +02:00
JC5
cdaff0d983 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 06:24:25 +02:00
James Cole
dda3863889 Fix account_id key. 2025-08-07 06:20:36 +02:00
github-actions[bot]
57dc423b3f Merge pull request #10718 from firefly-iii/release-1754540258
🤖 Automatically merge the PR into the develop branch.
2025-08-07 06:17:51 +02:00
JC5
c0570bc3b2 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 06:17:38 +02:00
James Cole
65110d1666 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2025-08-07 06:12:02 +02:00
James Cole
5a10b29402 Set to zero instead of null. 2025-08-07 06:11:56 +02:00
github-actions[bot]
028544ca2e Merge pull request #10717 from firefly-iii/release-1754539541
🤖 Automatically merge the PR into the develop branch.
2025-08-07 06:05:53 +02:00
JC5
af46729372 🤖 Auto commit for release 'develop' on 2025-08-07 2025-08-07 06:05:41 +02:00
James Cole
4c7789a668 Add missing access rights. 2025-08-07 06:01:14 +02:00
James Cole
292908048c Add enrichment to recurring 2025-08-07 05:59:26 +02:00
James Cole
e300314e05 Catch NULL. 2025-08-07 05:56:31 +02:00
James Cole
4f1f360346 Add primary to array. 2025-08-07 05:55:15 +02:00
James Cole
bff856aeff Rename method, remove 0. 2025-08-07 05:52:56 +02:00
James Cole
7f5a1bda8d Catch null 2025-08-06 20:53:30 +02:00
39 changed files with 381 additions and 202 deletions

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use FireflyIII\Transformers\ExchangeRateTransformer;
@@ -39,7 +40,7 @@ class IndexController extends Controller
use ValidatesUserGroupTrait;
public const string RESOURCE_KEY = 'currency_exchange_rates';
protected array $acceptedRoles = [UserRoleEnum::OWNER];
private ExchangeRateRepositoryInterface $repository;
public function __construct()

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
@@ -41,7 +42,7 @@ class ShowController extends Controller
use ValidatesUserGroupTrait;
public const string RESOURCE_KEY = 'exchange-rates';
protected array $acceptedRoles = [UserRoleEnum::OWNER];
private ExchangeRateRepositoryInterface $repository;
public function __construct()

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\StoreRequest;
use FireflyIII\Api\V1\Controllers\Controller;
@@ -37,7 +38,7 @@ class StoreController extends Controller
use ValidatesUserGroupTrait;
public const string RESOURCE_KEY = 'exchange-rates';
protected array $acceptedRoles = [UserRoleEnum::OWNER];
private ExchangeRateRepositoryInterface $repository;
public function __construct()

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\CurrencyExchangeRate;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\UpdateRequest;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
@@ -37,7 +38,7 @@ class UpdateController extends Controller
use ValidatesUserGroupTrait;
public const string RESOURCE_KEY = 'exchange-rates';
protected array $acceptedRoles = [UserRoleEnum::OWNER];
private ExchangeRateRepositoryInterface $repository;
public function __construct()

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Recurrence\StoreRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Transformers\RecurrenceTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use League\Fractal\Resource\Item;
@@ -69,6 +71,13 @@ class StoreController extends Controller
$recurrence = $this->repository->store($data);
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new RecurringEnrichment();
$enrichment->setUser($admin);
$recurrence = $enrichment->enrichSingle($recurrence);
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Recurrence\UpdateRequest;
use FireflyIII\Models\Recurrence;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Transformers\RecurrenceTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use League\Fractal\Resource\Item;
@@ -67,6 +69,13 @@ class UpdateController extends Controller
$recurrence = $this->repository->update($recurrence, $data);
$manager = $this->getManager();
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new RecurringEnrichment();
$enrichment->setUser($admin);
$recurrence = $enrichment->enrichSingle($recurrence);
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters($this->parameters);

View File

@@ -44,6 +44,7 @@ use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
use FireflyIII\Transformers\AccountTransformer;
@@ -276,17 +277,24 @@ class ListController extends Controller
}
);
$count = $collection->count();
$piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
$recurrences = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new RecurringEnrichment();
$enrichment->setUser($admin);
$recurrences = $enrichment->enrich($recurrences);
// make paginator:
$paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page'));
$paginator = new LengthAwarePaginator($recurrences, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.currencies.recurrences', [$currency->code]).$this->buildParams());
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new FractalCollection($piggyBanks, $transformer, 'recurrences');
$resource = new FractalCollection($recurrences, $transformer, 'recurrences');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);

View File

@@ -248,10 +248,10 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
'value_parsed' => Amount::formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
'local_icon' => 'balance-scale',
'sub_title' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
.' + '.app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
'sub_title' => Amount::formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
.' + '.Amount::formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
];
$return[] = [
'key' => sprintf('spent-in-%s', $currency->code),
@@ -261,7 +261,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false),
'value_parsed' => Amount::formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false),
'local_icon' => 'balance-scale',
'sub_title' => '',
];
@@ -273,7 +273,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
'value_parsed' => Amount::formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
'local_icon' => 'balance-scale',
'sub_title' => '',
];
@@ -289,10 +289,10 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
'value_parsed' => Amount::formatAnything($currency, '0', false),
'local_icon' => 'balance-scale',
'sub_title' => app('amount')->formatAnything($currency, '0', false)
.' + '.app('amount')->formatAnything($currency, '0', false),
'sub_title' => Amount::formatAnything($currency, '0', false)
.' + '.Amount::formatAnything($currency, '0', false),
];
$return[] = [
'key' => sprintf('spent-in-%s', $currency->code),
@@ -302,7 +302,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
'value_parsed' => Amount::formatAnything($currency, '0', false),
'local_icon' => 'balance-scale',
'sub_title' => '',
];
@@ -314,7 +314,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatAnything($currency, '0', false),
'value_parsed' => Amount::formatAnything($currency, '0', false),
'local_icon' => 'balance-scale',
'sub_title' => '',
];
@@ -405,7 +405,7 @@ class BasicController extends Controller
'currency_code' => $info['code'],
'currency_symbol' => $info['symbol'],
'currency_decimal_places' => $info['decimal_places'],
'value_parsed' => app('amount')->formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
'local_icon' => 'check',
'sub_title' => '',
];
@@ -424,7 +424,7 @@ class BasicController extends Controller
'currency_code' => $info['code'],
'currency_symbol' => $info['symbol'],
'currency_decimal_places' => $info['decimal_places'],
'value_parsed' => app('amount')->formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
'local_icon' => 'calendar-o',
'sub_title' => '',
];
@@ -443,7 +443,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
'local_icon' => 'check',
'sub_title' => '',
];
@@ -455,7 +455,7 @@ class BasicController extends Controller
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
'local_icon' => 'calendar-o',
'sub_title' => '',
];
@@ -493,14 +493,9 @@ class BasicController extends Controller
'currency_code' => $currencies[$currencyId]->code,
'currency_symbol' => $currencies[$currencyId]->symbol,
'currency_decimal_places' => $currencies[$currencyId]->decimal_places,
'value_parsed' => app('amount')->formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
'value_parsed' => Amount::formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
'local_icon' => 'money',
'sub_title' => app('amount')->formatFlat(
$currencies[$currencyId]->symbol,
$currencies[$currencyId]->decimal_places,
$availableBudget,
false
),
'sub_title' => Amount::formatFlat($currencies[$currencyId]->symbol, $currencies[$currencyId]->decimal_places, $availableBudget, false),
];
}
foreach ($spent as $row) {
@@ -529,18 +524,14 @@ class BasicController extends Controller
'currency_code' => $row['currency_code'],
'currency_symbol' => $row['currency_symbol'],
'currency_decimal_places' => $row['currency_decimal_places'],
'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false),
'value_parsed' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $leftToSpend, false),
'local_icon' => 'money',
'sub_title' => app('amount')->formatFlat(
$row['currency_symbol'],
$row['currency_decimal_places'],
$perDay,
false
),
'sub_title' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $perDay, false),
];
}
unset($leftToSpend);
if (0 === count($return)) {
$days = (int) $start->diffInDays($end, true) + 1;
// a small trick to get every expense in this period, regardless of budget.
$spent = $this->opsRepository->sumExpenses($start, $end, null, new Collection());
foreach ($spent as $row) {
@@ -563,14 +554,9 @@ class BasicController extends Controller
'currency_code' => $row['currency_code'],
'currency_symbol' => $row['currency_symbol'],
'currency_decimal_places' => $row['currency_decimal_places'],
'value_parsed' => app('amount')->formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $spentInCurrency, false),
'value_parsed' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $spentInCurrency, false),
'local_icon' => 'money',
'sub_title' => app('amount')->formatFlat(
$row['currency_symbol'],
$row['currency_decimal_places'],
$perDay,
false
),
'sub_title' => Amount::formatFlat($row['currency_symbol'], $row['currency_decimal_places'], $perDay, false),
];
}
@@ -587,9 +573,9 @@ class BasicController extends Controller
// 'currency_code' => $currency->code,
// 'currency_symbol' => $currency->symbol,
// 'currency_decimal_places' => $currency->decimal_places,
// 'value_parsed' => app('amount')->formatFlat($currency->symbol, $currency->decimal_places, '0', false),
// 'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
// 'local_icon' => 'money',
// 'sub_title' => app('amount')->formatFlat(
// 'sub_title' => Amount::formatFlat(
// $currency->symbol,
// $currency->decimal_places,
// '0',
@@ -642,7 +628,7 @@ class BasicController extends Controller
'currency_code' => $data['currency_code'],
'currency_symbol' => $data['currency_symbol'],
'currency_decimal_places' => $data['currency_decimal_places'],
'value_parsed' => app('amount')->formatFlat($data['currency_symbol'], $data['currency_decimal_places'], $data['balance'], false),
'value_parsed' => Amount::formatFlat($data['currency_symbol'], $data['currency_decimal_places'], $data['balance'], false),
'local_icon' => 'line-chart',
'sub_title' => '',
];
@@ -656,7 +642,7 @@ class BasicController extends Controller
'currency_code' => $this->primaryCurrency->code,
'currency_symbol' => $this->primaryCurrency->symbol,
'currency_decimal_places' => $this->primaryCurrency->decimal_places,
'value_parsed' => app('amount')->formatFlat($this->primaryCurrency->symbol, $this->primaryCurrency->decimal_places, '0', false),
'value_parsed' => Amount::formatFlat($this->primaryCurrency->symbol, $this->primaryCurrency->decimal_places, '0', false),
'local_icon' => 'line-chart',
'sub_title' => '',
];

View File

@@ -136,6 +136,7 @@ class ShowController extends Controller
->setAccounts(new Collection([$account]))
->setLimit($pageSize)
->setPage($page)
->withAttachmentInformation()
->withAPIInformation()
->setRange($start, $end)
;

View File

@@ -179,14 +179,6 @@ class IndexController extends Controller
$return[$accountId]['target'] = '0';
$return[$accountId]['to_save'] = '0';
}
// calculate new interesting fields:
// $return[$accountId]['left'] -= $array['current_amount'];
// $return[$accountId]['saved'] += $array['current_amount'];
// $return[$accountId]['target'] += $array['target_amount'];
// $return[$accountId]['to_save'] += ($array['target_amount'] - $array['current_amount']);
// $return['account_name'] = $account['name'];
}
}
@@ -202,7 +194,7 @@ class IndexController extends Controller
// loop all accounts in this piggy bank subtract the current amount from "left to save" in the $accounts array.
/** @var array $piggyAccount */
foreach ($piggyBank['accounts'] as $piggyAccount) {
$accountId = $piggyAccount['id'];
$accountId = $piggyAccount['account_id'];
if (array_key_exists($accountId, $accounts)) {
$accounts[$accountId]['left'] = bcsub((string) $accounts[$accountId]['left'], (string) $piggyAccount['current_amount']);
$accounts[$accountId]['saved'] = bcadd((string) $accounts[$accountId]['saved'], (string) $piggyAccount['current_amount']);

View File

@@ -34,7 +34,10 @@ use FireflyIII\Models\RecurrenceRepetition;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\Facades\ExpandedForm;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Transformers\RecurrenceTransformer;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
@@ -92,13 +95,20 @@ class EditController extends Controller
throw new FireflyException('This recurring transaction has no meta-data. You will have to delete it and recreate it. Sorry!');
}
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new RecurringEnrichment();
$enrichment->setUser($admin);
$recurrence = $enrichment->enrichSingle($recurrence);
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters(new ParameterBag());
$array = $transformer->transform($recurrence);
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
$bills = app('expandedform')->makeSelectListWithEmpty($this->billRepository->getActiveBills());
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
$bills = ExpandedForm::makeSelectListWithEmpty($this->billRepository->getActiveBills());
/** @var RecurrenceRepetition $repetition */
$repetition = $recurrence->recurrenceRepetitions()->first();

View File

@@ -29,7 +29,9 @@ use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Recurrence;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\Http\Controllers\GetConfigurationData;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Transformers\RecurrenceTransformer;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
@@ -85,6 +87,13 @@ class IndexController extends Controller
$total = $collection->count();
$recurrences = $collection->slice(($page - 1) * $pageSize, $pageSize);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new RecurringEnrichment();
$enrichment->setUser($admin);
$recurrences = $enrichment->enrich($recurrences);
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters(new ParameterBag());

View File

@@ -32,8 +32,10 @@ use FireflyIII\Models\Recurrence;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use FireflyIII\Support\Http\Controllers\GetConfigurationData;
use FireflyIII\Support\JsonApi\Enrichments\RecurringEnrichment;
use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\Transformers\RecurrenceTransformer;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\ParameterBag;
@@ -80,6 +82,13 @@ class ShowController extends Controller
{
$repos = app(AttachmentRepositoryInterface::class);
// enrich
/** @var User $admin */
$admin = auth()->user();
$enrichment = new RecurringEnrichment();
$enrichment->setUser($admin);
$recurrence = $enrichment->enrichSingle($recurrence);
/** @var RecurrenceTransformer $transformer */
$transformer = app(RecurrenceTransformer::class);
$transformer->setParameters(new ParameterBag());

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Report;
use FireflyIII\Support\Facades\Navigation;
use Throwable;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
@@ -290,11 +291,12 @@ class BudgetController extends Controller
$cache->addProperty('budget-period-report');
$cache->addProperty($accounts->pluck('id')->toArray());
if ($cache->has()) {
return $cache->get();
// return $cache->get();
}
$periods = app('navigation')->listOfPeriods($start, $end);
$keyFormat = app('navigation')->preferredCarbonFormat($start, $end);
$periods = Navigation::listOfPeriods($start, $end);
$keyFormat = Navigation::preferredCarbonFormat($start, $end);
// list expenses for budgets in account(s)
$expenses = $this->opsRepository->listExpenses($start, $end, $accounts);
@@ -303,6 +305,17 @@ class BudgetController extends Controller
foreach ($currency['budgets'] as $budget) {
$count = 0;
foreach ($budget['transaction_journals'] as $journal) {
// #10678
// skip transactions between two asset / liability accounts.
if (
in_array($journal['source_account_type'], config('firefly.valid_currency_account_types'), true)
&& in_array($journal['destination_account_type'], config('firefly.valid_currency_account_types'), true)
) {
continue;
}
++$count;
$key = sprintf('%d-%d', $budget['id'], $currency['currency_id']);
$dateKey = $journal['date']->format($keyFormat);

View File

@@ -270,6 +270,7 @@ class CreateController extends Controller
$data = $request->getRuleData();
$rule = $this->ruleRepos->store($data);
session()->flash('success_url', route('rules.select-transactions', [$rule->id]));
session()->flash('success', (string) trans('firefly.stored_new_rule', ['title' => $rule->title]));
app('preferences')->mark();

View File

@@ -252,6 +252,7 @@ class TagController extends Controller
$collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withAccountInformation()
->setTag($tag)->withBudgetInformation()->withCategoryInformation()
->withAttachmentInformation()
;
$groups = $collector->getPaginatedGroups();
$groups->setPath($path);
@@ -283,6 +284,7 @@ class TagController extends Controller
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withAccountInformation()
->setTag($tag)->withBudgetInformation()->withCategoryInformation()
->withAttachmentInformation()
;
$groups = $collector->getPaginatedGroups();
$groups->setPath($path);

View File

@@ -202,8 +202,11 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
'amount' => $amount,
'destination_account_id' => $journal['destination_account_id'],
'destination_account_name' => $journal['destination_account_name'],
'destination_account_type' => $journal['destination_account_type'],
'currency_id' => $journalCurrencyId,
'source_account_id' => $journal['source_account_id'],
'source_account_name' => $journal['source_account_name'],
'source_account_type' => $journal['source_account_type'],
'category_name' => $journal['category_name'],
'description' => $journal['description'],
'transaction_group_id' => $journal['transaction_group_id'],

View File

@@ -33,6 +33,7 @@ use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Location;
use FireflyIII\Models\Note;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Support\Facades\Amount;
@@ -41,6 +42,7 @@ use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Override;
@@ -51,37 +53,30 @@ use Override;
*/
class AccountEnrichment implements EnrichmentInterface
{
private array $ids;
private array $accountTypeIds;
private array $accountTypes;
private array $ids = [];
private array $accountTypeIds = [];
private array $accountTypes = [];
private Collection $collection;
private array $currencies;
private array $locations;
private array $meta;
private array $currencies = [];
private array $locations = [];
private array $meta = [];
private TransactionCurrency $primaryCurrency;
private array $notes;
private array $openingBalances;
private array $notes = [];
private array $openingBalances = [];
private User $user;
private UserGroup $userGroup;
private array $lastActivities;
private ?Carbon $date = null;
private bool $convertToPrimary = false;
private array $balances = [];
private array $lastActivities = [];
private ?Carbon $date = null;
private bool $convertToPrimary;
private array $balances = [];
private array $objectGroups = [];
private array $mappedObjects = [];
/**
* TODO The account enricher must do conversion from and to the primary currency.
*/
public function __construct()
{
$this->ids = [];
$this->openingBalances = [];
$this->currencies = [];
$this->accountTypeIds = [];
$this->accountTypes = [];
$this->meta = [];
$this->notes = [];
$this->lastActivities = [];
$this->locations = [];
$this->primaryCurrency = Amount::getPrimaryCurrency();
$this->convertToPrimary = Amount::convertToPrimary();
}
@@ -113,6 +108,7 @@ class AccountEnrichment implements EnrichmentInterface
$this->collectLastActivities();
$this->collectLocations();
$this->collectOpeningBalances();
$this->collectObjectGroups();
$this->collectBalances();
$this->appendCollectedData();
@@ -247,6 +243,9 @@ class AccountEnrichment implements EnrichmentInterface
'longitude' => null,
'zoom_level' => null,
],
'object_group_id' => null,
'object_group_order' => null,
'object_group_title' => null,
'opening_balance_date' => null,
'opening_balance_amount' => null,
'account_number' => null,
@@ -254,6 +253,14 @@ class AccountEnrichment implements EnrichmentInterface
'last_activity' => $this->lastActivities[$id] ?? null,
];
// add object group if available
if (array_key_exists($id, $this->mappedObjects)) {
$key = $this->mappedObjects[$id];
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
}
// if location, add location:
if (array_key_exists($id, $this->locations)) {
$meta['location'] = $this->locations[$id];
@@ -344,7 +351,29 @@ class AccountEnrichment implements EnrichmentInterface
private function collectBalances(): void
{
$this->balances = Steam::finalAccountsBalanceOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
$this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
}
private function collectObjectGroups(): void
{
$set = DB::table('object_groupables')
->whereIn('object_groupable_id', $this->ids)
->where('object_groupable_type', Account::class)
->get(['object_groupable_id', 'object_group_id'])
;
$ids = array_unique($set->pluck('object_group_id')->toArray());
foreach ($set as $entry) {
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
}
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
foreach ($groups as $group) {
$group['id'] = (int)$group['id'];
$group['order'] = (int)$group['order'];
$this->objectGroups[(int)$group['id']] = $group;
}
}
public function setDate(?Carbon $date): void

View File

@@ -43,7 +43,7 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
private User $user;
private UserGroup $userGroup;
private TransactionCurrency $primaryCurrency;
private bool $convertToPrimary = false;
private bool $convertToPrimary;
private array $ids = [];
private array $currencyIds = [];
private array $currencies = [];
@@ -157,7 +157,7 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
$currency = $this->currencies[$currencyId];
$meta = [
'currency' => $currency,
'spent_in_budgets' => $this->spentInsideBudgets[$id] ?? [],
'spent_in_budgets' => $this->spentInBudgets[$id] ?? [],
'pc_spent_in_budgets' => $this->pcSpentInBudgets[$id] ?? [],
'spent_outside_budgets' => $this->spentOutsideBudgets[$id] ?? [],
'pc_spent_outside_budgets' => $this->pcSpentOutsideBudgets[$id] ?? [],

View File

@@ -8,6 +8,7 @@ use Carbon\Carbon;
use FireflyIII\Models\AutoBudget;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Note;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
@@ -15,23 +16,26 @@ use FireflyIII\Support\Facades\Amount;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class BudgetEnrichment implements EnrichmentInterface
{
private Collection $collection;
private bool $convertToPrimary = true;
private bool $convertToPrimary;
private TransactionCurrency $primaryCurrency;
private User $user;
private UserGroup $userGroup;
private array $ids = [];
private array $notes = [];
private array $autoBudgets = [];
private array $currencies = [];
private ?Carbon $start = null;
private ?Carbon $end = null;
private array $spent = [];
private array $pcSpent = [];
private array $ids = [];
private array $notes = [];
private array $autoBudgets = [];
private array $currencies = [];
private ?Carbon $start = null;
private ?Carbon $end = null;
private array $spent = [];
private array $pcSpent = [];
private array $objectGroups = [];
private array $mappedObjects = [];
public function __construct()
{
@@ -46,6 +50,8 @@ class BudgetEnrichment implements EnrichmentInterface
$this->collectNotes();
$this->collectAutoBudgets();
$this->collectExpenses();
$this->collectObjectGroups();
$this->appendCollectedData();
return $this->collection;
@@ -98,12 +104,25 @@ class BudgetEnrichment implements EnrichmentInterface
$this->collection = $this->collection->map(function (Budget $item) {
$id = (int)$item->id;
$meta = [
'notes' => $this->notes[$id] ?? null,
'currency' => $this->currencies[$id] ?? null,
'auto_budget' => $this->autoBudgets[$id] ?? null,
'spent' => $this->spent[$id] ?? null,
'pc_spent' => $this->pcSpent[$id] ?? null,
'object_group_id' => null,
'object_group_order' => null,
'object_group_title' => null,
'notes' => $this->notes[$id] ?? null,
'currency' => $this->currencies[$id] ?? null,
'auto_budget' => $this->autoBudgets[$id] ?? null,
'spent' => $this->spent[$id] ?? null,
'pc_spent' => $this->pcSpent[$id] ?? null,
];
// add object group if available
if (array_key_exists($id, $this->mappedObjects)) {
$key = $this->mappedObjects[$id];
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
}
$item->meta = $meta;
return $item;
@@ -154,4 +173,26 @@ class BudgetEnrichment implements EnrichmentInterface
{
$this->start = $start;
}
private function collectObjectGroups(): void
{
$set = DB::table('object_groupables')
->whereIn('object_groupable_id', $this->ids)
->where('object_groupable_type', Budget::class)
->get(['object_groupable_id', 'object_group_id'])
;
$ids = array_unique($set->pluck('object_group_id')->toArray());
foreach ($set as $entry) {
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
}
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
foreach ($groups as $group) {
$group['id'] = (int)$group['id'];
$group['order'] = (int)$group['order'];
$this->objectGroups[(int)$group['id']] = $group;
}
}
}

View File

@@ -29,6 +29,8 @@ class BudgetLimitEnrichment implements EnrichmentInterface
private Collection $budgets;
private array $expenses = [];
private array $pcExpenses = [];
private array $currencyIds = [];
private array $currencies = [];
private bool $convertToPrimary = true;
private TransactionCurrency $primaryCurrency;
@@ -42,6 +44,7 @@ class BudgetLimitEnrichment implements EnrichmentInterface
{
$this->collection = $collection;
$this->collectIds();
$this->collectCurrencies();
$this->collectNotes();
$this->collectBudgets();
$this->appendCollectedData();
@@ -71,14 +74,19 @@ class BudgetLimitEnrichment implements EnrichmentInterface
private function collectIds(): void
{
$this->start = $this->collection->min('start_date');
$this->end = $this->collection->max('end_date');
$this->start = $this->collection->min('start_date');
$this->end = $this->collection->max('end_date');
/** @var BudgetLimit $limit */
foreach ($this->collection as $limit) {
$this->ids[] = (int)$limit->id;
$id = (int)$limit->id;
$this->ids[] = $id;
if (0 !== (int)$limit->transaction_currency_id) {
$this->currencyIds[$id] = (int)$limit->transaction_currency_id;
}
}
$this->ids = array_unique($this->ids);
$this->ids = array_unique($this->ids);
$this->currencyIds = array_unique($this->currencyIds);
}
private function collectNotes(): void
@@ -98,10 +106,15 @@ class BudgetLimitEnrichment implements EnrichmentInterface
{
$this->collection = $this->collection->map(function (BudgetLimit $item) {
$id = (int)$item->id;
$currencyId = (int)$item->transaction_currency_id;
if (0 === $currencyId) {
$currencyId = $this->primaryCurrency->id;
}
$meta = [
'notes' => $this->notes[$id] ?? null,
'spent' => $this->expenses[$id] ?? [],
'pc_spent' => $this->pcExpenses[$id] ?? [],
'currency' => $this->currencies[$currencyId],
];
$item->meta = $meta;
@@ -133,4 +146,13 @@ class BudgetLimitEnrichment implements EnrichmentInterface
}
}
}
private function collectCurrencies(): void
{
$this->currencies[$this->primaryCurrency->id] = $this->primaryCurrency;
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->whereNot('id', $this->primaryCurrency->id)->get();
foreach ($currencies as $currency) {
$this->currencies[(int)$currency->id] = $currency;
}
}
}

View File

@@ -25,15 +25,15 @@ class PiggyBankEnrichment implements EnrichmentInterface
private User $user;
private UserGroup $userGroup;
private Collection $collection;
private array $ids;
private array $currencyIds = [];
private array $currencies = [];
private array $accountIds = [];
private array $accountCurrencies = [];
private array $notes = [];
private array $mappedObjects = [];
private array $ids = [];
private array $currencyIds = [];
private array $currencies = [];
private array $accountIds = [];
// private array $accountCurrencies = [];
private array $notes = [];
private array $mappedObjects = [];
private TransactionCurrency $primaryCurrency;
private array $amounts = [];
private array $amounts = [];
public function __construct()
{
@@ -93,8 +93,8 @@ class PiggyBankEnrichment implements EnrichmentInterface
// collect accounts
$set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->ids)->get(['piggy_bank_id', 'account_id', 'current_amount', 'native_current_amount']);
foreach ($set as $item) {
$id = (int)$item->piggy_bank_id;
$accountId = (int)$item->account_id;
$id = (int)$item->piggy_bank_id;
$accountId = (int)$item->account_id;
$this->amounts[$id] ??= [];
if (!array_key_exists($id, $this->accountIds)) {
$this->accountIds[$id] = (int)$item->account_id;
@@ -105,8 +105,10 @@ class PiggyBankEnrichment implements EnrichmentInterface
'pc_current_amount' => '0',
];
}
$this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], $item->current_amount);
$this->amounts[$id][$accountId]['pc_current_amount'] = bcadd($this->amounts[$id][$accountId]['pc_current_amount'], $item->native_current_amount);
$this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], $item->current_amount);
if (null !== $this->amounts[$id][$accountId]['pc_current_amount'] && null !== $item->native_current_amount) {
$this->amounts[$id][$accountId]['pc_current_amount'] = bcadd($this->amounts[$id][$accountId]['pc_current_amount'], $item->native_current_amount);
}
}
// get account currency preference for ALL.
@@ -114,12 +116,12 @@ class PiggyBankEnrichment implements EnrichmentInterface
/** @var AccountMeta $item */
foreach ($set as $item) {
$accountId = (int)$item->account_id;
$currencyId = (int)$item->data;
$accountId = (int)$item->account_id;
$currencyId = (int)$item->data;
if (!array_key_exists($currencyId, $this->currencies)) {
$this->currencies[$currencyId] = TransactionCurrency::find($currencyId);
}
$this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
// $this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
}
// get account info.
@@ -154,8 +156,8 @@ class PiggyBankEnrichment implements EnrichmentInterface
'object_group_id' => null,
'object_group_order' => null,
'object_group_title' => null,
'current_amount' => null,
'pc_current_amount' => null,
'current_amount' => '0',
'pc_current_amount' => '0',
'target_amount' => null === $targetAmount ? null : Steam::bcround($targetAmount, $currency->decimal_places),
'pc_target_amount' => null === $item->native_target_amount ? null : Steam::bcround($item->native_target_amount, $this->primaryCurrency->decimal_places),
'left_to_save' => null,

View File

@@ -25,7 +25,7 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
private array $journalIds = [];
private array $groupIds = [];
private array $accountIds = [];
private array $piggybankIds = [];
private array $piggyBankIds = [];
private array $accountCurrencies = [];
private array $currencies = [];
// private bool $convertToPrimary = false;
@@ -72,7 +72,7 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
foreach ($this->collection as $event) {
$this->ids[] = (int)$event->id;
$this->journalIds[(int)$event->id] = (int)$event->transaction_journal_id;
$this->piggybankIds[(int)$event->id] = (int)$event->piggy_bank_id;
$this->piggyBankIds[(int)$event->id] = (int)$event->piggy_bank_id;
}
$this->ids = array_unique($this->ids);
// collect groups with journal info.
@@ -84,7 +84,7 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
}
// collect account info.
$set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->piggybankIds)->get(['piggy_bank_id', 'account_id']);
$set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->piggyBankIds)->get(['piggy_bank_id', 'account_id']);
foreach ($set as $item) {
$id = (int)$item->piggy_bank_id;
if (!array_key_exists($id, $this->accountIds)) {
@@ -93,7 +93,6 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
}
// get account currency preference for ALL.
// TODO This method does a find in a loop.
$set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
/** @var AccountMeta $item */

View File

@@ -173,7 +173,7 @@ class RecurringEnrichment implements EnrichmentInterface
$this->transactions[$id][$transactionId] = [
'id' => (string)$transactionId,
'recurrence_id' => $id,
// 'recurrence_id' => $id,
'transaction_currency_id' => (int)$transaction->transaction_currency_id,
'foreign_currency_id' => null === $transaction->foreign_currency_id ? null : (int)$transaction->foreign_currency_id,
'source_id' => (int)$transaction->source_id,
@@ -309,7 +309,7 @@ class RecurringEnrichment implements EnrichmentInterface
if (true === $this->convertToPrimary && $currencyId !== (int)$this->primaryCurrency->id) {
$pcAmount = $converter->convert($this->currencies[$currencyId], $this->primaryCurrency, today(), $transaction['amount']);
}
if (null !== $transaction['foreign_amount']) {
if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
$foreignCurrencyId = $transaction['foreign_currency_id'];
if ($foreignCurrencyId !== $this->primaryCurrency->id) {
$pcForeignAmount = $converter->convert($this->currencies[$foreignCurrencyId], $this->primaryCurrency, today(), $transaction['foreign_amount']);

View File

@@ -27,15 +27,15 @@ class SubscriptionEnrichment implements EnrichmentInterface
private User $user;
private UserGroup $userGroup;
private Collection $collection;
private bool $convertToPrimary = false;
private ?Carbon $start = null;
private ?Carbon $end = null;
private array $subscriptionIds = [];
private array $objectGroups = [];
private array $mappedObjects = [];
private array $paidDates = [];
private array $notes = [];
private array $payDates = [];
private bool $convertToPrimary;
private ?Carbon $start = null;
private ?Carbon $end = null;
private array $subscriptionIds = [];
private array $objectGroups = [];
private array $mappedObjects = [];
private array $paidDates = [];
private array $notes = [];
private array $payDates = [];
private TransactionCurrency $primaryCurrency;
private BillDateCalculator $calculator;

View File

@@ -45,26 +45,20 @@ use Override;
class TransactionGroupEnrichment implements EnrichmentInterface
{
private array $attachmentCount;
private array $attachmentCount = [];
private Collection $collection;
private readonly array $dateFields;
private array $journalIds;
private array $locations;
private array $metaData; // @phpstan-ignore-line
private array $notes; // @phpstan-ignore-line
private array $tags;
private array $journalIds = [];
private array $locations = [];
private array $metaData = [];
private array $notes = [];
private array $tags = [];
private User $user;
private readonly TransactionCurrency $primaryCurrency;
private UserGroup $userGroup;
public function __construct()
{
$this->notes = [];
$this->journalIds = [];
$this->tags = [];
$this->metaData = [];
$this->locations = [];
$this->attachmentCount = [];
$this->dateFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date'];
$this->primaryCurrency = Amount::getPrimaryCurrency();
}

View File

@@ -319,7 +319,7 @@ class Steam
return $balances;
}
public function finalAccountsBalanceOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
{
$result = [];
$convertToPrimary ??= Amount::convertToPrimary();
@@ -759,20 +759,24 @@ class Steam
private function getCurrencies(Collection $accounts): array
{
$currencies = [];
$accountCurrencies = [];
$accountPreferences = [];
$primary = Amount::getPrimaryCurrency();
$currencies = [];
$accountCurrencies = [];
$accountPreferences = [];
$primary = Amount::getPrimaryCurrency();
$currencies[$primary->id] = $primary;
$ids = $accounts->pluck('id')->toArray();
$result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get();
$ids = $accounts->pluck('id')->toArray();
$result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get();
/** @var AccountMeta $item */
foreach ($result as $item) {
$accountPreferences[(int)$item->account_id] = (int)$item->data;
$integer = (int)$item->data;
if (0 !== $integer) {
$accountPreferences[(int)$item->account_id] = $integer;
}
}
// collect those currencies.
$set = TransactionCurrency::whereIn('id', $accountPreferences)->get();
// collect those currencies, skip primary because we already have it.
$set = TransactionCurrency::whereIn('id', $accountPreferences)->where('id', '!=', $primary->id)->get();
foreach ($set as $item) {
$currencies[$item->id] = $item;
}
@@ -786,11 +790,11 @@ class Steam
$currencies[$currencyId] ??= $account->meta['currency'];
$accountCurrencies[$accountId] = $account->meta['currency'];
}
if (!$currencyPresent && !array_key_exists($account->id, $accountPreferences)) {
if (!$currencyPresent && !array_key_exists($accountId, $accountPreferences)) {
$accountCurrencies[$accountId] = $primary;
}
if (!$currencyPresent && array_key_exists($account->id, $accountPreferences)) {
$accountCurrencies[$account->id] = $currencies[$accountPreferences[$account->id]];
if (!$currencyPresent && array_key_exists($accountId, $accountPreferences)) {
$accountCurrencies[$accountId] = $currencies[$accountPreferences[$account->id]];
}
}

View File

@@ -108,7 +108,9 @@ class AccountTransformer extends AbstractTransformer
'type' => strtolower($accountType),
'account_role' => $accountRole,
// TODO object group
'object_group_id' => $account->meta['object_group_id'],
'object_group_order' => $account->meta['object_group_order'],
'object_group_title' => $account->meta['object_group_title'],
// currency information, structured for 6.3.0.
'object_has_currency_setting' => $hasCurrencySettings,

View File

@@ -55,7 +55,6 @@ class AttachmentTransformer extends AbstractTransformer
'updated_at' => $attachment->updated_at->toAtomString(),
'attachable_id' => (string) $attachment->attachable_id,
'attachable_type' => str_replace('FireflyIII\Models\\', '', $attachment->attachable_type),
'md5' => $attachment->md5,
'hash' => $attachment->md5,
'filename' => $attachment->filename,
'download_url' => route('api.v1.attachments.download', [$attachment->id]),

View File

@@ -93,7 +93,6 @@ class BillTransformer extends AbstractTransformer
'object_group_order' => $bill->meta['object_group_order'],
'object_group_title' => $bill->meta['object_group_title'],
'paid_dates' => $bill->meta['paid_dates'],
'pay_dates' => $bill->meta['pay_dates'],
'next_expected_match' => $bill->meta['nem']?->toAtomString(),

View File

@@ -64,10 +64,7 @@ class BudgetLimitTransformer extends AbstractTransformer
public function transform(BudgetLimit $budgetLimit): array
{
$currency = $budgetLimit->transactionCurrency;
if (null === $currency) {
$currency = $this->primaryCurrency;
}
$currency = $budgetLimit->meta['currency'];
$amount = Steam::bcround($budgetLimit->amount, $currency->decimal_places);
$pcAmount = null;
if ($this->convertToPrimary && $currency->id === $this->primaryCurrency->id) {

View File

@@ -86,8 +86,9 @@ class BudgetTransformer extends AbstractTransformer
'notes' => $budget->meta['notes'],
'auto_budget_type' => $abType,
'auto_budget_period' => $abPeriod,
// TODO object group
'object_group_id' => $budget->meta['object_group_id'],
'object_group_order' => $budget->meta['object_group_order'],
'object_group_title' => $budget->meta['object_group_title'],
// new currency settings.
'object_has_currency_setting' => null !== $budget->meta['currency'],
@@ -105,8 +106,8 @@ class BudgetTransformer extends AbstractTransformer
'auto_budget_amount' => $abAmount,
'pc_auto_budget_amount' => $abPrimary,
'spent' => $this->beautify($budget->meta['spent']),
'pc_spent' => $this->beautify($budget->meta['pc_spent']),
'spent' => null === $budget->meta['spent'] ? null : $this->beautify($budget->meta['spent']),
'pc_spent' => null === $budget->meta['pc_spent'] ? null : $this->beautify($budget->meta['pc_spent']),
'links' => [
[
'rel' => 'self',

View File

@@ -67,7 +67,7 @@ class RecurrenceTransformer extends AbstractTransformer
'nr_of_repetitions' => $reps,
'notes' => $recurrence->meta['notes'],
'repetitions' => $recurrence->meta['repetitions'],
'new_transactions' => $recurrence->meta['transactions'],
'transactions' => $recurrence->meta['transactions'],
'links' => [
[
'rel' => 'self',

31
composer.lock generated
View File

@@ -1879,16 +1879,16 @@
},
{
"name": "laravel/framework",
"version": "v12.21.0",
"version": "v12.22.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b"
"reference": "6ab00c913ef6ec6fad0bd506f7452c0bb9e792c3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
"reference": "ac8c4e73bf1b5387b709f7736d41427e6af1c93b",
"url": "https://api.github.com/repos/laravel/framework/zipball/6ab00c913ef6ec6fad0bd506f7452c0bb9e792c3",
"reference": "6ab00c913ef6ec6fad0bd506f7452c0bb9e792c3",
"shasum": ""
},
"require": {
@@ -2090,7 +2090,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-07-22T15:41:55+00:00"
"time": "2025-08-07T13:49:53+00:00"
},
{
"name": "laravel/passport",
@@ -3878,29 +3878,29 @@
},
{
"name": "nette/utils",
"version": "v4.0.7",
"version": "v4.0.8",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2"
"reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/e67c4061eb40b9c113b218214e42cb5a0dda28f2",
"reference": "e67c4061eb40b9c113b218214e42cb5a0dda28f2",
"url": "https://api.github.com/repos/nette/utils/zipball/c930ca4e3cf4f17dcfb03037703679d2396d2ede",
"reference": "c930ca4e3cf4f17dcfb03037703679d2396d2ede",
"shasum": ""
},
"require": {
"php": "8.0 - 8.4"
"php": "8.0 - 8.5"
},
"conflict": {
"nette/finder": "<3",
"nette/schema": "<1.2.2"
},
"require-dev": {
"jetbrains/phpstorm-attributes": "dev-master",
"jetbrains/phpstorm-attributes": "^1.2",
"nette/tester": "^2.5",
"phpstan/phpstan": "^1.0",
"phpstan/phpstan-nette": "^2.0@stable",
"tracy/tracy": "^2.9"
},
"suggest": {
@@ -3918,6 +3918,9 @@
}
},
"autoload": {
"psr-4": {
"Nette\\": "src"
},
"classmap": [
"src/"
]
@@ -3958,9 +3961,9 @@
],
"support": {
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v4.0.7"
"source": "https://github.com/nette/utils/tree/v4.0.8"
},
"time": "2025-06-03T04:55:08+00:00"
"time": "2025-08-06T21:43:34+00:00"
},
{
"name": "nunomaduro/collision",

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-08-06',
'build_time' => 1754505490,
'version' => 'develop/2025-08-08',
'build_time' => 1754630161,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 26,

20
package-lock.json generated
View File

@@ -5700,9 +5700,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.197",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.197.tgz",
"integrity": "sha512-m1xWB3g7vJ6asIFz+2pBUbq3uGmfmln1M9SSvBe4QIFWYrRHylP73zL/3nMjDmwz8V+1xAXQDfBd6+HPW0WvDQ==",
"version": "1.5.199",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.199.tgz",
"integrity": "sha512-3gl0S7zQd88kCAZRO/DnxtBKuhMO4h0EaQIN3YgZfV6+pW+5+bf2AdQeHNESCoaQqo/gjGVYEf2YM4O5HJQqpQ==",
"dev": true,
"license": "ISC"
},
@@ -7838,9 +7838,9 @@
}
},
"node_modules/launch-editor": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.0.tgz",
"integrity": "sha512-R/PIF14L6e2eHkhvQPu7jDRCr0msfCYCxbYiLgkkAGi0dVPWuM+RrsPu0a5dpuNe0KWGL3jpAkOlv53xGfPheQ==",
"version": "2.11.1",
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz",
"integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11524,9 +11524,9 @@
}
},
"node_modules/vite": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz",
"integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.1.tgz",
"integrity": "sha512-yJ+Mp7OyV+4S+afWo+QyoL9jFWD11QFH0i5i7JypnfTcA1rmgxCbiA8WwAICDEtZ1Z1hzrVhN8R8rGTqkTY8ZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -11534,7 +11534,7 @@
"fdir": "^6.4.6",
"picomatch": "^4.0.3",
"postcss": "^8.5.6",
"rollup": "^4.40.0",
"rollup": "^4.43.0",
"tinyglobby": "^0.2.14"
},
"bin": {

View File

@@ -56,8 +56,12 @@
<div class="input-group-text"> <em class="fa-solid fa-user"></em> </div>
</div>
@endif
<div class="input-group mb-3"> <input type="password" name="password" class="form-control" placeholder="{{ trans('form.password') }}" @if(true===$IS_DEMO_SITE)value="{{ $DEMO_PASSWORD }}"@endif autocomplete="current-password">
<div class="input-group-text"> <em class="fa-solid fa-lock"></em> </div>
<div class="input-group mb-3">
<input type="password" id="password" name="password" class="form-control" placeholder="{{ trans('form.password') }}" @if(true===$IS_DEMO_SITE)value="{{ $DEMO_PASSWORD }}"@endif autocomplete="current-password">
<div class="input-group-text">
<em class="fa-solid fa-lock"></em>
<i class="fa-solid fa-eye-slash fa-eye" id="togglePassword"></i>
</div>
</div> <!--begin::Row-->
<div class="row">
<div class="col-8">
@@ -80,3 +84,21 @@
</div>
@endsection
@section('scripts')
<script nonce="{{ $JS_NONCE }}">
const togglePassword = document.querySelector('#togglePassword');
const password = document.querySelector('#password');
togglePassword.addEventListener('click', () => {
const type = password.getAttribute('type') === 'password' ? 'text' : 'password';
if('text' === type) {
togglePassword.classList.add('fa-eye');
togglePassword.classList.remove('fa-eye-slash');
}
if('password' === type) {
togglePassword.classList.add('fa-eye-slash');
togglePassword.classList.remove('fa-eye');
}
password.setAttribute('type', type);
});
</script>
@endsection

View File

@@ -346,9 +346,11 @@
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li><a href="{{ route('transactions.edit', [group.id]) }}"><span
class="fa fa-fw fa-pencil"></span> {{ 'edit'|_ }}</a></li>
{% if transaction.transaction_type_type != 'Reconciliation' and transaction.transaction_type_type != 'Opening balance' and transaction.transaction_type_type != 'Liability credit' %}
{% if transaction.transaction_type_type != 'Opening balance' and transaction.transaction_type_type != 'Liability credit' %}
<li><a href="{{ route('transactions.delete', [group.id]) }}"><span
class="fa fa-fw fa-trash"></span> {{ 'delete'|_ }}</a></li>
{% endif %}
{% if transaction.transaction_type_type != 'Reconciliation' and transaction.transaction_type_type != 'Opening balance' and transaction.transaction_type_type != 'Liability credit' %}
<li><a href="#" data-id="{{ group.id }}" class="clone-transaction"><span
class="fa fa-copy fa-fw"></span> {{ 'clone'|_ }}</a></li>
<li><a href="#" data-id="{{ group.id }}" class="clone-transaction-and-edit"><span

View File

@@ -6,12 +6,15 @@
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<p>
<div class="btn-group">
<a href="{{ route('rule-groups.create') }}" id="new_rule_group" class="btn btn-success">{{ 'new_rule_group'|_ }}</a>
</p>
<a href="{{ route('rules.create') }}" class="btn btn-success new_rule">{{ 'new_rule'|_ }}</a>
</div>
<p></p>
</div>
</div>
{% if ruleGroups|length == 1 and ruleGroups[0].rules.count() == 0 %}
{% include 'partials.empty' with {objectType: 'default', type: 'rules',route: route('rules.create')} %}
{# make FF ignore demo for now. #}
@@ -62,6 +65,10 @@
<p>
<em>{{ ruleGroup.description }}</em>
</p>
<p>
<a href="{{ route('rules.create', ruleGroup.id) }}"
class="btn btn-success new_rule">{{ 'new_rule'|_ }}</a>
</p>
{% if ruleGroup.rules.count() > 0 %}
<table class="table table-hover table-striped group-rules">