mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-12-01 10:31:59 +00:00
Compare commits
73 Commits
develop-20
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f827fb277 | ||
|
|
8e700944fd | ||
|
|
037a128942 | ||
|
|
ca364dc877 | ||
|
|
e55af7186c | ||
|
|
354bbebbee | ||
|
|
a70fab1e87 | ||
|
|
b9c93091cd | ||
|
|
4349f8f303 | ||
|
|
5088e20f25 | ||
|
|
3f81aa7403 | ||
|
|
8885d1dbeb | ||
|
|
a6ba75d528 | ||
|
|
667cbb1332 | ||
|
|
d1bae875f7 | ||
|
|
c5cf529413 | ||
|
|
62b9f2785f | ||
|
|
3b85f87502 | ||
|
|
87d3d14504 | ||
|
|
5637573fd0 | ||
|
|
48255ae6ee | ||
|
|
ea02986170 | ||
|
|
f4fbc15ac6 | ||
|
|
a02c4b42a4 | ||
|
|
1ff47441ce | ||
|
|
2cc8568077 | ||
|
|
408687ec6b | ||
|
|
308abffb0b | ||
|
|
6743b3fe83 | ||
|
|
ac0113e445 | ||
|
|
0f0a28c3d9 | ||
|
|
79f2d70211 | ||
|
|
8a06c0f7ec | ||
|
|
c54da62005 | ||
|
|
e5923202af | ||
|
|
eb6f78406e | ||
|
|
d61f87f649 | ||
|
|
33dcce7525 | ||
|
|
b1d86c3a37 | ||
|
|
822dee6e70 | ||
|
|
f6dac83777 | ||
|
|
d3c557ca22 | ||
|
|
853a99852e | ||
|
|
8f24ac4fcd | ||
|
|
8b09cfb8c9 | ||
|
|
18ae950d2e | ||
|
|
69dfbda847 | ||
|
|
4ec2fcdb8a | ||
|
|
08879d31ba | ||
|
|
66d09450d3 | ||
|
|
74f7c07a76 | ||
|
|
ae3d0a3e49 | ||
|
|
61e8d7d7a2 | ||
|
|
62c5440605 | ||
|
|
0aa90b9453 | ||
|
|
855bc2f8e7 | ||
|
|
d8f05492c3 | ||
|
|
4a264f34fa | ||
|
|
5a1413e758 | ||
|
|
84dbeeb0ce | ||
|
|
d868dc0945 | ||
|
|
beecf9c229 | ||
|
|
e39ba46398 | ||
|
|
e6b6a3cee5 | ||
|
|
b5483f6ad3 | ||
|
|
a751218d53 | ||
|
|
2af5e6eeef | ||
|
|
013c43f9f2 | ||
|
|
7e08a1f33c | ||
|
|
e592b56d7a | ||
|
|
a2479f71fe | ||
|
|
7d3b993b98 | ||
|
|
90623101a3 |
137
.ci/php-cs-fixer/composer.lock
generated
137
.ci/php-cs-fixer/composer.lock
generated
@@ -402,16 +402,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.87.2",
|
||||
"version": "v3.88.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992"
|
||||
"reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992",
|
||||
"reference": "da5f0a7858c79b56fc0b8c36d3efcfe5f37f0992",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a8d15584bafb0f0d9d938827840060fd4a3ebc99",
|
||||
"reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -438,12 +438,13 @@
|
||||
"symfony/polyfill-mbstring": "^1.33",
|
||||
"symfony/polyfill-php80": "^1.33",
|
||||
"symfony/polyfill-php81": "^1.33",
|
||||
"symfony/polyfill-php84": "^1.33",
|
||||
"symfony/process": "^5.4.47 || ^6.4.24 || ^7.2",
|
||||
"symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"facile-it/paraunit": "^1.3.1 || ^2.7",
|
||||
"infection/infection": "^0.29.14",
|
||||
"infection/infection": "^0.31.0",
|
||||
"justinrainbow/json-schema": "^6.5",
|
||||
"keradus/cli-executor": "^2.2",
|
||||
"mikey179/vfsstream": "^1.6.12",
|
||||
@@ -451,7 +452,6 @@
|
||||
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
|
||||
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
|
||||
"phpunit/phpunit": "^9.6.25 || ^10.5.53 || ^11.5.34",
|
||||
"symfony/polyfill-php84": "^1.33",
|
||||
"symfony/var-dumper": "^5.4.48 || ^6.4.24 || ^7.3.2",
|
||||
"symfony/yaml": "^5.4.45 || ^6.4.24 || ^7.3.2"
|
||||
},
|
||||
@@ -494,7 +494,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.87.2"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.88.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -502,7 +502,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-10T09:51:40+00:00"
|
||||
"time": "2025-09-27T00:24:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -1252,16 +1252,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7"
|
||||
"reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
|
||||
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
|
||||
"reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1326,7 +1326,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1346,7 +1346,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-25T06:35:40+00:00"
|
||||
"time": "2025-09-22T15:31:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -2284,17 +2284,97 @@
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.3.3",
|
||||
"name": "symfony/polyfill-php84",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1"
|
||||
"url": "https://github.com/symfony/polyfill-php84.git",
|
||||
"reference": "d8ced4d875142b6a7426000426b8abc631d6b191"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1",
|
||||
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191",
|
||||
"reference": "d8ced4d875142b6a7426000426b8abc631d6b191",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php84\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-06-24T13:30:11+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/f24f8f316367b30810810d4eb30c543d7003ff3b",
|
||||
"reference": "f24f8f316367b30810810d4eb30c543d7003ff3b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2326,7 +2406,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/process/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2346,7 +2426,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-18T09:42:54+00:00"
|
||||
"time": "2025-09-11T10:12:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
@@ -2495,16 +2575,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v7.3.3",
|
||||
"version": "v7.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c"
|
||||
"reference": "f96476035142921000338bad71e5247fbc138872"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
|
||||
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/f96476035142921000338bad71e5247fbc138872",
|
||||
"reference": "f96476035142921000338bad71e5247fbc138872",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2519,7 +2599,6 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/emoji": "^7.1",
|
||||
"symfony/error-handler": "^6.4|^7.0",
|
||||
"symfony/http-client": "^6.4|^7.0",
|
||||
"symfony/intl": "^6.4|^7.0",
|
||||
"symfony/translation-contracts": "^2.5|^3.0",
|
||||
@@ -2562,7 +2641,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v7.3.3"
|
||||
"source": "https://github.com/symfony/string/tree/v7.3.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2582,7 +2661,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-08-25T06:35:40+00:00"
|
||||
"time": "2025-09-11T14:36:48+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
||||
@@ -4,6 +4,7 @@ Over time, many people have contributed to Firefly III. Their efforts are not al
|
||||
Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution.
|
||||
|
||||
## 2025
|
||||
- Nicky De Maeyer
|
||||
- Denis Iskandarov
|
||||
- =
|
||||
- Lompi
|
||||
|
||||
@@ -28,8 +28,10 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
/**
|
||||
@@ -96,6 +98,7 @@ class PiggyBankController extends Controller
|
||||
|
||||
/** @var PiggyBank $piggy */
|
||||
foreach ($piggies as $piggy) {
|
||||
/** @var TransactionCurrency $currency */
|
||||
$currency = $piggy->transactionCurrency;
|
||||
$currentAmount = $this->piggyRepository->getCurrentAmount($piggy);
|
||||
$objectGroup = $piggy->objectGroups()->first();
|
||||
@@ -105,8 +108,8 @@ class PiggyBankController extends Controller
|
||||
'name_with_balance' => sprintf(
|
||||
'%s (%s / %s)',
|
||||
$piggy->name,
|
||||
app('amount')->formatAnything($currency, $currentAmount, false),
|
||||
app('amount')->formatAnything($currency, $piggy->target_amount, false),
|
||||
Amount::formatAnything($currency, $currentAmount, false),
|
||||
Amount::formatAnything($currency, $piggy->target_amount, false),
|
||||
),
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_name' => $currency->name,
|
||||
|
||||
@@ -72,7 +72,7 @@ class DestroyController extends Controller
|
||||
public function destroySingleByDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
|
||||
{
|
||||
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if (null !== $exchangeRate) {
|
||||
if ($exchangeRate instanceof CurrencyExchangeRate) {
|
||||
$this->repository->deleteRate($exchangeRate);
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ class ShowController extends Controller
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if (null === $exchangeRate) {
|
||||
if (!$exchangeRate instanceof CurrencyExchangeRate) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ class UpdateController extends Controller
|
||||
public function updateByDate(UpdateRequest $request, TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
|
||||
{
|
||||
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if (null === $exchangeRate) {
|
||||
if (!$exchangeRate instanceof CurrencyExchangeRate) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
$date = $request->getDate();
|
||||
|
||||
@@ -68,10 +68,6 @@ class UpdateController extends Controller
|
||||
$data = $request->getAll();
|
||||
$piggyBank = $this->repository->update($piggyBank, $data);
|
||||
|
||||
if (array_key_exists('current_amount', $data) && '' !== $data['current_amount']) {
|
||||
$this->repository->setCurrentAmount($piggyBank, $data['current_amount']);
|
||||
}
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
|
||||
@@ -158,18 +158,23 @@ class ShowController extends Controller
|
||||
Log::debug(sprintf('Now in triggerTransaction(%d, %d)', $webhook->id, $group->id));
|
||||
Log::channel('audit')->info(sprintf('User triggers webhook #%d on transaction group #%d.', $webhook->id, $group->id));
|
||||
|
||||
/** @var MessageGeneratorInterface $engine */
|
||||
$engine = app(MessageGeneratorInterface::class);
|
||||
$engine->setUser(auth()->user());
|
||||
|
||||
// tell the generator which trigger it should look for
|
||||
$engine->setTrigger(WebhookTrigger::tryFrom($webhook->trigger));
|
||||
// tell the generator which objects to process
|
||||
$engine->setObjects(new Collection()->push($group));
|
||||
// set the webhook to trigger
|
||||
$engine->setWebhooks(new Collection()->push($webhook));
|
||||
// tell the generator to generate the messages
|
||||
$engine->generateMessages();
|
||||
/** @var \FireflyIII\Models\WebhookTrigger $trigger */
|
||||
foreach ($webhook->webhookTriggers as $trigger) {
|
||||
/** @var MessageGeneratorInterface $engine */
|
||||
$engine = app(MessageGeneratorInterface::class);
|
||||
$engine->setUser(auth()->user());
|
||||
|
||||
// tell the generator which trigger it should look for
|
||||
$engine->setTrigger(WebhookTrigger::tryFrom((int)$trigger->key));
|
||||
// tell the generator which objects to process
|
||||
$engine->setObjects(new Collection()->push($group));
|
||||
// set the webhook to trigger
|
||||
$engine->setWebhooks(new Collection()->push($webhook));
|
||||
// tell the generator to generate the messages
|
||||
$engine->generateMessages();
|
||||
}
|
||||
|
||||
|
||||
// trigger event to send them:
|
||||
Log::debug('send event RequestedSendWebhookMessages from ShowController::triggerTransaction()');
|
||||
|
||||
@@ -44,8 +44,18 @@ class DateRequest extends FormRequest
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
$start = $this->getCarbonDate('start');
|
||||
$end = $this->getCarbonDate('end');
|
||||
$start = $this->getCarbonDate('start');
|
||||
$end = $this->getCarbonDate('end');
|
||||
if (null === $start) {
|
||||
$start = now()->startOfMonth();
|
||||
}
|
||||
if (null === $end) {
|
||||
$end = now()->endOfMonth();
|
||||
}
|
||||
// sanity check on dates:
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
if ($start->diffInYears($end, true) > 5) {
|
||||
|
||||
@@ -64,7 +64,7 @@ class UpdateRequest extends FormRequest
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$this->integerFields = ['order', 'currency_id', 'foreign_currency_id', 'transaction_journal_id', 'source_id', 'destination_id', 'budget_id', 'category_id', 'bill_id', 'recurrence_id'];
|
||||
$this->dateFields = ['date', 'interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date'];
|
||||
$this->textareaFields = ['notes'];
|
||||
@@ -97,7 +97,7 @@ class UpdateRequest extends FormRequest
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$return = [];
|
||||
|
||||
/** @var null|array $transactions */
|
||||
@@ -181,7 +181,7 @@ class UpdateRequest extends FormRequest
|
||||
private function getDateData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->dateFields as $fieldName) {
|
||||
app('log')->debug(sprintf('Now at date field %s', $fieldName));
|
||||
Log::debug(sprintf('Now at date field %s', $fieldName));
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
Log::debug(sprintf('New value: "%s"', $transaction[$fieldName]));
|
||||
$current[$fieldName] = $this->dateFromValue((string) $transaction[$fieldName]);
|
||||
@@ -247,7 +247,7 @@ class UpdateRequest extends FormRequest
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$validProtocols = config('firefly.valid_url_protocols');
|
||||
|
||||
return [
|
||||
@@ -330,7 +330,7 @@ class UpdateRequest extends FormRequest
|
||||
*/
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
app('log')->debug('Now in withValidator');
|
||||
Log::debug('Now in withValidator');
|
||||
|
||||
/** @var TransactionGroup $transactionGroup */
|
||||
$transactionGroup = $this->route()->parameter('transactionGroup');
|
||||
|
||||
@@ -28,7 +28,6 @@ namespace FireflyIII\Casts;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class SeparateTimezoneCaster
|
||||
|
||||
@@ -26,12 +26,14 @@ namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Support\System\GeneratesInstallationId;
|
||||
use FireflyIII\Support\System\IsOldVersion;
|
||||
use Illuminate\Console\Command;
|
||||
use Random\RandomException;
|
||||
|
||||
class OutputsInstructions extends Command
|
||||
{
|
||||
use GeneratesInstallationId;
|
||||
use IsOldVersion;
|
||||
|
||||
protected $description = 'Instructions in case of upgrade trouble.';
|
||||
|
||||
@@ -58,7 +60,7 @@ class OutputsInstructions extends Command
|
||||
*/
|
||||
private function updateInstructions(): void
|
||||
{
|
||||
$version = (string) config('firefly.version');
|
||||
$version = (string)config('firefly.version');
|
||||
|
||||
/** @var array $config */
|
||||
$config = config('upgrade.text.upgrade');
|
||||
@@ -68,12 +70,12 @@ class OutputsInstructions extends Command
|
||||
foreach (array_keys($config) as $compare) {
|
||||
// if string starts with:
|
||||
if (str_starts_with($version, $compare)) {
|
||||
$text = (string) $config[$compare];
|
||||
$text = (string)$config[$compare];
|
||||
}
|
||||
}
|
||||
|
||||
// validate some settings.
|
||||
if ('' === $text && 'local' === (string) config('app.env')) {
|
||||
if ('' === $text && 'local' === (string)config('app.env')) {
|
||||
$text = 'Please set APP_ENV=production for a safer environment.';
|
||||
}
|
||||
|
||||
@@ -191,7 +193,7 @@ class OutputsInstructions extends Command
|
||||
*/
|
||||
private function installInstructions(): void
|
||||
{
|
||||
$version = (string) config('firefly.version');
|
||||
$version = (string)config('firefly.version');
|
||||
|
||||
/** @var array $config */
|
||||
$config = config('upgrade.text.install');
|
||||
@@ -201,12 +203,12 @@ class OutputsInstructions extends Command
|
||||
foreach (array_keys($config) as $compare) {
|
||||
// if string starts with:
|
||||
if (str_starts_with($version, $compare)) {
|
||||
$text = (string) $config[$compare];
|
||||
$text = (string)$config[$compare];
|
||||
}
|
||||
}
|
||||
|
||||
// validate some settings.
|
||||
if ('' === $text && 'local' === (string) config('app.env')) {
|
||||
if ('' === $text && 'local' === (string)config('app.env')) {
|
||||
$text = 'Please set APP_ENV=production for a safer environment.';
|
||||
}
|
||||
|
||||
@@ -242,14 +244,15 @@ class OutputsInstructions extends Command
|
||||
|
||||
private function someQuote(): void
|
||||
{
|
||||
$lines = [
|
||||
'Forgive yourself for not being at peace.',
|
||||
'Doesn\'t look like anything to me.',
|
||||
'Be proud of what you make.',
|
||||
'Be there or forever wonder.',
|
||||
'A year from now you will wish you had started today.',
|
||||
$lines = [
|
||||
'"Forgive yourself for not being at peace."',
|
||||
'"Doesn\'t look like anything to me."',
|
||||
'"Be proud of what you make."',
|
||||
'"Be there or forever wonder."',
|
||||
'"A year from now you will wish you had started today."',
|
||||
'🇺🇦 Слава Україні!',
|
||||
'🇺🇦 Slava Ukraini!',
|
||||
];
|
||||
$addQuotes = true;
|
||||
|
||||
// fuck the Russian aggression in Ukraine.
|
||||
|
||||
@@ -260,8 +263,7 @@ class OutputsInstructions extends Command
|
||||
// going on, to allow that to happen.
|
||||
|
||||
if ('ru_RU' === config('firefly.default_language')) {
|
||||
$addQuotes = false;
|
||||
$lines = [
|
||||
$lines = [
|
||||
'🇺🇦 Слава Україні!',
|
||||
'🇺🇦 Slava Ukraini!',
|
||||
];
|
||||
@@ -272,11 +274,6 @@ class OutputsInstructions extends Command
|
||||
} catch (RandomException) {
|
||||
$random = 0;
|
||||
}
|
||||
if ($addQuotes) {
|
||||
$this->line(sprintf(' "%s"', $lines[$random]));
|
||||
|
||||
return;
|
||||
}
|
||||
$this->line(sprintf(' %s', $lines[$random]));
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SetsLatestVersion extends Command
|
||||
@@ -45,8 +46,7 @@ class SetsLatestVersion extends Command
|
||||
|
||||
return 0;
|
||||
}
|
||||
app('fireflyconfig')->set('db_version', config('firefly.db_version'));
|
||||
app('fireflyconfig')->set('ff3_version', config('firefly.version'));
|
||||
FireflyConfig::set('ff3_version', config('firefly.version'));
|
||||
$this->friendlyInfo('Updated version.');
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -283,7 +283,7 @@ class ApplyRules extends Command
|
||||
if (null !== $endString && '' !== $endString) {
|
||||
$inputEnd = Carbon::createFromFormat('Y-m-d', $endString);
|
||||
}
|
||||
if (null === $inputEnd || null === $inputStart) {
|
||||
if (!$inputEnd instanceof Carbon || null === $inputStart) {
|
||||
Log::error('Could not parse start or end date in verifyInputDate().');
|
||||
|
||||
return;
|
||||
|
||||
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\Upgrade;
|
||||
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Safe\Exceptions\InfoException;
|
||||
|
||||
@@ -86,10 +87,8 @@ class UpgradesDatabase extends Command
|
||||
$this->friendlyLine(sprintf('Now executing %s', $command));
|
||||
$this->call($command, $args);
|
||||
}
|
||||
// set new DB version.
|
||||
app('fireflyconfig')->set('db_version', (int) config('firefly.db_version'));
|
||||
// index will set FF3 version.
|
||||
app('fireflyconfig')->set('ff3_version', (string) config('firefly.version'));
|
||||
FireflyConfig::set('ff3_version', (string) config('firefly.version'));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
return $this->handleAttachment($request, $e);
|
||||
|
||||
case 'bills.show':
|
||||
case 'subscriptions.show':
|
||||
$request->session()->reflash();
|
||||
|
||||
return redirect(route('bills.index'));
|
||||
|
||||
@@ -242,6 +242,7 @@ class PiggyBankFactory
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::debug('Looping all accounts.');
|
||||
|
||||
/** @var array $info */
|
||||
foreach ($accounts as $info) {
|
||||
@@ -251,6 +252,7 @@ class PiggyBankFactory
|
||||
|
||||
continue;
|
||||
}
|
||||
Log::debug(sprintf('Working on account #%d', $account->id));
|
||||
if (array_key_exists('current_amount', $info) && null !== $info['current_amount']) {
|
||||
// an amount is set, first check out if there is a difference with the previous amount.
|
||||
$previous = $toBeLinked[$account->id]['current_amount'] ?? '0';
|
||||
@@ -258,22 +260,24 @@ class PiggyBankFactory
|
||||
|
||||
// create event for difference.
|
||||
if (0 !== bccomp($diff, '0')) {
|
||||
// 2025-10-01 for issue #10990 disable this event.
|
||||
Log::debug(sprintf('[a] Will save event for difference %s (previous value was %s)', $diff, $previous));
|
||||
event(new ChangedAmount($piggyBank, $diff, null, null));
|
||||
// event(new ChangedAmount($piggyBank, $diff, null, null));
|
||||
}
|
||||
|
||||
$toBeLinked[$account->id] = ['current_amount' => $info['current_amount']];
|
||||
Log::debug(sprintf('[a] Will link account #%d with amount %s', $account->id, $info['current_amount']));
|
||||
}
|
||||
if (array_key_exists('current_amount', $info) && null === $info['current_amount']) {
|
||||
// an amount is set, first check out if there is a difference with the previous amount.
|
||||
// no amount is set, first check out if there is a difference with the previous amount.
|
||||
$previous = $toBeLinked[$account->id]['current_amount'] ?? '0';
|
||||
$diff = bcsub('0', $previous);
|
||||
|
||||
// create event for difference.
|
||||
if (0 !== bccomp($diff, '0')) {
|
||||
// 2025-10-01 for issue #10990 disable this event.
|
||||
Log::debug(sprintf('[b] Will save event for difference %s (previous value was %s)', $diff, $previous));
|
||||
event(new ChangedAmount($piggyBank, $diff, null, null));
|
||||
// event(new ChangedAmount($piggyBank, $diff, null, null));
|
||||
}
|
||||
|
||||
// no amount set, use previous amount or go to ZERO.
|
||||
@@ -282,7 +286,8 @@ class PiggyBankFactory
|
||||
|
||||
// create event:
|
||||
Log::debug('linkToAccountIds: Trigger change for positive amount [b].');
|
||||
event(new ChangedAmount($piggyBank, $toBeLinked[$account->id]['current_amount'] ?? '0', null, null));
|
||||
// 2025-10-01 for issue #10990 disable this event.
|
||||
// event(new ChangedAmount($piggyBank, $toBeLinked[$account->id]['current_amount'] ?? '0', null, null));
|
||||
}
|
||||
if (!array_key_exists('current_amount', $info)) {
|
||||
$toBeLinked[$account->id] ??= [];
|
||||
|
||||
@@ -28,6 +28,7 @@ use FireflyIII\Events\RequestedSendWebhookMessages;
|
||||
use FireflyIII\Events\StoredTransactionGroup;
|
||||
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
|
||||
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
|
||||
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
|
||||
@@ -36,6 +37,8 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class StoredGroupEventHandler
|
||||
*
|
||||
* TODO migrate to observer?
|
||||
*/
|
||||
class StoredGroupEventHandler
|
||||
{
|
||||
@@ -44,6 +47,7 @@ class StoredGroupEventHandler
|
||||
$this->processRules($event);
|
||||
$this->recalculateCredit($event);
|
||||
$this->triggerWebhooks($event);
|
||||
$this->removePeriodStatistics($event);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,6 +98,38 @@ class StoredGroupEventHandler
|
||||
$object->recalculate();
|
||||
}
|
||||
|
||||
private function removePeriodStatistics(StoredTransactionGroup $event): void
|
||||
{
|
||||
/** @var PeriodStatisticRepositoryInterface $repository */
|
||||
$repository = app(PeriodStatisticRepositoryInterface::class);
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($event->transactionGroup->transactionJournals as $journal) {
|
||||
$source = $journal->transactions()->where('amount', '<', '0')->first();
|
||||
$dest = $journal->transactions()->where('amount', '>', '0')->first();
|
||||
$repository->deleteStatisticsForModel($source->account, $journal->date);
|
||||
$repository->deleteStatisticsForModel($dest->account, $journal->date);
|
||||
$categories = $journal->categories;
|
||||
$tags = $journal->tags;
|
||||
$budgets = $journal->budgets;
|
||||
foreach ($categories as $category) {
|
||||
$repository->deleteStatisticsForModel($category, $journal->date);
|
||||
}
|
||||
foreach ($tags as $tag) {
|
||||
$repository->deleteStatisticsForModel($tag, $journal->date);
|
||||
}
|
||||
foreach ($budgets as $budget) {
|
||||
$repository->deleteStatisticsForModel($budget, $journal->date);
|
||||
}
|
||||
if (0 === $categories->count()) {
|
||||
$repository->deleteStatisticsForPrefix($journal->userGroup, 'no_category', $journal->date);
|
||||
}
|
||||
if (0 === $budgets->count()) {
|
||||
$repository->deleteStatisticsForPrefix($journal->userGroup, 'no_budget', $journal->date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method processes all webhooks that respond to the "stored transaction group" trigger (100)
|
||||
*/
|
||||
|
||||
@@ -31,6 +31,7 @@ use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
|
||||
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
|
||||
use FireflyIII\Support\Models\AccountBalanceCalculator;
|
||||
@@ -49,10 +50,35 @@ class UpdatedGroupEventHandler
|
||||
$this->processRules($event);
|
||||
$this->recalculateCredit($event);
|
||||
$this->triggerWebhooks($event);
|
||||
$this->removePeriodStatistics($event);
|
||||
if ($event->runRecalculations) {
|
||||
$this->updateRunningBalance($event);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO duplicate
|
||||
*/
|
||||
private function removePeriodStatistics(UpdatedTransactionGroup $event): void
|
||||
{
|
||||
/** @var PeriodStatisticRepositoryInterface $repository */
|
||||
$repository = app(PeriodStatisticRepositoryInterface::class);
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($event->transactionGroup->transactionJournals as $journal) {
|
||||
$source = $journal->transactions()->where('amount', '<', '0')->first();
|
||||
$dest = $journal->transactions()->where('amount', '>', '0')->first();
|
||||
$repository->deleteStatisticsForModel($source->account, $journal->date);
|
||||
$repository->deleteStatisticsForModel($dest->account, $journal->date);
|
||||
foreach ($journal->categories as $category) {
|
||||
$repository->deleteStatisticsForModel($category, $journal->date);
|
||||
}
|
||||
foreach ($journal->tags as $tag) {
|
||||
$repository->deleteStatisticsForModel($tag, $journal->date);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,5 +62,8 @@ class WebhookEventHandler
|
||||
Log::debug(sprintf('Skip message #%d', $message->id));
|
||||
}
|
||||
}
|
||||
|
||||
// clean up sent messages table:
|
||||
WebhookMessage::where('webhook_messages.sent', true)->where('webhook_messages.created_at', '<', now()->subDays(30))->delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class ShowController extends Controller
|
||||
|
||||
// make sure dates are end of day and start of day:
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
$end->endOfDay()->milli(0);
|
||||
|
||||
$location = $this->repository->getLocation($account);
|
||||
$attachments = $this->repository->getAttachments($account);
|
||||
|
||||
@@ -122,6 +122,11 @@ class NotificationController extends Controller
|
||||
|
||||
public function testNotification(Request $request): RedirectResponse
|
||||
{
|
||||
if (true === auth()->user()->hasRole('demo')) {
|
||||
session()->flash('error', (string) trans('firefly.not_available_demo_user'));
|
||||
|
||||
return redirect(route('settings.notification.index'));
|
||||
}
|
||||
|
||||
$all = $request->all();
|
||||
$channel = $all['test_submit'] ?? '';
|
||||
|
||||
@@ -27,6 +27,7 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Update\UpdateTrait;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Middleware\IsDemoUser;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -66,8 +67,8 @@ class UpdateController extends Controller
|
||||
{
|
||||
$subTitle = (string) trans('firefly.update_check_title');
|
||||
$subTitleIcon = 'fa-star';
|
||||
$permission = app('fireflyconfig')->get('permission_update_check', -1);
|
||||
$channel = app('fireflyconfig')->get('update_channel', 'stable');
|
||||
$permission = FireflyConfig::get('permission_update_check', -1);
|
||||
$channel = FireflyConfig::get('update_channel', 'stable');
|
||||
$selected = $permission->data;
|
||||
$channelSelected = $channel->data;
|
||||
$options = [
|
||||
@@ -96,9 +97,9 @@ class UpdateController extends Controller
|
||||
$channel = $request->get('update_channel');
|
||||
$channel = in_array($channel, ['stable', 'beta', 'alpha'], true) ? $channel : 'stable';
|
||||
|
||||
app('fireflyconfig')->set('permission_update_check', $checkForUpdates);
|
||||
app('fireflyconfig')->set('last_update_check', Carbon::now()->getTimestamp());
|
||||
app('fireflyconfig')->set('update_channel', $channel);
|
||||
FireflyConfig::set('permission_update_check', $checkForUpdates);
|
||||
FireflyConfig::set('last_update_check', Carbon::now()->getTimestamp());
|
||||
FireflyConfig::set('update_channel', $channel);
|
||||
session()->flash('success', (string) trans('firefly.configuration_updated'));
|
||||
|
||||
return redirect(route('settings.update-check'));
|
||||
|
||||
@@ -92,7 +92,7 @@ class ShowController extends Controller
|
||||
// get first journal ever to set off the budget period overview.
|
||||
$first = $this->journalRepos->firstNull();
|
||||
$firstDate = $first instanceof TransactionJournal ? $first->date : $start;
|
||||
$periods = $this->getNoBudgetPeriodOverview($firstDate, $end);
|
||||
$periods = $this->getNoModelPeriodOverview('budget', $firstDate, $end);
|
||||
$page = (int) $request->get('page');
|
||||
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ use FireflyIII\Support\Http\Controllers\PeriodOverview;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
@@ -74,7 +75,7 @@ class NoCategoryController extends Controller
|
||||
*/
|
||||
public function show(Request $request, ?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
app('log')->debug('Start of noCategory()');
|
||||
Log::debug('Start of noCategory()');
|
||||
$start ??= session('start');
|
||||
$end ??= session('end');
|
||||
|
||||
@@ -82,14 +83,12 @@ class NoCategoryController extends Controller
|
||||
/** @var Carbon $end */
|
||||
$page = (int) $request->get('page');
|
||||
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
|
||||
$subTitle = trans(
|
||||
'firefly.without_category_between',
|
||||
['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]
|
||||
);
|
||||
$periods = $this->getNoCategoryPeriodOverview($start);
|
||||
$subTitle = trans('firefly.without_category_between', ['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]);
|
||||
$first = $this->journalRepos->firstNull()->date ?? clone $start;
|
||||
$periods = $this->getNoModelPeriodOverview('category', $first, $end);
|
||||
|
||||
app('log')->debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
|
||||
app('log')->debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
|
||||
Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
|
||||
Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
@@ -117,13 +116,13 @@ class NoCategoryController extends Controller
|
||||
$periods = new Collection();
|
||||
$page = (int) $request->get('page');
|
||||
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
|
||||
app('log')->debug('Start of noCategory()');
|
||||
Log::debug('Start of noCategory()');
|
||||
$subTitle = (string) trans('firefly.all_journals_without_category');
|
||||
$first = $this->journalRepos->firstNull();
|
||||
$start = $first instanceof TransactionJournal ? $first->date : new Carbon();
|
||||
$end = today(config('app.timezone'));
|
||||
app('log')->debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
|
||||
app('log')->debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
|
||||
Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d')));
|
||||
Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d')));
|
||||
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
|
||||
@@ -30,6 +30,7 @@ use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Middleware\IsDemoUser;
|
||||
use FireflyIII\Models\PeriodStatistic;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
@@ -108,6 +109,8 @@ class DebugController extends Controller
|
||||
Artisan::call('route:clear');
|
||||
Artisan::call('view:clear');
|
||||
|
||||
PeriodStatistic::where('id', '>', 0)->delete();
|
||||
|
||||
// also do some recalculations.
|
||||
Artisan::call('correction:recalculates-liabilities');
|
||||
AccountBalanceCalculator::recalculateAll(false);
|
||||
@@ -181,7 +184,6 @@ class DebugController extends Controller
|
||||
$currentDriver = DB::getDriverName();
|
||||
|
||||
return [
|
||||
'db_version' => app('fireflyconfig')->get('db_version', 1)->data,
|
||||
'php_version' => PHP_VERSION,
|
||||
'php_os' => PHP_OS,
|
||||
'uname' => php_uname('m'),
|
||||
|
||||
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\System;
|
||||
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Exception;
|
||||
@@ -81,10 +82,7 @@ class InstallController extends Controller
|
||||
{
|
||||
app('view')->share('FF_VERSION', config('firefly.version'));
|
||||
// index will set FF3 version.
|
||||
app('fireflyconfig')->set('ff3_version', (string) config('firefly.version'));
|
||||
|
||||
// set new DB version.
|
||||
app('fireflyconfig')->set('db_version', (int) config('firefly.db_version'));
|
||||
FireflyConfig::set('ff3_version', (string) config('firefly.version'));
|
||||
|
||||
return view('install.index');
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Update\GroupCloneService;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -76,7 +77,7 @@ class CreateController extends Controller
|
||||
// event!
|
||||
event(new StoredTransactionGroup($newGroup, true, true));
|
||||
|
||||
app('preferences')->mark();
|
||||
Preferences::mark();
|
||||
|
||||
$title = $newGroup->title ?? $newGroup->transactionJournals->first()->description;
|
||||
$link = route('transactions.show', [$newGroup->id]);
|
||||
@@ -103,7 +104,7 @@ class CreateController extends Controller
|
||||
* */
|
||||
public function create(?string $objectType)
|
||||
{
|
||||
app('preferences')->mark();
|
||||
Preferences::mark();
|
||||
|
||||
$sourceId = (int) request()->get('source');
|
||||
$destinationId = (int) request()->get('destination');
|
||||
@@ -114,7 +115,9 @@ class CreateController extends Controller
|
||||
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
|
||||
$subTitle = (string) trans(sprintf('breadcrumbs.create_%s', strtolower((string) $objectType)));
|
||||
$subTitleIcon = 'fa-plus';
|
||||
$optionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data;
|
||||
|
||||
/** @var null|array $optionalFields */
|
||||
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
|
||||
$accountToTypes = config('firefly.account_to_transaction');
|
||||
$previousUrl = $this->rememberPreviousUrl('transactions.create.url');
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FireflyIII\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Support\System\IsOldVersion;
|
||||
use FireflyIII\Support\System\OAuthKeys;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -37,6 +38,8 @@ use Illuminate\Support\Facades\Log;
|
||||
*/
|
||||
class Installer
|
||||
{
|
||||
use IsOldVersion;
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
@@ -65,7 +68,7 @@ class Installer
|
||||
// run installer when no tables are present,
|
||||
// or when old scheme version
|
||||
// or when old firefly version
|
||||
if ($this->hasNoTables() || $this->oldDBVersion() || $this->oldVersion()) {
|
||||
if ($this->hasNoTables() || $this->isOldVersionInstalled()) {
|
||||
return response()->redirectTo(route('installer.index'));
|
||||
}
|
||||
OAuthKeys::verifyKeysRoutine();
|
||||
@@ -126,59 +129,4 @@ class Installer
|
||||
{
|
||||
return false !== stripos($message, 'Base table or view not found');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the "db_version" variable is correct.
|
||||
*/
|
||||
private function oldDBVersion(): bool
|
||||
{
|
||||
// older version in config than database?
|
||||
$configVersion = (int) config('firefly.db_version');
|
||||
$dbVersion = (int) app('fireflyconfig')->getFresh('db_version', 1)->data;
|
||||
if ($configVersion > $dbVersion) {
|
||||
Log::warning(
|
||||
sprintf(
|
||||
'The current configured version (%d) is older than the required version (%d). Redirect to migrate routine.',
|
||||
$dbVersion,
|
||||
$configVersion
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Log::info(sprintf('Configured DB version (%d) equals expected DB version (%d)', $dbVersion, $configVersion));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the "firefly_version" variable is correct.
|
||||
*/
|
||||
private function oldVersion(): bool
|
||||
{
|
||||
// version compare thing.
|
||||
$configVersion = (string) config('firefly.version');
|
||||
$dbVersion = (string) app('fireflyconfig')->getFresh('ff3_version', '1.0')->data;
|
||||
if (str_starts_with($configVersion, 'develop')) {
|
||||
Log::debug('Skipping version check for develop version.');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (1 === version_compare($configVersion, $dbVersion)) {
|
||||
Log::warning(
|
||||
sprintf(
|
||||
'The current configured Firefly III version (%s) is older than the required version (%s). Redirect to migrate routine.',
|
||||
$dbVersion,
|
||||
$configVersion
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Log::info(sprintf('Installed Firefly III version (%s) equals expected Firefly III version (%s)', $dbVersion, $configVersion));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,4 +241,11 @@ class Account extends Model
|
||||
get: static fn ($value) => (string)$value,
|
||||
);
|
||||
}
|
||||
|
||||
public function primaryPeriodStatistics(): MorphMany
|
||||
{
|
||||
|
||||
return $this->morphMany(PeriodStatistic::class, 'primary_statable');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,4 +109,9 @@ class Category extends Model
|
||||
'user_group_id' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function primaryPeriodStatistics(): MorphMany
|
||||
{
|
||||
return $this->morphMany(PeriodStatistic::class, 'primary_statable');
|
||||
}
|
||||
}
|
||||
|
||||
60
app/Models/PeriodStatistic.php
Normal file
60
app/Models/PeriodStatistic.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use FireflyIII\Casts\SeparateTimezoneCaster;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class PeriodStatistic extends Model
|
||||
{
|
||||
use ReturnsIntegerUserIdTrait;
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'start' => SeparateTimezoneCaster::class,
|
||||
'end' => SeparateTimezoneCaster::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function userGroup(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(UserGroup::class);
|
||||
}
|
||||
|
||||
protected function count(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => (int)$value,
|
||||
);
|
||||
}
|
||||
|
||||
public function primaryStatable(): MorphTo
|
||||
{
|
||||
|
||||
return $this->morphTo();
|
||||
|
||||
}
|
||||
|
||||
public function secondaryStatable(): MorphTo
|
||||
{
|
||||
|
||||
return $this->morphTo();
|
||||
|
||||
}
|
||||
|
||||
public function tertiaryStatable(): MorphTo
|
||||
{
|
||||
|
||||
return $this->morphTo();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -104,4 +104,9 @@ class Tag extends Model
|
||||
'user_group_id' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function primaryPeriodStatistics(): MorphMany
|
||||
{
|
||||
return $this->morphMany(PeriodStatistic::class, 'primary_statable');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,14 @@ class UserGroup extends Model
|
||||
return $this->hasMany(Account::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link to accounts.
|
||||
*/
|
||||
public function periodStatistics(): HasMany
|
||||
{
|
||||
return $this->hasMany(PeriodStatistic::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Link to attachments.
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,8 @@ use FireflyIII\Repositories\AuditLogEntry\ALERepository;
|
||||
use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface;
|
||||
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepository;
|
||||
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface;
|
||||
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepository;
|
||||
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
|
||||
use FireflyIII\Repositories\TransactionType\TransactionTypeRepository;
|
||||
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
|
||||
use FireflyIII\Repositories\User\UserRepository;
|
||||
@@ -174,6 +176,18 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
}
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
static function (Application $app): PeriodStatisticRepositoryInterface {
|
||||
/** @var PeriodStatisticRepository $repository */
|
||||
$repository = app(PeriodStatisticRepository::class);
|
||||
if ($app->auth->check()) { // @phpstan-ignore-line (phpstan does not understand the reference to auth)
|
||||
$repository->setUser(auth()->user());
|
||||
}
|
||||
|
||||
return $repository;
|
||||
}
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
static function (Application $app): WebhookRepositoryInterface {
|
||||
/** @var WebhookRepository $repository */
|
||||
|
||||
@@ -546,6 +546,8 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
|
||||
#[Override]
|
||||
public function periodCollection(Account $account, Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug(sprintf('periodCollection(#%d, %s, %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
|
||||
return $account->transactions()
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
|
||||
@@ -358,4 +358,43 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
|
||||
|
||||
return $service->update($category, $data);
|
||||
}
|
||||
|
||||
public function periodCollection(Category $category, Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug(sprintf('periodCollection(#%d, %s, %s)', $category->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
|
||||
return $category->transactionJournals()
|
||||
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
|
||||
->leftJoin('transaction_currencies as foreign_currencies', 'foreign_currencies.id', '=', 'transactions.foreign_currency_id')
|
||||
->where('transaction_journals.date', '>=', $start)
|
||||
->where('transaction_journals.date', '<=', $end)
|
||||
->where('transactions.amount', '>', 0)
|
||||
->get([
|
||||
// currencies
|
||||
'transaction_currencies.id as currency_id',
|
||||
'transaction_currencies.code as currency_code',
|
||||
'transaction_currencies.name as currency_name',
|
||||
'transaction_currencies.symbol as currency_symbol',
|
||||
'transaction_currencies.decimal_places as currency_decimal_places',
|
||||
|
||||
// foreign
|
||||
'foreign_currencies.id as foreign_currency_id',
|
||||
'foreign_currencies.code as foreign_currency_code',
|
||||
'foreign_currencies.name as foreign_currency_name',
|
||||
'foreign_currencies.symbol as foreign_currency_symbol',
|
||||
'foreign_currencies.decimal_places as foreign_currency_decimal_places',
|
||||
|
||||
// fields
|
||||
'transaction_journals.date',
|
||||
'transaction_types.type',
|
||||
'transaction_journals.transaction_currency_id',
|
||||
'transactions.amount',
|
||||
'transactions.native_amount as pc_amount',
|
||||
'transactions.foreign_amount',
|
||||
])
|
||||
->toArray()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ interface CategoryRepositoryInterface
|
||||
|
||||
public function categoryStartsWith(string $query, int $limit): Collection;
|
||||
|
||||
public function periodCollection(Category $category, Carbon $start, Carbon $end): array;
|
||||
|
||||
public function destroy(Category $category): bool;
|
||||
|
||||
/**
|
||||
|
||||
@@ -510,9 +510,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$summarizer->setConvertToPrimary($convertToPrimary);
|
||||
|
||||
// filter $journals by range AND currency if it is present.
|
||||
$expenses = array_filter($expenses, static function (array $expense) use ($category): bool {
|
||||
return $expense['category_id'] === $category->id;
|
||||
});
|
||||
$expenses = array_filter($expenses, static fn (array $expense): bool => $expense['category_id'] === $category->id);
|
||||
|
||||
return $summarizer->groupByCurrencyId($expenses, $method, false);
|
||||
}
|
||||
|
||||
143
app/Repositories/PeriodStatistic/PeriodStatisticRepository.php
Normal file
143
app/Repositories/PeriodStatistic/PeriodStatisticRepository.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* PeriodStatisticRepository.php
|
||||
* Copyright (c) 2025 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/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Repositories\PeriodStatistic;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\PeriodStatistic;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Override;
|
||||
|
||||
class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, UserGroupInterface
|
||||
{
|
||||
use UserGroupTrait;
|
||||
|
||||
public function findPeriodStatistics(Model $model, Carbon $start, Carbon $end, array $types): Collection
|
||||
{
|
||||
return $model->primaryPeriodStatistics()
|
||||
->where('start', $start)
|
||||
->where('end', $end)
|
||||
->whereIn('type', $types)
|
||||
->get()
|
||||
;
|
||||
}
|
||||
|
||||
public function findPeriodStatistic(Model $model, Carbon $start, Carbon $end, string $type): Collection
|
||||
{
|
||||
return $model->primaryPeriodStatistics()
|
||||
->where('start', $start)
|
||||
->where('end', $end)
|
||||
->where('type', $type)
|
||||
->get()
|
||||
;
|
||||
}
|
||||
|
||||
public function saveStatistic(Model $model, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic
|
||||
{
|
||||
$stat = new PeriodStatistic();
|
||||
$stat->primaryStatable()->associate($model);
|
||||
$stat->transaction_currency_id = $currencyId;
|
||||
$stat->user_group_id = $this->getUserGroup()->id;
|
||||
$stat->start = $start;
|
||||
$stat->start_tz = $start->format('e');
|
||||
$stat->end = $end;
|
||||
$stat->end_tz = $end->format('e');
|
||||
$stat->amount = $amount;
|
||||
$stat->count = $count;
|
||||
$stat->type = $type;
|
||||
$stat->save();
|
||||
|
||||
Log::debug(sprintf(
|
||||
'Saved #%d [currency #%d, Model %s #%d, %s to %s, %d, %s] as new statistic.',
|
||||
$stat->id,
|
||||
$model::class,
|
||||
$model->id,
|
||||
$stat->transaction_currency_id,
|
||||
$stat->start->toW3cString(),
|
||||
$stat->end->toW3cString(),
|
||||
$count,
|
||||
$amount
|
||||
));
|
||||
|
||||
return $stat;
|
||||
}
|
||||
|
||||
public function allInRangeForModel(Model $model, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
return $model->primaryPeriodStatistics()->where('start', '>=', $start)->where('end', '<=', $end)->get();
|
||||
}
|
||||
|
||||
public function deleteStatisticsForModel(Model $model, Carbon $date): void
|
||||
{
|
||||
$model->primaryPeriodStatistics()->where('start', '<=', $date)->where('end', '>=', $date)->delete();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function allInRangeForPrefix(string $prefix, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
return $this->userGroup->periodStatistics()
|
||||
->where('type', 'LIKE', sprintf('%s%%', $prefix))
|
||||
->where('start', '>=', $start)->where('end', '<=', $end)->get()
|
||||
;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function savePrefixedStatistic(string $prefix, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic
|
||||
{
|
||||
$stat = new PeriodStatistic();
|
||||
$stat->transaction_currency_id = $currencyId;
|
||||
$stat->user_group_id = $this->getUserGroup()->id;
|
||||
$stat->start = $start;
|
||||
$stat->start_tz = $start->format('e');
|
||||
$stat->end = $end;
|
||||
$stat->end_tz = $end->format('e');
|
||||
$stat->amount = $amount;
|
||||
$stat->count = $count;
|
||||
$stat->type = sprintf('%s_%s', $prefix, $type);
|
||||
$stat->save();
|
||||
|
||||
Log::debug(sprintf(
|
||||
'Saved #%d [currency #%d, type "%s", %s to %s, %d, %s] as new statistic.',
|
||||
$stat->id,
|
||||
$stat->transaction_currency_id,
|
||||
$stat->type,
|
||||
$stat->start->toW3cString(),
|
||||
$stat->end->toW3cString(),
|
||||
$count,
|
||||
$amount
|
||||
));
|
||||
|
||||
return $stat;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function deleteStatisticsForPrefix(UserGroup $userGroup, string $prefix, Carbon $date): void
|
||||
{
|
||||
$userGroup->periodStatistics()->where('start', '<=', $date)->where('end', '>=', $date)->where('type', 'LIKE', sprintf('%s%%', $prefix))->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* PeriodStatisticRepositoryInterface.php
|
||||
* Copyright (c) 2025 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/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Repositories\PeriodStatistic;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\PeriodStatistic;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
interface PeriodStatisticRepositoryInterface
|
||||
{
|
||||
public function findPeriodStatistics(Model $model, Carbon $start, Carbon $end, array $types): Collection;
|
||||
|
||||
public function findPeriodStatistic(Model $model, Carbon $start, Carbon $end, string $type): Collection;
|
||||
|
||||
public function saveStatistic(Model $model, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic;
|
||||
|
||||
public function savePrefixedStatistic(string $prefix, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic;
|
||||
|
||||
public function allInRangeForModel(Model $model, Carbon $start, Carbon $end): Collection;
|
||||
|
||||
public function allInRangeForPrefix(string $prefix, Carbon $start, Carbon $end): Collection;
|
||||
|
||||
public function deleteStatisticsForModel(Model $model, Carbon $date): void;
|
||||
|
||||
public function deleteStatisticsForPrefix(UserGroup $userGroup, string $prefix, Carbon $date): void;
|
||||
}
|
||||
@@ -36,6 +36,7 @@ use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -67,7 +68,7 @@ trait ModifiesPiggyBanks
|
||||
{
|
||||
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
|
||||
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
|
||||
$pivot->current_amount = bcsub($currentAmount, $amount);
|
||||
$pivot->current_amount = bcsub((string) $currentAmount, $amount);
|
||||
$pivot->native_current_amount = null;
|
||||
|
||||
// also update native_current_amount.
|
||||
@@ -90,7 +91,7 @@ trait ModifiesPiggyBanks
|
||||
{
|
||||
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
|
||||
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
|
||||
$pivot->current_amount = bcadd($currentAmount, $amount);
|
||||
$pivot->current_amount = bcadd((string) $currentAmount, $amount);
|
||||
$pivot->native_current_amount = null;
|
||||
|
||||
// also update native_current_amount.
|
||||
@@ -122,13 +123,13 @@ trait ModifiesPiggyBanks
|
||||
|
||||
|
||||
if (0 !== bccomp($piggyBank->target_amount, '0')) {
|
||||
$leftToSave = bcsub($piggyBank->target_amount, $savedSoFar);
|
||||
$maxAmount = 1 === bccomp($leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
|
||||
$leftToSave = bcsub($piggyBank->target_amount, (string) $savedSoFar);
|
||||
$maxAmount = 1 === bccomp((string) $leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
|
||||
Log::debug(sprintf('Left to save: %s', $leftToSave));
|
||||
Log::debug(sprintf('Maximum amount: %s', $maxAmount));
|
||||
}
|
||||
|
||||
$compare = bccomp($amount, $maxAmount);
|
||||
$compare = bccomp($amount, (string) $maxAmount);
|
||||
$result = $compare <= 0;
|
||||
|
||||
Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
|
||||
@@ -140,7 +141,7 @@ trait ModifiesPiggyBanks
|
||||
{
|
||||
$savedSoFar = $this->getCurrentAmount($piggyBank, $account);
|
||||
|
||||
return bccomp($amount, $savedSoFar) <= 0;
|
||||
return bccomp($amount, (string) $savedSoFar) <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -227,16 +228,15 @@ trait ModifiesPiggyBanks
|
||||
$factory->user = $this->user;
|
||||
|
||||
// the piggy bank currency is set or updated FIRST, if it exists.
|
||||
|
||||
$factory->linkToAccountIds($piggyBank, $data['accounts'] ?? []);
|
||||
|
||||
|
||||
// if the piggy bank is now smaller than the sum of the money saved,
|
||||
// remove money from all accounts until the piggy bank is the right amount.
|
||||
$currentAmount = $this->getCurrentAmount($piggyBank);
|
||||
if (1 === bccomp($currentAmount, (string)$piggyBank->target_amount) && 0 !== bccomp((string)$piggyBank->target_amount, '0')) {
|
||||
if (1 === bccomp((string) $currentAmount, (string)$piggyBank->target_amount) && 0 !== bccomp((string)$piggyBank->target_amount, '0')) {
|
||||
Log::debug(sprintf('Current amount is %s, target amount is %s', $currentAmount, $piggyBank->target_amount));
|
||||
$difference = bcsub((string)$piggyBank->target_amount, $currentAmount);
|
||||
$difference = bcsub((string)$piggyBank->target_amount, (string) $currentAmount);
|
||||
|
||||
// an amount will be removed, create "negative" event:
|
||||
// Log::debug(sprintf('ChangedAmount: is triggered with difference "%s"', $difference));
|
||||
@@ -244,7 +244,7 @@ trait ModifiesPiggyBanks
|
||||
|
||||
// question is, from which account(s) to remove the difference?
|
||||
// solution: just start from the top until there is no more money left to remove.
|
||||
$this->removeAmountFromAll($piggyBank, app('steam')->positive($difference));
|
||||
$this->removeAmountFromAll($piggyBank, Steam::positive($difference));
|
||||
}
|
||||
|
||||
// update using name:
|
||||
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\Tag;
|
||||
|
||||
use Override;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Factory\TagFactory;
|
||||
@@ -379,4 +380,44 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
|
||||
/** @var null|Location */
|
||||
return $tag->locations()->first();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function periodCollection(Tag $tag, Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug(sprintf('periodCollection(#%d, %s, %s)', $tag->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
|
||||
return $tag->transactionJournals()
|
||||
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
|
||||
->leftJoin('transaction_currencies as foreign_currencies', 'foreign_currencies.id', '=', 'transactions.foreign_currency_id')
|
||||
->where('transaction_journals.date', '>=', $start)
|
||||
->where('transaction_journals.date', '<=', $end)
|
||||
->where('transactions.amount', '>', 0)
|
||||
->get([
|
||||
// currencies
|
||||
'transaction_currencies.id as currency_id',
|
||||
'transaction_currencies.code as currency_code',
|
||||
'transaction_currencies.name as currency_name',
|
||||
'transaction_currencies.symbol as currency_symbol',
|
||||
'transaction_currencies.decimal_places as currency_decimal_places',
|
||||
|
||||
// foreign
|
||||
'foreign_currencies.id as foreign_currency_id',
|
||||
'foreign_currencies.code as foreign_currency_code',
|
||||
'foreign_currencies.name as foreign_currency_name',
|
||||
'foreign_currencies.symbol as foreign_currency_symbol',
|
||||
'foreign_currencies.decimal_places as foreign_currency_decimal_places',
|
||||
|
||||
// fields
|
||||
'transaction_journals.date',
|
||||
'transaction_types.type',
|
||||
'transaction_journals.transaction_currency_id',
|
||||
'transactions.amount',
|
||||
'transactions.native_amount as pc_amount',
|
||||
'transactions.foreign_amount',
|
||||
])
|
||||
->toArray()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ interface TagRepositoryInterface
|
||||
*/
|
||||
public function destroy(Tag $tag): bool;
|
||||
|
||||
public function periodCollection(Tag $tag, Carbon $start, Carbon $end): array;
|
||||
|
||||
/**
|
||||
* Destroy all tags.
|
||||
*/
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FireflyIII\Services\FireflyIIIOrg\Update;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\NewVersionAvailable;
|
||||
use FireflyIII\Support\System\IsOldVersion;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -38,6 +39,8 @@ use function Safe\json_decode;
|
||||
*/
|
||||
class UpdateRequest implements UpdateRequestInterface
|
||||
{
|
||||
use IsOldVersion;
|
||||
|
||||
public function getUpdateInformation(string $channel): array
|
||||
{
|
||||
Log::debug(sprintf('Now in getUpdateInformation(%s)', $channel));
|
||||
@@ -183,20 +186,15 @@ class UpdateRequest implements UpdateRequestInterface
|
||||
private function parseResultDevelop(string $current, string $latest, array $information): array
|
||||
{
|
||||
Log::debug(sprintf('User is running develop version "%s"', $current));
|
||||
$parts = explode('/', $current);
|
||||
$compare = $this->compareDevelopVersions($current, $latest);
|
||||
$return = [];
|
||||
|
||||
/** @var Carbon $devDate */
|
||||
$devDate = Carbon::createFromFormat('Y-m-d', $parts[1]);
|
||||
|
||||
if ($devDate->lte($information['date'])) {
|
||||
Log::debug(sprintf('This development release is older, release = %s, latest version %s = %s', $devDate->format('Y-m-d'), $latest, $information['date']->format('Y-m-d')));
|
||||
if (-1 === $compare) {
|
||||
$return['level'] = 'info';
|
||||
$return['message'] = (string) trans('firefly.update_current_dev_older', ['version' => $current, 'new_version' => $latest]);
|
||||
|
||||
return $return;
|
||||
}
|
||||
Log::debug(sprintf('This development release is newer, release = %s, latest version %s = %s', $devDate->format('Y-m-d'), $latest, $information['date']->format('Y-m-d')));
|
||||
$return['level'] = 'info';
|
||||
$return['message'] = (string) trans('firefly.update_current_dev_newer', ['version' => $current, 'new_version' => $latest]);
|
||||
|
||||
|
||||
@@ -276,66 +276,66 @@ class CreditRecalculateService
|
||||
if ($isSameAccount && $isCredit && $this->isWithdrawalIn($usedAmount, $type)) { // case 1
|
||||
$usedAmount = app('steam')->positive($usedAmount);
|
||||
|
||||
return bcadd($leftOfDebt, $usedAmount);
|
||||
return bcadd($leftOfDebt, (string) $usedAmount);
|
||||
// Log::debug(sprintf('Case 1 (withdrawal into credit liability): %s + %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
|
||||
if ($isSameAccount && $isCredit && $this->isWithdrawalOut($usedAmount, $type)) { // case 2
|
||||
$usedAmount = app('steam')->positive($usedAmount);
|
||||
|
||||
return bcsub($leftOfDebt, $usedAmount);
|
||||
return bcsub($leftOfDebt, (string) $usedAmount);
|
||||
// Log::debug(sprintf('Case 2 (withdrawal away from liability): %s - %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
|
||||
if ($isSameAccount && $isCredit && $this->isDepositOut($usedAmount, $type)) { // case 3
|
||||
$usedAmount = app('steam')->positive($usedAmount);
|
||||
|
||||
return bcsub($leftOfDebt, $usedAmount);
|
||||
return bcsub($leftOfDebt, (string) $usedAmount);
|
||||
// Log::debug(sprintf('Case 3 (deposit away from liability): %s - %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
|
||||
if ($isSameAccount && $isCredit && $this->isDepositIn($usedAmount, $type)) { // case 4
|
||||
$usedAmount = app('steam')->positive($usedAmount);
|
||||
|
||||
return bcadd($leftOfDebt, $usedAmount);
|
||||
return bcadd($leftOfDebt, (string) $usedAmount);
|
||||
// Log::debug(sprintf('Case 4 (deposit into credit liability): %s + %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
if ($isSameAccount && $isCredit && $this->isTransferIn($usedAmount, $type)) { // case 5
|
||||
$usedAmount = app('steam')->positive($usedAmount);
|
||||
|
||||
return bcadd($leftOfDebt, $usedAmount);
|
||||
return bcadd($leftOfDebt, (string) $usedAmount);
|
||||
// Log::debug(sprintf('Case 5 (transfer into credit liability): %s + %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
if ($isSameAccount && $isDebit && $this->isWithdrawalIn($usedAmount, $type)) { // case 6
|
||||
$usedAmount = app('steam')->positive($usedAmount);
|
||||
|
||||
return bcsub($leftOfDebt, $usedAmount);
|
||||
return bcsub($leftOfDebt, (string) $usedAmount);
|
||||
// Log::debug(sprintf('Case 6 (withdrawal into debit liability): %s - %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
if ($isSameAccount && $isDebit && $this->isDepositOut($usedAmount, $type)) { // case 7
|
||||
$usedAmount = app('steam')->positive($usedAmount);
|
||||
|
||||
return bcadd($leftOfDebt, $usedAmount);
|
||||
return bcadd($leftOfDebt, (string) $usedAmount);
|
||||
// Log::debug(sprintf('Case 7 (deposit away from liability): %s + %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
if ($isSameAccount && $isDebit && $this->isWithdrawalOut($usedAmount, $type)) { // case 8
|
||||
$usedAmount = app('steam')->positive($usedAmount);
|
||||
|
||||
return bcadd($leftOfDebt, $usedAmount);
|
||||
return bcadd($leftOfDebt, (string) $usedAmount);
|
||||
// Log::debug(sprintf('Case 8 (withdrawal away from liability): %s + %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
|
||||
if ($isSameAccount && $isDebit && $this->isTransferIn($usedAmount, $type)) { // case 9
|
||||
$usedAmount = app('steam')->positive($usedAmount);
|
||||
|
||||
return bcsub($leftOfDebt, $usedAmount);
|
||||
return bcsub($leftOfDebt, (string) $usedAmount);
|
||||
// 2024-10-05, #9225 this used to say you would owe more, but a transfer INTO a debit from wherever means you owe LESS.
|
||||
// Log::debug(sprintf('Case 9 (transfer into debit liability, means you owe LESS): %s - %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
if ($isSameAccount && $isDebit && $this->isTransferOut($usedAmount, $type)) { // case 10
|
||||
$usedAmount = app('steam')->positive($usedAmount);
|
||||
|
||||
return bcadd($leftOfDebt, $usedAmount);
|
||||
return bcadd($leftOfDebt, (string) $usedAmount);
|
||||
// 2024-10-05, #9225 this used to say you would owe less, but a transfer OUT OF a debit from wherever means you owe MORE.
|
||||
// Log::debug(sprintf('Case 10 (transfer out of debit liability, means you owe MORE): %s + %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
@@ -344,7 +344,7 @@ class CreditRecalculateService
|
||||
if (in_array($type, [TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::TRANSFER->value], true)) {
|
||||
$usedAmount = app('steam')->negative($usedAmount);
|
||||
|
||||
return bcadd($leftOfDebt, $usedAmount);
|
||||
return bcadd($leftOfDebt, (string) $usedAmount);
|
||||
// Log::debug(sprintf('Case X (all other cases): %s + %s = %s', app('steam')->bcround($leftOfDebt, 2), app('steam')->bcround($usedAmount, 2), app('steam')->bcround($result, 2)));
|
||||
}
|
||||
|
||||
|
||||
@@ -41,280 +41,6 @@ use NumberFormatter;
|
||||
*/
|
||||
class Amount
|
||||
{
|
||||
/**
|
||||
* This method will properly format the given number, in color or "black and white",
|
||||
* as a currency, given two things: the currency required and the current locale.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function formatAnything(TransactionCurrency $format, string $amount, ?bool $coloured = null): string
|
||||
{
|
||||
return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will properly format the given number, in color or "black and white",
|
||||
* as a currency, given two things: the currency required and the current locale.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string
|
||||
{
|
||||
$locale = Steam::getLocale();
|
||||
$rounded = Steam::bcround($amount, $decimalPlaces);
|
||||
$coloured ??= true;
|
||||
|
||||
$fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY);
|
||||
$fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol);
|
||||
$fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces);
|
||||
$fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces);
|
||||
$result = (string)$fmt->format((float)$rounded); // intentional float
|
||||
|
||||
if (true === $coloured) {
|
||||
if (1 === bccomp($rounded, '0')) {
|
||||
return sprintf('<span class="text-success money-positive">%s</span>', $result);
|
||||
}
|
||||
if (-1 === bccomp($rounded, '0')) {
|
||||
return sprintf('<span class="text-danger money-negative">%s</span>', $result);
|
||||
}
|
||||
|
||||
return sprintf('<span class="money-neutral">%s</span>', $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function formatByCurrencyId(int $currencyId, string $amount, ?bool $coloured = null): string
|
||||
{
|
||||
$format = $this->getTransactionCurrencyById($currencyId);
|
||||
|
||||
return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
|
||||
}
|
||||
|
||||
public function getAllCurrencies(): Collection
|
||||
{
|
||||
return TransactionCurrency::orderBy('code', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Experimental function to see if we can quickly and quietly get the amount from a journal.
|
||||
* This depends on the user's default currency and the wish to have it converted.
|
||||
*/
|
||||
public function getAmountFromJournal(array $journal): string
|
||||
{
|
||||
$convertToPrimary = $this->convertToPrimary();
|
||||
$currency = $this->getPrimaryCurrency();
|
||||
$field = $convertToPrimary && $currency->id !== $journal['currency_id'] ? 'pc_amount' : 'amount';
|
||||
$amount = $journal[$field] ?? '0';
|
||||
// Log::debug(sprintf('Field is %s, amount is %s', $field, $amount));
|
||||
// fallback, the transaction has a foreign amount in $currency.
|
||||
if ($convertToPrimary && null !== $journal['foreign_amount'] && $currency->id === (int)$journal['foreign_currency_id']) {
|
||||
$amount = $journal['foreign_amount'];
|
||||
// Log::debug(sprintf('Overruled, amount is now %s', $amount));
|
||||
}
|
||||
|
||||
return (string)$amount;
|
||||
}
|
||||
|
||||
public function getTransactionCurrencyById(int $currencyId): TransactionCurrency
|
||||
{
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
$key = sprintf('transaction_currency_%d', $currencyId);
|
||||
|
||||
/** @var null|TransactionCurrency $pref */
|
||||
$pref = $instance->getPreference($key);
|
||||
if (null !== $pref) {
|
||||
return $pref;
|
||||
}
|
||||
$currency = TransactionCurrency::find($currencyId);
|
||||
if (null === $currency) {
|
||||
$message = sprintf('Could not find a transaction currency with ID #%d in %s', $currencyId, __METHOD__);
|
||||
Log::error($message);
|
||||
|
||||
throw new FireflyException($message);
|
||||
}
|
||||
$instance->setPreference($key, $currency);
|
||||
|
||||
return $currency;
|
||||
}
|
||||
|
||||
public function getTransactionCurrencyByCode(string $code): TransactionCurrency
|
||||
{
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
$key = sprintf('transaction_currency_%s', $code);
|
||||
|
||||
/** @var null|TransactionCurrency $pref */
|
||||
$pref = $instance->getPreference($key);
|
||||
if (null !== $pref) {
|
||||
return $pref;
|
||||
}
|
||||
$currency = TransactionCurrency::whereCode($code)->first();
|
||||
if (null === $currency) {
|
||||
$message = sprintf('Could not find a transaction currency with code "%s" in %s', $code, __METHOD__);
|
||||
Log::error($message);
|
||||
|
||||
throw new FireflyException($message);
|
||||
}
|
||||
$instance->setPreference($key, $currency);
|
||||
|
||||
return $currency;
|
||||
}
|
||||
|
||||
public function convertToPrimary(?User $user = null): bool
|
||||
{
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
if (!$user instanceof User) {
|
||||
$pref = $instance->getPreference('convert_to_primary_no_user');
|
||||
if (null === $pref) {
|
||||
$res = true === Preferences::get('convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
$instance->setPreference('convert_to_primary_no_user', $res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return $pref;
|
||||
}
|
||||
$key = sprintf('convert_to_primary_%d', $user->id);
|
||||
$pref = $instance->getPreference($key);
|
||||
if (null === $pref) {
|
||||
$res = true === Preferences::getForUser($user, 'convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
$instance->setPreference($key, $res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return $pref;
|
||||
}
|
||||
|
||||
public function getPrimaryCurrency(): TransactionCurrency
|
||||
{
|
||||
if (auth()->check()) {
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
if (null !== $user->userGroup) {
|
||||
return $this->getPrimaryCurrencyByUserGroup($user->userGroup);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getSystemCurrency();
|
||||
}
|
||||
|
||||
public function getPrimaryCurrencyByUserGroup(UserGroup $userGroup): TransactionCurrency
|
||||
{
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty('getPrimaryCurrencyByGroup');
|
||||
$cache->addProperty($userGroup->id);
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
}
|
||||
|
||||
/** @var null|TransactionCurrency $primary */
|
||||
$primary = $userGroup->currencies()->where('group_default', true)->first();
|
||||
if (null === $primary) {
|
||||
$primary = $this->getSystemCurrency();
|
||||
// could be the user group has no default right now.
|
||||
$userGroup->currencies()->sync([$primary->id => ['group_default' => true]]);
|
||||
}
|
||||
$cache->store($primary);
|
||||
|
||||
return $primary;
|
||||
}
|
||||
|
||||
public function getSystemCurrency(): TransactionCurrency
|
||||
{
|
||||
return TransactionCurrency::whereNull('deleted_at')->where('code', 'EUR')->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Experimental function to see if we can quickly and quietly get the amount from a journal.
|
||||
* This depends on the user's default currency and the wish to have it converted.
|
||||
*/
|
||||
public function getAmountFromJournalObject(TransactionJournal $journal): string
|
||||
{
|
||||
$convertToPrimary = $this->convertToPrimary();
|
||||
$currency = $this->getPrimaryCurrency();
|
||||
$field = $convertToPrimary && $currency->id !== $journal->transaction_currency_id ? 'pc_amount' : 'amount';
|
||||
|
||||
/** @var null|Transaction $sourceTransaction */
|
||||
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
if (null === $sourceTransaction) {
|
||||
return '0';
|
||||
}
|
||||
$amount = $sourceTransaction->{$field} ?? '0';
|
||||
if ((int)$sourceTransaction->foreign_currency_id === $currency->id) {
|
||||
// use foreign amount instead!
|
||||
$amount = (string)$sourceTransaction->foreign_amount; // hard coded to be foreign amount.
|
||||
}
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
public function getCurrencies(): Collection
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->currencies()->orderBy('code', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the correct format rules required by accounting.js,
|
||||
* the library used to format amounts in charts.
|
||||
*
|
||||
* Used only in one place.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getJsConfig(): array
|
||||
{
|
||||
$config = $this->getLocaleInfo();
|
||||
$negative = self::getAmountJsConfig($config['n_sep_by_space'], $config['n_sign_posn'], $config['negative_sign'], $config['n_cs_precedes']);
|
||||
$positive = self::getAmountJsConfig($config['p_sep_by_space'], $config['p_sign_posn'], $config['positive_sign'], $config['p_cs_precedes']);
|
||||
|
||||
return [
|
||||
'mon_decimal_point' => $config['mon_decimal_point'],
|
||||
'mon_thousands_sep' => $config['mon_thousands_sep'],
|
||||
'format' => [
|
||||
'pos' => $positive,
|
||||
'neg' => $negative,
|
||||
'zero' => $positive,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getLocaleInfo(): array
|
||||
{
|
||||
// get config from preference, not from translation:
|
||||
$locale = Steam::getLocale();
|
||||
$array = Steam::getLocaleArray($locale);
|
||||
|
||||
setlocale(LC_MONETARY, $array);
|
||||
$info = localeconv();
|
||||
|
||||
// correct variables
|
||||
$info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes');
|
||||
$info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes');
|
||||
|
||||
$info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space');
|
||||
$info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space');
|
||||
|
||||
$fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY);
|
||||
|
||||
$info['mon_decimal_point'] = $fmt->getSymbol(NumberFormatter::MONETARY_SEPARATOR_SYMBOL);
|
||||
$info['mon_thousands_sep'] = $fmt->getSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL);
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
private function getLocaleField(array $info, string $field): bool
|
||||
{
|
||||
return (is_bool($info[$field]) && true === $info[$field])
|
||||
|| (is_int($info[$field]) && 1 === $info[$field]);
|
||||
}
|
||||
|
||||
/**
|
||||
* bool $sepBySpace is $localeconv['n_sep_by_space']
|
||||
* int $signPosn = $localeconv['n_sign_posn']
|
||||
@@ -384,4 +110,278 @@ class Amount
|
||||
|
||||
return $posA.$posD.'%v'.$space.$posB.'%s'.$posC.$posE;
|
||||
}
|
||||
|
||||
public function convertToPrimary(?User $user = null): bool
|
||||
{
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
if (!$user instanceof User) {
|
||||
$pref = $instance->getPreference('convert_to_primary_no_user');
|
||||
if (null === $pref) {
|
||||
$res = true === Preferences::get('convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
$instance->setPreference('convert_to_primary_no_user', $res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return $pref;
|
||||
}
|
||||
$key = sprintf('convert_to_primary_%d', $user->id);
|
||||
$pref = $instance->getPreference($key);
|
||||
if (null === $pref) {
|
||||
$res = true === Preferences::getForUser($user, 'convert_to_primary', false)->data && true === config('cer.enabled');
|
||||
$instance->setPreference($key, $res);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
return $pref;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will properly format the given number, in color or "black and white",
|
||||
* as a currency, given two things: the currency required and the current locale.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function formatAnything(TransactionCurrency $format, string $amount, ?bool $coloured = null): string
|
||||
{
|
||||
return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
|
||||
}
|
||||
|
||||
public function formatByCurrencyId(int $currencyId, string $amount, ?bool $coloured = null): string
|
||||
{
|
||||
$format = $this->getTransactionCurrencyById($currencyId);
|
||||
|
||||
return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will properly format the given number, in color or "black and white",
|
||||
* as a currency, given two things: the currency required and the current locale.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string
|
||||
{
|
||||
$locale = Steam::getLocale();
|
||||
$rounded = Steam::bcround($amount, $decimalPlaces);
|
||||
$coloured ??= true;
|
||||
|
||||
$fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY);
|
||||
$fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol);
|
||||
$fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces);
|
||||
$fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces);
|
||||
$result = (string)$fmt->format((float)$rounded); // intentional float
|
||||
|
||||
if (true === $coloured) {
|
||||
if (1 === bccomp($rounded, '0')) {
|
||||
return sprintf('<span class="text-success money-positive">%s</span>', $result);
|
||||
}
|
||||
if (-1 === bccomp($rounded, '0')) {
|
||||
return sprintf('<span class="text-danger money-negative">%s</span>', $result);
|
||||
}
|
||||
|
||||
return sprintf('<span class="money-neutral">%s</span>', $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getAllCurrencies(): Collection
|
||||
{
|
||||
return TransactionCurrency::orderBy('code', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Experimental function to see if we can quickly and quietly get the amount from a journal.
|
||||
* This depends on the user's default currency and the wish to have it converted.
|
||||
*/
|
||||
public function getAmountFromJournal(array $journal): string
|
||||
{
|
||||
$convertToPrimary = $this->convertToPrimary();
|
||||
$currency = $this->getPrimaryCurrency();
|
||||
$field = $convertToPrimary && $currency->id !== $journal['currency_id'] ? 'pc_amount' : 'amount';
|
||||
$amount = $journal[$field] ?? '0';
|
||||
// Log::debug(sprintf('Field is %s, amount is %s', $field, $amount));
|
||||
// fallback, the transaction has a foreign amount in $currency.
|
||||
if ($convertToPrimary && null !== $journal['foreign_amount'] && $currency->id === (int)$journal['foreign_currency_id']) {
|
||||
$amount = $journal['foreign_amount'];
|
||||
// Log::debug(sprintf('Overruled, amount is now %s', $amount));
|
||||
}
|
||||
|
||||
return (string)$amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Experimental function to see if we can quickly and quietly get the amount from a journal.
|
||||
* This depends on the user's default currency and the wish to have it converted.
|
||||
*/
|
||||
public function getAmountFromJournalObject(TransactionJournal $journal): string
|
||||
{
|
||||
$convertToPrimary = $this->convertToPrimary();
|
||||
$currency = $this->getPrimaryCurrency();
|
||||
$field = $convertToPrimary && $currency->id !== $journal->transaction_currency_id ? 'pc_amount' : 'amount';
|
||||
|
||||
/** @var null|Transaction $sourceTransaction */
|
||||
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
if (null === $sourceTransaction) {
|
||||
return '0';
|
||||
}
|
||||
$amount = $sourceTransaction->{$field} ?? '0';
|
||||
if ((int)$sourceTransaction->foreign_currency_id === $currency->id) {
|
||||
// use foreign amount instead!
|
||||
$amount = (string)$sourceTransaction->foreign_amount; // hard coded to be foreign amount.
|
||||
}
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
public function getCurrencies(): Collection
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
return $user->currencies()->orderBy('code', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the correct format rules required by accounting.js,
|
||||
* the library used to format amounts in charts.
|
||||
*
|
||||
* Used only in one place.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getJsConfig(): array
|
||||
{
|
||||
$config = $this->getLocaleInfo();
|
||||
$negative = self::getAmountJsConfig($config['n_sep_by_space'], $config['n_sign_posn'], $config['negative_sign'], $config['n_cs_precedes']);
|
||||
$positive = self::getAmountJsConfig($config['p_sep_by_space'], $config['p_sign_posn'], $config['positive_sign'], $config['p_cs_precedes']);
|
||||
|
||||
return [
|
||||
'mon_decimal_point' => $config['mon_decimal_point'],
|
||||
'mon_thousands_sep' => $config['mon_thousands_sep'],
|
||||
'format' => [
|
||||
'pos' => $positive,
|
||||
'neg' => $negative,
|
||||
'zero' => $positive,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getPrimaryCurrency(): TransactionCurrency
|
||||
{
|
||||
if (auth()->check()) {
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
if (null !== $user->userGroup) {
|
||||
return $this->getPrimaryCurrencyByUserGroup($user->userGroup);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getSystemCurrency();
|
||||
}
|
||||
|
||||
public function getPrimaryCurrencyByUserGroup(UserGroup $userGroup): TransactionCurrency
|
||||
{
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty('getPrimaryCurrencyByGroup');
|
||||
$cache->addProperty($userGroup->id);
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
}
|
||||
|
||||
/** @var null|TransactionCurrency $primary */
|
||||
$primary = $userGroup->currencies()->where('group_default', true)->first();
|
||||
if (null === $primary) {
|
||||
$primary = $this->getSystemCurrency();
|
||||
// could be the user group has no default right now.
|
||||
$userGroup->currencies()->sync([$primary->id => ['group_default' => true]]);
|
||||
}
|
||||
$cache->store($primary);
|
||||
|
||||
return $primary;
|
||||
}
|
||||
|
||||
public function getSystemCurrency(): TransactionCurrency
|
||||
{
|
||||
return TransactionCurrency::whereNull('deleted_at')->where('code', 'EUR')->first();
|
||||
}
|
||||
|
||||
public function getTransactionCurrencyByCode(string $code): TransactionCurrency
|
||||
{
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
$key = sprintf('transaction_currency_%s', $code);
|
||||
|
||||
/** @var null|TransactionCurrency $pref */
|
||||
$pref = $instance->getPreference($key);
|
||||
if (null !== $pref) {
|
||||
return $pref;
|
||||
}
|
||||
$currency = TransactionCurrency::whereCode($code)->first();
|
||||
if (null === $currency) {
|
||||
$message = sprintf('Could not find a transaction currency with code "%s" in %s', $code, __METHOD__);
|
||||
Log::error($message);
|
||||
|
||||
throw new FireflyException($message);
|
||||
}
|
||||
$instance->setPreference($key, $currency);
|
||||
|
||||
return $currency;
|
||||
}
|
||||
|
||||
public function getTransactionCurrencyById(int $currencyId): TransactionCurrency
|
||||
{
|
||||
$instance = PreferencesSingleton::getInstance();
|
||||
$key = sprintf('transaction_currency_%d', $currencyId);
|
||||
|
||||
/** @var null|TransactionCurrency $pref */
|
||||
$pref = $instance->getPreference($key);
|
||||
if (null !== $pref) {
|
||||
return $pref;
|
||||
}
|
||||
$currency = TransactionCurrency::find($currencyId);
|
||||
if (null === $currency) {
|
||||
$message = sprintf('Could not find a transaction currency with ID #%d in %s', $currencyId, __METHOD__);
|
||||
Log::error($message);
|
||||
|
||||
throw new FireflyException($message);
|
||||
}
|
||||
$instance->setPreference($key, $currency);
|
||||
|
||||
return $currency;
|
||||
}
|
||||
|
||||
private function getLocaleField(array $info, string $field): bool
|
||||
{
|
||||
return (is_bool($info[$field]) && true === $info[$field])
|
||||
|| (is_int($info[$field]) && 1 === $info[$field]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getLocaleInfo(): array
|
||||
{
|
||||
// get config from preference, not from translation:
|
||||
$locale = Steam::getLocale();
|
||||
$array = Steam::getLocaleArray($locale);
|
||||
|
||||
setlocale(LC_MONETARY, $array);
|
||||
$info = localeconv();
|
||||
|
||||
// correct variables
|
||||
$info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes');
|
||||
$info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes');
|
||||
|
||||
$info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space');
|
||||
$info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space');
|
||||
|
||||
$fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY);
|
||||
|
||||
$info['mon_decimal_point'] = $fmt->getSymbol(NumberFormatter::MONETARY_SEPARATOR_SYMBOL);
|
||||
$info['mon_thousands_sep'] = $fmt->getSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL);
|
||||
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class RemoteUserGuard implements Guard
|
||||
$header = config('auth.guard_email');
|
||||
|
||||
if (null !== $header) {
|
||||
$emailAddress = (string) (request()->server($header) ?? apache_request_headers()[$header] ?? null);
|
||||
$emailAddress = (string)(request()->server($header) ?? apache_request_headers()[$header] ?? null);
|
||||
$preference = Preferences::getForUser($retrievedUser, 'remote_guard_alt_email');
|
||||
|
||||
if ('' !== $emailAddress && null === $preference && $emailAddress !== $userID) {
|
||||
@@ -102,13 +102,6 @@ class RemoteUserGuard implements Guard
|
||||
$this->user = $retrievedUser;
|
||||
}
|
||||
|
||||
public function guest(): bool
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
|
||||
return !$this->check();
|
||||
}
|
||||
|
||||
public function check(): bool
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
@@ -116,17 +109,11 @@ class RemoteUserGuard implements Guard
|
||||
return $this->user() instanceof User;
|
||||
}
|
||||
|
||||
public function user(): ?User
|
||||
public function guest(): bool
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
$user = $this->user;
|
||||
if (!$user instanceof User) {
|
||||
Log::debug('User is NULL');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $user;
|
||||
return !$this->check();
|
||||
}
|
||||
|
||||
public function hasUser(): bool
|
||||
@@ -157,6 +144,19 @@ class RemoteUserGuard implements Guard
|
||||
Log::error(sprintf('Did not set user at %s', __METHOD__));
|
||||
}
|
||||
|
||||
public function user(): ?User
|
||||
{
|
||||
Log::debug(sprintf('Now at %s', __METHOD__));
|
||||
$user = $this->user;
|
||||
if (!$user instanceof User) {
|
||||
Log::debug('User is NULL');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*
|
||||
|
||||
@@ -59,8 +59,8 @@ class Balance
|
||||
|
||||
$result = $query->get(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.balance_after']);
|
||||
foreach ($result as $entry) {
|
||||
$accountId = (int) $entry->account_id;
|
||||
$currencyId = (int) $entry->transaction_currency_id;
|
||||
$accountId = (int)$entry->account_id;
|
||||
$currencyId = (int)$entry->transaction_currency_id;
|
||||
$currencies[$currencyId] ??= Amount::getTransactionCurrencyById($currencyId);
|
||||
$return[$accountId] ??= [];
|
||||
if (array_key_exists($currencyId, $return[$accountId])) {
|
||||
|
||||
@@ -68,7 +68,7 @@ class TagList implements BinderInterface
|
||||
|
||||
return true;
|
||||
}
|
||||
if (in_array((string) $tag->id, $list, true)) {
|
||||
if (in_array((string)$tag->id, $list, true)) {
|
||||
Log::debug(sprintf('TagList: (id) found tag #%d ("%s") in list.', $tag->id, $tag->tag));
|
||||
|
||||
return true;
|
||||
|
||||
@@ -42,7 +42,7 @@ class TagOrId implements BinderInterface
|
||||
|
||||
$result = $repository->findByTag($value);
|
||||
if (null === $result) {
|
||||
$result = $repository->find((int) $value);
|
||||
$result = $repository->find((int)$value);
|
||||
}
|
||||
if (null !== $result) {
|
||||
return $result;
|
||||
|
||||
@@ -41,7 +41,7 @@ class UserGroupAccount implements BinderInterface
|
||||
if (auth()->check()) {
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$account = Account::where('id', (int) $value)
|
||||
$account = Account::where('id', (int)$value)
|
||||
->where('user_group_id', $user->user_group_id)
|
||||
->first()
|
||||
;
|
||||
|
||||
@@ -41,7 +41,7 @@ class UserGroupBill implements BinderInterface
|
||||
if (auth()->check()) {
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$currency = Bill::where('id', (int) $value)
|
||||
$currency = Bill::where('id', (int)$value)
|
||||
->where('user_group_id', $user->user_group_id)
|
||||
->first()
|
||||
;
|
||||
|
||||
@@ -38,7 +38,7 @@ class UserGroupExchangeRate implements BinderInterface
|
||||
if (auth()->check()) {
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$rate = CurrencyExchangeRate::where('id', (int) $value)
|
||||
$rate = CurrencyExchangeRate::where('id', (int)$value)
|
||||
->where('user_group_id', $user->user_group_id)
|
||||
->first()
|
||||
;
|
||||
|
||||
@@ -38,7 +38,7 @@ class UserGroupTransaction implements BinderInterface
|
||||
if (auth()->check()) {
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$group = TransactionGroup::where('id', (int) $value)
|
||||
$group = TransactionGroup::where('id', (int)$value)
|
||||
->where('user_group_id', $user->user_group_id)
|
||||
->first()
|
||||
;
|
||||
|
||||
@@ -78,6 +78,14 @@ class CacheProperties
|
||||
return Cache::has($this->hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
*/
|
||||
public function store($data): void
|
||||
{
|
||||
Cache::forever($this->hash, $data);
|
||||
}
|
||||
|
||||
private function hash(): void
|
||||
{
|
||||
$content = '';
|
||||
@@ -86,17 +94,9 @@ class CacheProperties
|
||||
$content = sprintf('%s%s', $content, json_encode($property, JSON_THROW_ON_ERROR));
|
||||
} catch (JsonException) {
|
||||
// @ignoreException
|
||||
$content = sprintf('%s%s', $content, hash('sha256', (string) Carbon::now()->getTimestamp()));
|
||||
$content = sprintf('%s%s', $content, hash('sha256', (string)Carbon::now()->getTimestamp()));
|
||||
}
|
||||
}
|
||||
$this->hash = substr(hash('sha256', $content), 0, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
*/
|
||||
public function store($data): void
|
||||
{
|
||||
Cache::forever($this->hash, $data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,27 +37,6 @@ class Calculator
|
||||
private static ?SplObjectStorage $intervalMap = null; // @phpstan-ignore-line
|
||||
private static array $intervals = [];
|
||||
|
||||
/**
|
||||
* @throws IntervalException
|
||||
*/
|
||||
public function nextDateByInterval(Carbon $epoch, Periodicity $periodicity, int $skipInterval = 0): Carbon
|
||||
{
|
||||
if (!self::isAvailablePeriodicity($periodicity)) {
|
||||
throw IntervalException::unavailable($periodicity, self::$intervals);
|
||||
}
|
||||
|
||||
/** @var Periodicity\Interval $periodicity */
|
||||
$periodicity = self::$intervalMap->offsetGet($periodicity);
|
||||
$interval = $this->skipInterval($skipInterval);
|
||||
|
||||
return $periodicity->nextDate($epoch->clone(), $interval);
|
||||
}
|
||||
|
||||
public function isAvailablePeriodicity(Periodicity $periodicity): bool
|
||||
{
|
||||
return self::containsInterval($periodicity);
|
||||
}
|
||||
|
||||
private static function containsInterval(Periodicity $periodicity): bool
|
||||
{
|
||||
return self::loadIntervalMap()->contains($periodicity);
|
||||
@@ -78,6 +57,27 @@ class Calculator
|
||||
return self::$intervalMap;
|
||||
}
|
||||
|
||||
public function isAvailablePeriodicity(Periodicity $periodicity): bool
|
||||
{
|
||||
return self::containsInterval($periodicity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IntervalException
|
||||
*/
|
||||
public function nextDateByInterval(Carbon $epoch, Periodicity $periodicity, int $skipInterval = 0): Carbon
|
||||
{
|
||||
if (!self::isAvailablePeriodicity($periodicity)) {
|
||||
throw IntervalException::unavailable($periodicity, self::$intervals);
|
||||
}
|
||||
|
||||
/** @var Periodicity\Interval $periodicity */
|
||||
$periodicity = self::$intervalMap->offsetGet($periodicity);
|
||||
$interval = $this->skipInterval($skipInterval);
|
||||
|
||||
return $periodicity->nextDate($epoch->clone(), $interval);
|
||||
}
|
||||
|
||||
private function skipInterval(int $skip): int
|
||||
{
|
||||
return self::DEFAULT_INTERVAL + $skip;
|
||||
|
||||
@@ -69,9 +69,9 @@ class FrontpageChartGenerator
|
||||
Log::debug('Now in generate for budget chart.');
|
||||
$budgets = $this->budgetRepository->getActiveBudgets();
|
||||
$data = [
|
||||
['label' => (string) trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'],
|
||||
['label' => (string) trans('firefly.left_to_spend'), 'entries' => [], 'type' => 'bar'],
|
||||
['label' => (string) trans('firefly.overspent'), 'entries' => [], 'type' => 'bar'],
|
||||
['label' => (string)trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'],
|
||||
['label' => (string)trans('firefly.left_to_spend'), 'entries' => [], 'type' => 'bar'],
|
||||
['label' => (string)trans('firefly.overspent'), 'entries' => [], 'type' => 'bar'],
|
||||
];
|
||||
|
||||
// loop al budgets:
|
||||
@@ -84,6 +84,64 @@ class FrontpageChartGenerator
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function setEnd(Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function setStart(Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
/**
|
||||
* A basic setter for the user. Also updates the repositories with the right user.
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->budgetRepository->setUser($user);
|
||||
$this->blRepository->setUser($user);
|
||||
$this->opsRepository->setUser($user);
|
||||
|
||||
$locale = app('steam')->getLocale();
|
||||
$this->monthAndDayFormat = (string)trans('config.month_and_day_js', [], $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* If a budget has budget limit, each limit is processed individually.
|
||||
*/
|
||||
private function budgetLimits(array $data, Budget $budget, Collection $limits): array
|
||||
{
|
||||
Log::debug('Start processing budget limits.');
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($limits as $limit) {
|
||||
$data = $this->processLimit($data, $budget, $limit);
|
||||
}
|
||||
Log::debug('Done processing budget limits.');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* When no limits are present, the expenses of the whole period are collected and grouped.
|
||||
* This is grouped per currency. Because there is no limit set, "left to spend" and "overspent" are empty.
|
||||
*/
|
||||
private function noBudgetLimits(array $data, Budget $budget): array
|
||||
{
|
||||
$spent = $this->opsRepository->sumExpenses($this->start, $this->end, null, new Collection()->push($budget));
|
||||
|
||||
/** @var array $entry */
|
||||
foreach ($spent as $entry) {
|
||||
$title = sprintf('%s (%s)', $budget->name, $entry['currency_name']);
|
||||
$data[0]['entries'][$title] = bcmul((string)$entry['sum'], '-1'); // spent
|
||||
$data[1]['entries'][$title] = 0; // left to spend
|
||||
$data[2]['entries'][$title] = 0; // overspent
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each budget, gets all budget limits for the current time range.
|
||||
* When no limits are present, the time range is used to collect information on money spent.
|
||||
@@ -108,41 +166,6 @@ class FrontpageChartGenerator
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* When no limits are present, the expenses of the whole period are collected and grouped.
|
||||
* This is grouped per currency. Because there is no limit set, "left to spend" and "overspent" are empty.
|
||||
*/
|
||||
private function noBudgetLimits(array $data, Budget $budget): array
|
||||
{
|
||||
$spent = $this->opsRepository->sumExpenses($this->start, $this->end, null, new Collection()->push($budget));
|
||||
|
||||
/** @var array $entry */
|
||||
foreach ($spent as $entry) {
|
||||
$title = sprintf('%s (%s)', $budget->name, $entry['currency_name']);
|
||||
$data[0]['entries'][$title] = bcmul((string) $entry['sum'], '-1'); // spent
|
||||
$data[1]['entries'][$title] = 0; // left to spend
|
||||
$data[2]['entries'][$title] = 0; // overspent
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a budget has budget limit, each limit is processed individually.
|
||||
*/
|
||||
private function budgetLimits(array $data, Budget $budget, Collection $limits): array
|
||||
{
|
||||
Log::debug('Start processing budget limits.');
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($limits as $limit) {
|
||||
$data = $this->processLimit($data, $budget, $limit);
|
||||
}
|
||||
Log::debug('Done processing budget limits.');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each limit, the expenses from the time range of the limit are collected. Each row from the result is
|
||||
* processed individually.
|
||||
@@ -204,14 +227,14 @@ class FrontpageChartGenerator
|
||||
Log::debug(sprintf('Amount is now "%s".', $amount));
|
||||
}
|
||||
$amount ??= '0';
|
||||
$sumSpent = bcmul((string) $entry['sum'], '-1'); // spent
|
||||
$sumSpent = bcmul((string)$entry['sum'], '-1'); // spent
|
||||
$data[0]['entries'][$title] ??= '0';
|
||||
$data[1]['entries'][$title] ??= '0';
|
||||
$data[2]['entries'][$title] ??= '0';
|
||||
|
||||
$data[0]['entries'][$title] = bcadd((string) $data[0]['entries'][$title], 1 === bccomp($sumSpent, $amount) ? $amount : $sumSpent); // spent
|
||||
$data[1]['entries'][$title] = bcadd((string) $data[1]['entries'][$title], 1 === bccomp($amount, $sumSpent) ? bcadd((string) $entry['sum'], $amount) : '0'); // left to spent
|
||||
$data[2]['entries'][$title] = bcadd((string) $data[2]['entries'][$title], 1 === bccomp($amount, $sumSpent) ? '0' : bcmul(bcadd((string) $entry['sum'], $amount), '-1')); // overspent
|
||||
$data[0]['entries'][$title] = bcadd((string)$data[0]['entries'][$title], 1 === bccomp($sumSpent, $amount) ? $amount : $sumSpent); // spent
|
||||
$data[1]['entries'][$title] = bcadd((string)$data[1]['entries'][$title], 1 === bccomp($amount, $sumSpent) ? bcadd((string)$entry['sum'], $amount) : '0'); // left to spent
|
||||
$data[2]['entries'][$title] = bcadd((string)$data[2]['entries'][$title], 1 === bccomp($amount, $sumSpent) ? '0' : bcmul(bcadd((string)$entry['sum'], $amount), '-1')); // overspent
|
||||
|
||||
Log::debug(sprintf('Amount [spent] is now %s.', $data[0]['entries'][$title]));
|
||||
Log::debug(sprintf('Amount [left] is now %s.', $data[1]['entries'][$title]));
|
||||
@@ -219,27 +242,4 @@ class FrontpageChartGenerator
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function setEnd(Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function setStart(Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
/**
|
||||
* A basic setter for the user. Also updates the repositories with the right user.
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->budgetRepository->setUser($user);
|
||||
$this->blRepository->setUser($user);
|
||||
$this->opsRepository->setUser($user);
|
||||
|
||||
$locale = app('steam')->getLocale();
|
||||
$this->monthAndDayFormat = (string) trans('config.month_and_day_js', [], $locale);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace FireflyIII\Support\Chart\Category;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
@@ -96,6 +95,30 @@ class FrontpageChartGenerator
|
||||
];
|
||||
}
|
||||
|
||||
private function collectExpensesAll(Collection $categories, Collection $accounts): array
|
||||
{
|
||||
Log::debug(sprintf('Collect expenses for %d category(ies).', count($categories)));
|
||||
$spent = $this->opsRepos->collectExpenses($this->start, $this->end, $accounts, $categories);
|
||||
$tempData = [];
|
||||
foreach ($categories as $category) {
|
||||
$sums = $this->opsRepos->sumCollectedTransactionsByCategory($spent, $category, 'negative', $this->convertToPrimary);
|
||||
if (0 === count($sums)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($sums as $currency) {
|
||||
$this->addCurrency($currency);
|
||||
$tempData[] = [
|
||||
'name' => $category->name,
|
||||
'sum' => $currency['sum'],
|
||||
'sum_float' => round((float)$currency['sum'], $currency['currency_decimal_places']),
|
||||
'currency_id' => (int)$currency['currency_id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $tempData;
|
||||
}
|
||||
|
||||
private function collectNoCatExpenses(Collection $accounts): array
|
||||
{
|
||||
$noCatExp = $this->noCatRepos->sumExpenses($this->start, $this->end, $accounts);
|
||||
@@ -147,28 +170,4 @@ class FrontpageChartGenerator
|
||||
|
||||
return $currencyData;
|
||||
}
|
||||
|
||||
private function collectExpensesAll(Collection $categories, Collection $accounts): array
|
||||
{
|
||||
Log::debug(sprintf('Collect expenses for %d category(ies).', count($categories)));
|
||||
$spent = $this->opsRepos->collectExpenses($this->start, $this->end, $accounts, $categories);
|
||||
$tempData = [];
|
||||
foreach ($categories as $category) {
|
||||
$sums = $this->opsRepos->sumCollectedTransactionsByCategory($spent, $category, 'negative', $this->convertToPrimary);
|
||||
if (0 === count($sums)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($sums as $currency) {
|
||||
$this->addCurrency($currency);
|
||||
$tempData[] = [
|
||||
'name' => $category->name,
|
||||
'sum' => $currency['sum'],
|
||||
'sum_float' => round((float)$currency['sum'], $currency['currency_decimal_places']),
|
||||
'currency_id' => (int)$currency['currency_id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $tempData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,14 +73,14 @@ class WholePeriodChartGenerator
|
||||
$code = $currency['currency_code'];
|
||||
$name = $currency['currency_name'];
|
||||
$chartData[sprintf('spent-in-%s', $code)] = [
|
||||
'label' => (string) trans('firefly.box_spent_in_currency', ['currency' => $name]),
|
||||
'label' => (string)trans('firefly.box_spent_in_currency', ['currency' => $name]),
|
||||
'entries' => [],
|
||||
'type' => 'bar',
|
||||
'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red
|
||||
];
|
||||
|
||||
$chartData[sprintf('earned-in-%s', $code)] = [
|
||||
'label' => (string) trans('firefly.box_earned_in_currency', ['currency' => $name]),
|
||||
'label' => (string)trans('firefly.box_earned_in_currency', ['currency' => $name]),
|
||||
'entries' => [],
|
||||
'type' => 'bar',
|
||||
'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green
|
||||
|
||||
@@ -44,10 +44,10 @@ class ChartData
|
||||
public function add(array $data): void
|
||||
{
|
||||
if (array_key_exists('currency_id', $data)) {
|
||||
$data['currency_id'] = (string) $data['currency_id'];
|
||||
$data['currency_id'] = (string)$data['currency_id'];
|
||||
}
|
||||
if (array_key_exists('primary_currency_id', $data)) {
|
||||
$data['primary_currency_id'] = (string) $data['primary_currency_id'];
|
||||
$data['primary_currency_id'] = (string)$data['primary_currency_id'];
|
||||
}
|
||||
$required = ['start', 'date', 'end', 'entries'];
|
||||
foreach ($required as $field) {
|
||||
|
||||
@@ -39,7 +39,7 @@ class AutoBudgetCronjob extends AbstractCronjob
|
||||
{
|
||||
/** @var Configuration $config */
|
||||
$config = FireflyConfig::get('last_ab_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$lastTime = (int)$config->data;
|
||||
$diff = now(config('app.timezone'))->getTimestamp() - $lastTime;
|
||||
$diffForHumans = now(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
if (0 === $lastTime) {
|
||||
@@ -80,7 +80,7 @@ class AutoBudgetCronjob extends AbstractCronjob
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Auto-budget cron job fired successfully.';
|
||||
|
||||
FireflyConfig::set('last_ab_job', (int) $this->date->format('U'));
|
||||
FireflyConfig::set('last_ab_job', (int)$this->date->format('U'));
|
||||
Log::info('Done with auto budget cron job task.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class BillWarningCronjob extends AbstractCronjob
|
||||
|
||||
/** @var Configuration $config */
|
||||
$config = FireflyConfig::get('last_bw_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$lastTime = (int)$config->data;
|
||||
$diff = now(config('app.timezone'))->getTimestamp() - $lastTime;
|
||||
$diffForHumans = now(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
|
||||
@@ -93,8 +93,8 @@ class BillWarningCronjob extends AbstractCronjob
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Bill notification cron job fired successfully.';
|
||||
|
||||
FireflyConfig::set('last_bw_job', (int) $this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
|
||||
FireflyConfig::set('last_bw_job', (int)$this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int)$this->date->format('U')));
|
||||
Log::info('Done with bill notification cron job task.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class ExchangeRatesCronjob extends AbstractCronjob
|
||||
{
|
||||
/** @var Configuration $config */
|
||||
$config = FireflyConfig::get('last_cer_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$lastTime = (int)$config->data;
|
||||
$diff = now(config('app.timezone'))->getTimestamp() - $lastTime;
|
||||
$diffForHumans = now(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
if (0 === $lastTime) {
|
||||
@@ -81,7 +81,7 @@ class ExchangeRatesCronjob extends AbstractCronjob
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Exchange rates cron job fired successfully.';
|
||||
|
||||
FireflyConfig::set('last_cer_job', (int) $this->date->format('U'));
|
||||
FireflyConfig::set('last_cer_job', (int)$this->date->format('U'));
|
||||
Log::info('Done with exchange rates job task.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class RecurringCronjob extends AbstractCronjob
|
||||
|
||||
/** @var Configuration $config */
|
||||
$config = FireflyConfig::get('last_rt_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$lastTime = (int)$config->data;
|
||||
$diff = now(config('app.timezone'))->getTimestamp() - $lastTime;
|
||||
$diffForHumans = now(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
|
||||
@@ -90,8 +90,8 @@ class RecurringCronjob extends AbstractCronjob
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Recurring transactions cron job fired successfully.';
|
||||
|
||||
FireflyConfig::set('last_rt_job', (int) $this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
|
||||
FireflyConfig::set('last_rt_job', (int)$this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int)$this->date->format('U')));
|
||||
Log::info('Done with recurring cron job task.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class UpdateCheckCronjob extends AbstractCronjob
|
||||
|
||||
// should not check for updates:
|
||||
$permission = FireflyConfig::get('permission_update_check', -1);
|
||||
$value = (int) $permission->data;
|
||||
$value = (int)$permission->data;
|
||||
if (1 !== $value) {
|
||||
Log::debug('Update check is not enabled.');
|
||||
// get stuff from job:
|
||||
|
||||
@@ -45,7 +45,7 @@ class WebhookCronjob extends AbstractCronjob
|
||||
|
||||
/** @var Configuration $config */
|
||||
$config = FireflyConfig::get('last_webhook_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$lastTime = (int)$config->data;
|
||||
$diff = now(config('app.timezone'))->getTimestamp() - $lastTime;
|
||||
$diffForHumans = now(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
|
||||
@@ -90,8 +90,8 @@ class WebhookCronjob extends AbstractCronjob
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Send webhook messages cron job fired successfully.';
|
||||
|
||||
FireflyConfig::set('last_webhook_job', (int) $this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
|
||||
FireflyConfig::set('last_webhook_job', (int)$this->date->format('U'));
|
||||
Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int)$this->date->format('U')));
|
||||
Log::info('Done with webhook cron job task.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class Timer
|
||||
{
|
||||
private array $times = [];
|
||||
private static ?Timer $instance = null;
|
||||
private array $times = [];
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
@@ -38,7 +38,7 @@ class Timer
|
||||
|
||||
public static function getInstance(): self
|
||||
{
|
||||
if (null === self::$instance) {
|
||||
if (!self::$instance instanceof self) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Support\Form\FormSupport;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Throwable;
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class ExportDataGenerator
|
||||
private bool $exportTransactions;
|
||||
private Carbon $start;
|
||||
private User $user;
|
||||
private UserGroup $userGroup; // @phpstan-ignore-line
|
||||
private UserGroup $userGroup; // @phpstan-ignore-line
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -141,6 +141,92 @@ class ExportDataGenerator
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function has(mixed $key): mixed
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setAccounts(Collection $accounts): void
|
||||
{
|
||||
$this->accounts = $accounts;
|
||||
}
|
||||
|
||||
public function setEnd(Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function setExportAccounts(bool $exportAccounts): void
|
||||
{
|
||||
$this->exportAccounts = $exportAccounts;
|
||||
}
|
||||
|
||||
public function setExportBills(bool $exportBills): void
|
||||
{
|
||||
$this->exportBills = $exportBills;
|
||||
}
|
||||
|
||||
public function setExportBudgets(bool $exportBudgets): void
|
||||
{
|
||||
$this->exportBudgets = $exportBudgets;
|
||||
}
|
||||
|
||||
public function setExportCategories(bool $exportCategories): void
|
||||
{
|
||||
$this->exportCategories = $exportCategories;
|
||||
}
|
||||
|
||||
public function setExportPiggies(bool $exportPiggies): void
|
||||
{
|
||||
$this->exportPiggies = $exportPiggies;
|
||||
}
|
||||
|
||||
public function setExportRecurring(bool $exportRecurring): void
|
||||
{
|
||||
$this->exportRecurring = $exportRecurring;
|
||||
}
|
||||
|
||||
public function setExportRules(bool $exportRules): void
|
||||
{
|
||||
$this->exportRules = $exportRules;
|
||||
}
|
||||
|
||||
public function setExportTags(bool $exportTags): void
|
||||
{
|
||||
$this->exportTags = $exportTags;
|
||||
}
|
||||
|
||||
public function setExportTransactions(bool $exportTransactions): void
|
||||
{
|
||||
$this->exportTransactions = $exportTransactions;
|
||||
}
|
||||
|
||||
public function setStart(Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CannotInsertRecord
|
||||
* @throws Exception
|
||||
@@ -222,11 +308,6 @@ class ExportDataGenerator
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CannotInsertRecord
|
||||
* @throws Exception
|
||||
@@ -588,14 +669,6 @@ class ExportDataGenerator
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function get(string $key, mixed $default = null): mixed
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws CannotInsertRecord
|
||||
* @throws Exception
|
||||
@@ -828,11 +901,6 @@ class ExportDataGenerator
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function setAccounts(Collection $accounts): void
|
||||
{
|
||||
$this->accounts = $accounts;
|
||||
}
|
||||
|
||||
private function mergeTags(array $tags): string
|
||||
{
|
||||
if (0 === count($tags)) {
|
||||
@@ -845,72 +913,4 @@ class ExportDataGenerator
|
||||
|
||||
return implode(',', $smol);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
|
||||
*/
|
||||
public function has(mixed $key): mixed
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setEnd(Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function setExportAccounts(bool $exportAccounts): void
|
||||
{
|
||||
$this->exportAccounts = $exportAccounts;
|
||||
}
|
||||
|
||||
public function setExportBills(bool $exportBills): void
|
||||
{
|
||||
$this->exportBills = $exportBills;
|
||||
}
|
||||
|
||||
public function setExportBudgets(bool $exportBudgets): void
|
||||
{
|
||||
$this->exportBudgets = $exportBudgets;
|
||||
}
|
||||
|
||||
public function setExportCategories(bool $exportCategories): void
|
||||
{
|
||||
$this->exportCategories = $exportCategories;
|
||||
}
|
||||
|
||||
public function setExportPiggies(bool $exportPiggies): void
|
||||
{
|
||||
$this->exportPiggies = $exportPiggies;
|
||||
}
|
||||
|
||||
public function setExportRecurring(bool $exportRecurring): void
|
||||
{
|
||||
$this->exportRecurring = $exportRecurring;
|
||||
}
|
||||
|
||||
public function setExportRules(bool $exportRules): void
|
||||
{
|
||||
$this->exportRules = $exportRules;
|
||||
}
|
||||
|
||||
public function setExportTags(bool $exportTags): void
|
||||
{
|
||||
$this->exportTags = $exportTags;
|
||||
}
|
||||
|
||||
public function setExportTransactions(bool $exportTransactions): void
|
||||
{
|
||||
$this->exportTransactions = $exportTransactions;
|
||||
}
|
||||
|
||||
public function setStart(Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
@@ -30,7 +31,6 @@ use Illuminate\Contracts\Encryption\EncryptException;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class FireflyConfig.
|
||||
@@ -46,34 +46,6 @@ class FireflyConfig
|
||||
Configuration::where('name', $name)->forceDelete();
|
||||
}
|
||||
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return 1 === Configuration::where('name', $name)->count();
|
||||
}
|
||||
|
||||
public function getEncrypted(string $name, mixed $default = null): ?Configuration
|
||||
{
|
||||
$result = $this->get($name, $default);
|
||||
if (!$result instanceof Configuration) {
|
||||
return null;
|
||||
}
|
||||
if ('' === $result->data) {
|
||||
Log::warning(sprintf('Empty encrypted configuration value found: "%s"', $name));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
try {
|
||||
$result->data = decrypt($result->data);
|
||||
} catch (DecryptException $e) {
|
||||
Log::error(sprintf('Could not decrypt configuration value "%s": %s', $name, $e->getMessage()));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|bool|int|string $default
|
||||
*
|
||||
@@ -106,6 +78,56 @@ class FireflyConfig
|
||||
return $this->set($name, $default);
|
||||
}
|
||||
|
||||
public function getEncrypted(string $name, mixed $default = null): ?Configuration
|
||||
{
|
||||
$result = $this->get($name, $default);
|
||||
if (!$result instanceof Configuration) {
|
||||
return null;
|
||||
}
|
||||
if ('' === $result->data) {
|
||||
Log::warning(sprintf('Empty encrypted configuration value found: "%s"', $name));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
try {
|
||||
$result->data = decrypt($result->data);
|
||||
} catch (DecryptException $e) {
|
||||
Log::error(sprintf('Could not decrypt configuration value "%s": %s', $name, $e->getMessage()));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getFresh(string $name, mixed $default = null): ?Configuration
|
||||
{
|
||||
$config = Configuration::where('name', $name)->first(['id', 'name', 'data']);
|
||||
if (null !== $config) {
|
||||
return $config;
|
||||
}
|
||||
// no preference found and default is null:
|
||||
if (null === $default) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->set($name, $default);
|
||||
}
|
||||
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return 1 === Configuration::where('name', $name)->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function put(string $name, $value): Configuration
|
||||
{
|
||||
return $this->set($name, $value);
|
||||
}
|
||||
|
||||
public function set(string $name, mixed $value): Configuration
|
||||
{
|
||||
try {
|
||||
@@ -135,28 +157,6 @@ class FireflyConfig
|
||||
return $config;
|
||||
}
|
||||
|
||||
public function getFresh(string $name, mixed $default = null): ?Configuration
|
||||
{
|
||||
$config = Configuration::where('name', $name)->first(['id', 'name', 'data']);
|
||||
if (null !== $config) {
|
||||
return $config;
|
||||
}
|
||||
// no preference found and default is null:
|
||||
if (null === $default) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->set($name, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function put(string $name, $value): Configuration
|
||||
{
|
||||
return $this->set($name, $value);
|
||||
}
|
||||
|
||||
public function setEncrypted(string $name, mixed $value): Configuration
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -51,43 +51,12 @@ class AccountForm
|
||||
$repository = $this->getAccountRepository();
|
||||
$grouped = $this->getAccountsGrouped($types, $repository);
|
||||
$cash = $repository->getCashAccount();
|
||||
$key = (string) trans('firefly.cash_account_type');
|
||||
$grouped[$key][$cash->id] = sprintf('(%s)', (string) trans('firefly.cash'));
|
||||
$key = (string)trans('firefly.cash_account_type');
|
||||
$grouped[$key][$cash->id] = sprintf('(%s)', (string)trans('firefly.cash'));
|
||||
|
||||
return $this->select($name, $grouped, $value, $options);
|
||||
}
|
||||
|
||||
private function getAccountsGrouped(array $types, ?AccountRepositoryInterface $repository = null): array
|
||||
{
|
||||
if (!$repository instanceof AccountRepositoryInterface) {
|
||||
$repository = $this->getAccountRepository();
|
||||
}
|
||||
$accountList = $repository->getActiveAccountsByType($types);
|
||||
$liabilityTypes = [AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::LOAN->value];
|
||||
$grouped = [];
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accountList as $account) {
|
||||
$role = (string) $repository->getMetaValue($account, 'account_role');
|
||||
if (in_array($account->accountType->type, $liabilityTypes, true)) {
|
||||
$role = sprintf('l_%s', $account->accountType->type);
|
||||
}
|
||||
if ('' === $role) {
|
||||
$role = 'no_account_type';
|
||||
if (AccountTypeEnum::EXPENSE->value === $account->accountType->type) {
|
||||
$role = 'expense_account';
|
||||
}
|
||||
if (AccountTypeEnum::REVENUE->value === $account->accountType->type) {
|
||||
$role = 'revenue_account';
|
||||
}
|
||||
}
|
||||
$key = (string) trans(sprintf('firefly.opt_group_%s', $role));
|
||||
$grouped[$key][$account->id] = $account->name;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouped dropdown list of all accounts that are valid as the destination of a withdrawal.
|
||||
*/
|
||||
@@ -98,8 +67,8 @@ class AccountForm
|
||||
$grouped = $this->getAccountsGrouped($types, $repository);
|
||||
|
||||
$cash = $repository->getCashAccount();
|
||||
$key = (string) trans('firefly.cash_account_type');
|
||||
$grouped[$key][$cash->id] = sprintf('(%s)', (string) trans('firefly.cash'));
|
||||
$key = (string)trans('firefly.cash_account_type');
|
||||
$grouped[$key][$cash->id] = sprintf('(%s)', (string)trans('firefly.cash'));
|
||||
|
||||
return $this->select($name, $grouped, $value, $options);
|
||||
}
|
||||
@@ -173,4 +142,35 @@ class AccountForm
|
||||
|
||||
return $this->select($name, $grouped, $value, $options);
|
||||
}
|
||||
|
||||
private function getAccountsGrouped(array $types, ?AccountRepositoryInterface $repository = null): array
|
||||
{
|
||||
if (!$repository instanceof AccountRepositoryInterface) {
|
||||
$repository = $this->getAccountRepository();
|
||||
}
|
||||
$accountList = $repository->getActiveAccountsByType($types);
|
||||
$liabilityTypes = [AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::LOAN->value];
|
||||
$grouped = [];
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accountList as $account) {
|
||||
$role = (string)$repository->getMetaValue($account, 'account_role');
|
||||
if (in_array($account->accountType->type, $liabilityTypes, true)) {
|
||||
$role = sprintf('l_%s', $account->accountType->type);
|
||||
}
|
||||
if ('' === $role) {
|
||||
$role = 'no_account_type';
|
||||
if (AccountTypeEnum::EXPENSE->value === $account->accountType->type) {
|
||||
$role = 'expense_account';
|
||||
}
|
||||
if (AccountTypeEnum::REVENUE->value === $account->accountType->type) {
|
||||
$role = 'revenue_account';
|
||||
}
|
||||
}
|
||||
$key = (string)trans(sprintf('firefly.opt_group_%s', $role));
|
||||
$grouped[$key][$account->id] = $account->name;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,60 +49,6 @@ class CurrencyForm
|
||||
return $this->currencyField($name, 'amount', $value, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param view-string $view
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function currencyField(string $name, string $view, mixed $value = null, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
$classes = $this->getHolderClasses($name);
|
||||
$value = $this->fillFieldValue($name, $value);
|
||||
$options['step'] = 'any';
|
||||
$primaryCurrency = $options['currency'] ?? app('amount')->getPrimaryCurrency();
|
||||
|
||||
/** @var Collection $currencies */
|
||||
$currencies = app('amount')->getCurrencies();
|
||||
unset($options['currency'], $options['placeholder']);
|
||||
// perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount)
|
||||
$preFilled = session('preFilled');
|
||||
if (!is_array($preFilled)) {
|
||||
$preFilled = [];
|
||||
}
|
||||
$key = 'amount_currency_id_'.$name;
|
||||
$sentCurrencyId = array_key_exists($key, $preFilled) ? (int) $preFilled[$key] : $primaryCurrency->id;
|
||||
|
||||
app('log')->debug(sprintf('Sent currency ID is %d', $sentCurrencyId));
|
||||
|
||||
// find this currency in set of currencies:
|
||||
foreach ($currencies as $currency) {
|
||||
if ($currency->id === $sentCurrencyId) {
|
||||
$primaryCurrency = $currency;
|
||||
app('log')->debug(sprintf('default currency is now %s', $primaryCurrency->code));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure value is formatted nicely:
|
||||
if (null !== $value && '' !== $value) {
|
||||
$value = app('steam')->bcround($value, $primaryCurrency->decimal_places);
|
||||
}
|
||||
|
||||
try {
|
||||
$html = view('form.'.$view, compact('primaryCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
|
||||
} catch (Throwable $e) {
|
||||
app('log')->debug(sprintf('Could not render currencyField(): %s', $e->getMessage()));
|
||||
$html = 'Could not render currencyField.';
|
||||
|
||||
throw new FireflyException($html, 0, $e);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO describe and cleanup.
|
||||
*
|
||||
@@ -115,63 +61,6 @@ class CurrencyForm
|
||||
return $this->allCurrencyField($name, 'balance', $value, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO describe and cleanup
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function allCurrencyField(string $name, string $view, $value = null, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
$classes = $this->getHolderClasses($name);
|
||||
$value = $this->fillFieldValue($name, $value);
|
||||
$options['step'] = 'any';
|
||||
$primaryCurrency = $options['currency'] ?? app('amount')->getPrimaryCurrency();
|
||||
|
||||
/** @var Collection $currencies */
|
||||
$currencies = app('amount')->getAllCurrencies();
|
||||
unset($options['currency'], $options['placeholder']);
|
||||
|
||||
// perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount)
|
||||
$preFilled = session('preFilled');
|
||||
if (!is_array($preFilled)) {
|
||||
$preFilled = [];
|
||||
}
|
||||
$key = 'amount_currency_id_'.$name;
|
||||
$sentCurrencyId = array_key_exists($key, $preFilled) ? (int) $preFilled[$key] : $primaryCurrency->id;
|
||||
|
||||
app('log')->debug(sprintf('Sent currency ID is %d', $sentCurrencyId));
|
||||
|
||||
// find this currency in set of currencies:
|
||||
foreach ($currencies as $currency) {
|
||||
if ($currency->id === $sentCurrencyId) {
|
||||
$primaryCurrency = $currency;
|
||||
app('log')->debug(sprintf('default currency is now %s', $primaryCurrency->code));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure value is formatted nicely:
|
||||
if (null !== $value && '' !== $value) {
|
||||
$value = app('steam')->bcround($value, $primaryCurrency->decimal_places);
|
||||
}
|
||||
|
||||
try {
|
||||
$html = view('form.'.$view, compact('primaryCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
|
||||
} catch (Throwable $e) {
|
||||
app('log')->debug(sprintf('Could not render currencyField(): %s', $e->getMessage()));
|
||||
$html = 'Could not render currencyField.';
|
||||
|
||||
throw new FireflyException($html, 0, $e);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO cleanup and describe
|
||||
*
|
||||
@@ -207,7 +96,7 @@ class CurrencyForm
|
||||
// get all currencies:
|
||||
$list = $currencyRepos->get();
|
||||
$array = [
|
||||
0 => (string) trans('firefly.no_currency'),
|
||||
0 => (string)trans('firefly.no_currency'),
|
||||
];
|
||||
|
||||
/** @var TransactionCurrency $currency */
|
||||
@@ -217,4 +106,115 @@ class CurrencyForm
|
||||
|
||||
return $this->select($name, $array, $value, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO describe and cleanup
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function allCurrencyField(string $name, string $view, $value = null, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
$classes = $this->getHolderClasses($name);
|
||||
$value = $this->fillFieldValue($name, $value);
|
||||
$options['step'] = 'any';
|
||||
$primaryCurrency = $options['currency'] ?? app('amount')->getPrimaryCurrency();
|
||||
|
||||
/** @var Collection $currencies */
|
||||
$currencies = app('amount')->getAllCurrencies();
|
||||
unset($options['currency'], $options['placeholder']);
|
||||
|
||||
// perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount)
|
||||
$preFilled = session('preFilled');
|
||||
if (!is_array($preFilled)) {
|
||||
$preFilled = [];
|
||||
}
|
||||
$key = 'amount_currency_id_'.$name;
|
||||
$sentCurrencyId = array_key_exists($key, $preFilled) ? (int)$preFilled[$key] : $primaryCurrency->id;
|
||||
|
||||
app('log')->debug(sprintf('Sent currency ID is %d', $sentCurrencyId));
|
||||
|
||||
// find this currency in set of currencies:
|
||||
foreach ($currencies as $currency) {
|
||||
if ($currency->id === $sentCurrencyId) {
|
||||
$primaryCurrency = $currency;
|
||||
app('log')->debug(sprintf('default currency is now %s', $primaryCurrency->code));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure value is formatted nicely:
|
||||
if (null !== $value && '' !== $value) {
|
||||
$value = app('steam')->bcround($value, $primaryCurrency->decimal_places);
|
||||
}
|
||||
|
||||
try {
|
||||
$html = view('form.'.$view, compact('primaryCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
|
||||
} catch (Throwable $e) {
|
||||
app('log')->debug(sprintf('Could not render currencyField(): %s', $e->getMessage()));
|
||||
$html = 'Could not render currencyField.';
|
||||
|
||||
throw new FireflyException($html, 0, $e);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @phpstan-param view-string $view
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function currencyField(string $name, string $view, mixed $value = null, ?array $options = null): string
|
||||
{
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
$classes = $this->getHolderClasses($name);
|
||||
$value = $this->fillFieldValue($name, $value);
|
||||
$options['step'] = 'any';
|
||||
$primaryCurrency = $options['currency'] ?? app('amount')->getPrimaryCurrency();
|
||||
|
||||
/** @var Collection $currencies */
|
||||
$currencies = app('amount')->getCurrencies();
|
||||
unset($options['currency'], $options['placeholder']);
|
||||
// perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount)
|
||||
$preFilled = session('preFilled');
|
||||
if (!is_array($preFilled)) {
|
||||
$preFilled = [];
|
||||
}
|
||||
$key = 'amount_currency_id_'.$name;
|
||||
$sentCurrencyId = array_key_exists($key, $preFilled) ? (int)$preFilled[$key] : $primaryCurrency->id;
|
||||
|
||||
app('log')->debug(sprintf('Sent currency ID is %d', $sentCurrencyId));
|
||||
|
||||
// find this currency in set of currencies:
|
||||
foreach ($currencies as $currency) {
|
||||
if ($currency->id === $sentCurrencyId) {
|
||||
$primaryCurrency = $currency;
|
||||
app('log')->debug(sprintf('default currency is now %s', $primaryCurrency->code));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure value is formatted nicely:
|
||||
if (null !== $value && '' !== $value) {
|
||||
$value = app('steam')->bcround($value, $primaryCurrency->decimal_places);
|
||||
}
|
||||
|
||||
try {
|
||||
$html = view('form.'.$view, compact('primaryCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
|
||||
} catch (Throwable $e) {
|
||||
app('log')->debug(sprintf('Could not render currencyField(): %s', $e->getMessage()));
|
||||
$html = 'Could not render currencyField.';
|
||||
|
||||
throw new FireflyException($html, 0, $e);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,15 +54,26 @@ trait FormSupport
|
||||
return $html;
|
||||
}
|
||||
|
||||
protected function label(string $name, ?array $options = null): string
|
||||
/**
|
||||
* @param mixed $selected
|
||||
*/
|
||||
public function select(string $name, ?array $list = null, $selected = null, ?array $options = null): string
|
||||
{
|
||||
$options ??= [];
|
||||
if (array_key_exists('label', $options)) {
|
||||
return $options['label'];
|
||||
}
|
||||
$name = str_replace('[]', '', $name);
|
||||
$list ??= [];
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
$classes = $this->getHolderClasses($name);
|
||||
$selected = $this->fillFieldValue($name, $selected);
|
||||
unset($options['autocomplete'], $options['placeholder']);
|
||||
|
||||
return (string)trans('form.'.$name);
|
||||
try {
|
||||
$html = view('form.select', compact('classes', 'name', 'label', 'selected', 'options', 'list'))->render();
|
||||
} catch (Throwable $e) {
|
||||
app('log')->debug(sprintf('Could not render select(): %s', $e->getMessage()));
|
||||
$html = 'Could not render select.';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,19 +91,6 @@ trait FormSupport
|
||||
return $options;
|
||||
}
|
||||
|
||||
protected function getHolderClasses(string $name): string
|
||||
{
|
||||
// Get errors from session:
|
||||
/** @var null|MessageBag $errors */
|
||||
$errors = session('errors');
|
||||
|
||||
if (null !== $errors && $errors->has($name)) {
|
||||
return 'form-group has-error has-feedback';
|
||||
}
|
||||
|
||||
return 'form-group';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|mixed $value
|
||||
*
|
||||
@@ -116,28 +114,6 @@ trait FormSupport
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $selected
|
||||
*/
|
||||
public function select(string $name, ?array $list = null, $selected = null, ?array $options = null): string
|
||||
{
|
||||
$list ??= [];
|
||||
$label = $this->label($name, $options);
|
||||
$options = $this->expandOptionArray($name, $label, $options);
|
||||
$classes = $this->getHolderClasses($name);
|
||||
$selected = $this->fillFieldValue($name, $selected);
|
||||
unset($options['autocomplete'], $options['placeholder']);
|
||||
|
||||
try {
|
||||
$html = view('form.select', compact('classes', 'name', 'label', 'selected', 'options', 'list'))->render();
|
||||
} catch (Throwable $e) {
|
||||
app('log')->debug(sprintf('Could not render select(): %s', $e->getMessage()));
|
||||
$html = 'Could not render select.';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
protected function getAccountRepository(): AccountRepositoryInterface
|
||||
{
|
||||
return app(AccountRepositoryInterface::class);
|
||||
@@ -147,4 +123,28 @@ trait FormSupport
|
||||
{
|
||||
return today(config('app.timezone'));
|
||||
}
|
||||
|
||||
protected function getHolderClasses(string $name): string
|
||||
{
|
||||
// Get errors from session:
|
||||
/** @var null|MessageBag $errors */
|
||||
$errors = session('errors');
|
||||
|
||||
if (null !== $errors && $errors->has($name)) {
|
||||
return 'form-group has-error has-feedback';
|
||||
}
|
||||
|
||||
return 'form-group';
|
||||
}
|
||||
|
||||
protected function label(string $name, ?array $options = null): string
|
||||
{
|
||||
$options ??= [];
|
||||
if (array_key_exists('label', $options)) {
|
||||
return $options['label'];
|
||||
}
|
||||
$name = str_replace('[]', '', $name);
|
||||
|
||||
return (string)trans('form.'.$name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ class PiggyBankForm
|
||||
/** @var PiggyBankRepositoryInterface $repository */
|
||||
$repository = app(PiggyBankRepositoryInterface::class);
|
||||
$piggyBanks = $repository->getPiggyBanksWithAmount();
|
||||
$title = (string) trans('firefly.default_group_title_name');
|
||||
$title = (string)trans('firefly.default_group_title_name');
|
||||
$array = [];
|
||||
$subList = [
|
||||
0 => [
|
||||
@@ -55,7 +55,7 @@ class PiggyBankForm
|
||||
'title' => $title,
|
||||
],
|
||||
'piggies' => [
|
||||
(string) trans('firefly.none_in_select_list'),
|
||||
(string)trans('firefly.none_in_select_list'),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@@ -66,12 +66,12 @@ class RuleForm
|
||||
// get all currencies:
|
||||
$list = $groupRepos->get();
|
||||
$array = [
|
||||
0 => (string) trans('firefly.none_in_select_list'),
|
||||
0 => (string)trans('firefly.none_in_select_list'),
|
||||
];
|
||||
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($list as $group) {
|
||||
if (array_key_exists('hidden', $options) && (int) $options['hidden'] !== $group->id) {
|
||||
if (array_key_exists('hidden', $options) && (int)$options['hidden'] !== $group->id) {
|
||||
$array[$group->id] = $group->title;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,10 @@ class AccountBalanceGrouped
|
||||
private readonly ExchangeRateConverter $converter;
|
||||
private array $currencies = [];
|
||||
private array $data = [];
|
||||
private TransactionCurrency $primary;
|
||||
private Carbon $end;
|
||||
private array $journals = [];
|
||||
private string $preferredRange;
|
||||
private TransactionCurrency $primary;
|
||||
private Carbon $start;
|
||||
|
||||
public function __construct()
|
||||
@@ -146,48 +146,49 @@ class AccountBalanceGrouped
|
||||
$converter->summarize();
|
||||
}
|
||||
|
||||
private function processJournal(array $journal): void
|
||||
public function setAccounts(Collection $accounts): void
|
||||
{
|
||||
// format the date according to the period
|
||||
$period = $journal['date']->format($this->carbonFormat);
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currency = $this->findCurrency($currencyId);
|
||||
|
||||
// set the array with monetary info, if it does not exist.
|
||||
$this->createDefaultDataEntry($journal);
|
||||
// set the array (in monetary info) with spent/earned in this $period, if it does not exist.
|
||||
$this->createDefaultPeriodEntry($journal);
|
||||
|
||||
// is this journal's amount in- our outgoing?
|
||||
$key = $this->getDataKey($journal);
|
||||
$amount = 'spent' === $key ? Steam::negative($journal['amount']) : Steam::positive($journal['amount']);
|
||||
|
||||
// get conversion rate
|
||||
$rate = $this->getRate($currency, $journal['date']);
|
||||
$amountConverted = bcmul($amount, $rate);
|
||||
|
||||
// perhaps transaction already has the foreign amount in the primary currency.
|
||||
if ((int)$journal['foreign_currency_id'] === $this->primary->id) {
|
||||
$amountConverted = $journal['foreign_amount'] ?? '0';
|
||||
$amountConverted = 'earned' === $key ? Steam::positive($amountConverted) : Steam::negative($amountConverted);
|
||||
}
|
||||
|
||||
// add normal entry
|
||||
$this->data[$currencyId][$period][$key] = bcadd((string)$this->data[$currencyId][$period][$key], $amount);
|
||||
|
||||
// add converted entry
|
||||
$convertedKey = sprintf('pc_%s', $key);
|
||||
$this->data[$currencyId][$period][$convertedKey] = bcadd((string)$this->data[$currencyId][$period][$convertedKey], $amountConverted);
|
||||
$this->accountIds = $accounts->pluck('id')->toArray();
|
||||
}
|
||||
|
||||
private function findCurrency(int $currencyId): TransactionCurrency
|
||||
public function setEnd(Carbon $end): void
|
||||
{
|
||||
if (array_key_exists($currencyId, $this->currencies)) {
|
||||
return $this->currencies[$currencyId];
|
||||
}
|
||||
$this->currencies[$currencyId] = Amount::getTransactionCurrencyById($currencyId);
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
return $this->currencies[$currencyId];
|
||||
public function setJournals(array $journals): void
|
||||
{
|
||||
$this->journals = $journals;
|
||||
}
|
||||
|
||||
public function setPreferredRange(string $preferredRange): void
|
||||
{
|
||||
$this->preferredRange = $preferredRange;
|
||||
$this->carbonFormat = Navigation::preferredCarbonFormatByPeriod($preferredRange);
|
||||
}
|
||||
|
||||
public function setPrimary(TransactionCurrency $primary): void
|
||||
{
|
||||
$this->primary = $primary;
|
||||
$primaryCurrencyId = $primary->id;
|
||||
$this->currencies = [$primary->id => $primary]; // currency cache
|
||||
$this->data[$primaryCurrencyId] = [
|
||||
'currency_id' => (string)$primaryCurrencyId,
|
||||
'currency_symbol' => $primary->symbol,
|
||||
'currency_code' => $primary->code,
|
||||
'currency_name' => $primary->name,
|
||||
'currency_decimal_places' => $primary->decimal_places,
|
||||
'primary_currency_id' => (string)$primaryCurrencyId,
|
||||
'primary_currency_symbol' => $primary->symbol,
|
||||
'primary_currency_code' => $primary->code,
|
||||
'primary_currency_name' => $primary->name,
|
||||
'primary_currency_decimal_places' => $primary->decimal_places,
|
||||
];
|
||||
}
|
||||
|
||||
public function setStart(Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
private function createDefaultDataEntry(array $journal): void
|
||||
@@ -220,6 +221,16 @@ class AccountBalanceGrouped
|
||||
];
|
||||
}
|
||||
|
||||
private function findCurrency(int $currencyId): TransactionCurrency
|
||||
{
|
||||
if (array_key_exists($currencyId, $this->currencies)) {
|
||||
return $this->currencies[$currencyId];
|
||||
}
|
||||
$this->currencies[$currencyId] = Amount::getTransactionCurrencyById($currencyId);
|
||||
|
||||
return $this->currencies[$currencyId];
|
||||
}
|
||||
|
||||
private function getDataKey(array $journal): string
|
||||
{
|
||||
// deposit = incoming
|
||||
@@ -254,48 +265,37 @@ class AccountBalanceGrouped
|
||||
return $rate;
|
||||
}
|
||||
|
||||
public function setAccounts(Collection $accounts): void
|
||||
private function processJournal(array $journal): void
|
||||
{
|
||||
$this->accountIds = $accounts->pluck('id')->toArray();
|
||||
}
|
||||
// format the date according to the period
|
||||
$period = $journal['date']->format($this->carbonFormat);
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currency = $this->findCurrency($currencyId);
|
||||
|
||||
public function setPrimary(TransactionCurrency $primary): void
|
||||
{
|
||||
$this->primary = $primary;
|
||||
$primaryCurrencyId = $primary->id;
|
||||
$this->currencies = [$primary->id => $primary]; // currency cache
|
||||
$this->data[$primaryCurrencyId] = [
|
||||
'currency_id' => (string)$primaryCurrencyId,
|
||||
'currency_symbol' => $primary->symbol,
|
||||
'currency_code' => $primary->code,
|
||||
'currency_name' => $primary->name,
|
||||
'currency_decimal_places' => $primary->decimal_places,
|
||||
'primary_currency_id' => (string)$primaryCurrencyId,
|
||||
'primary_currency_symbol' => $primary->symbol,
|
||||
'primary_currency_code' => $primary->code,
|
||||
'primary_currency_name' => $primary->name,
|
||||
'primary_currency_decimal_places' => $primary->decimal_places,
|
||||
];
|
||||
}
|
||||
// set the array with monetary info, if it does not exist.
|
||||
$this->createDefaultDataEntry($journal);
|
||||
// set the array (in monetary info) with spent/earned in this $period, if it does not exist.
|
||||
$this->createDefaultPeriodEntry($journal);
|
||||
|
||||
public function setEnd(Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
// is this journal's amount in- our outgoing?
|
||||
$key = $this->getDataKey($journal);
|
||||
$amount = 'spent' === $key ? Steam::negative($journal['amount']) : Steam::positive($journal['amount']);
|
||||
|
||||
public function setJournals(array $journals): void
|
||||
{
|
||||
$this->journals = $journals;
|
||||
}
|
||||
// get conversion rate
|
||||
$rate = $this->getRate($currency, $journal['date']);
|
||||
$amountConverted = bcmul($amount, $rate);
|
||||
|
||||
public function setPreferredRange(string $preferredRange): void
|
||||
{
|
||||
$this->preferredRange = $preferredRange;
|
||||
$this->carbonFormat = Navigation::preferredCarbonFormatByPeriod($preferredRange);
|
||||
}
|
||||
// perhaps transaction already has the foreign amount in the primary currency.
|
||||
if ((int)$journal['foreign_currency_id'] === $this->primary->id) {
|
||||
$amountConverted = $journal['foreign_amount'] ?? '0';
|
||||
$amountConverted = 'earned' === $key ? Steam::positive($amountConverted) : Steam::negative($amountConverted);
|
||||
}
|
||||
|
||||
public function setStart(Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
// add normal entry
|
||||
$this->data[$currencyId][$period][$key] = bcadd((string)$this->data[$currencyId][$period][$key], $amount);
|
||||
|
||||
// add converted entry
|
||||
$convertedKey = sprintf('pc_%s', $key);
|
||||
$this->data[$currencyId][$period][$convertedKey] = bcadd((string)$this->data[$currencyId][$period][$convertedKey], $amountConverted);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ trait CollectsAccountsFromFilter
|
||||
// always collect from the query parameter, even when it's empty.
|
||||
if (null !== $queryParameters['accounts']) {
|
||||
foreach ($queryParameters['accounts'] as $accountId) {
|
||||
$account = $this->repository->find((int) $accountId);
|
||||
$account = $this->repository->find((int)$accountId);
|
||||
if (null !== $account) {
|
||||
$collection->push($account);
|
||||
}
|
||||
|
||||
@@ -94,6 +94,149 @@ class ExchangeRateConverter
|
||||
return '0' === $rate ? '1' : $rate;
|
||||
}
|
||||
|
||||
public function setIgnoreSettings(bool $ignoreSettings): void
|
||||
{
|
||||
$this->ignoreSettings = $ignoreSettings;
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
public function summarize(): void
|
||||
{
|
||||
if (false === $this->enabled()) {
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('ExchangeRateConverter ran %d queries.', $this->queryCount));
|
||||
}
|
||||
|
||||
private function getCacheKey(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
|
||||
{
|
||||
return sprintf('cer-%d-%d-%s', $from->id, $to->id, $date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getEuroId(): int
|
||||
{
|
||||
Log::debug('getEuroId()');
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty('cer-euro-id');
|
||||
if ($cache->has()) {
|
||||
return (int)$cache->get();
|
||||
}
|
||||
$euro = Amount::getTransactionCurrencyByCode('EUR');
|
||||
++$this->queryCount;
|
||||
$cache->store($euro->id);
|
||||
|
||||
return $euro->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getEuroRate(TransactionCurrency $currency, Carbon $date): string
|
||||
{
|
||||
$euroId = $this->getEuroId();
|
||||
if ($euroId === $currency->id) {
|
||||
return '1';
|
||||
}
|
||||
$rate = $this->getFromDB($currency->id, $euroId, $date->format('Y-m-d'));
|
||||
|
||||
if (null !== $rate) {
|
||||
// app('log')->debug(sprintf('Rate for %s to EUR is %s.', $currency->code, $rate));
|
||||
return $rate;
|
||||
}
|
||||
$rate = $this->getFromDB($euroId, $currency->id, $date->format('Y-m-d'));
|
||||
if (null !== $rate) {
|
||||
return bcdiv('1', $rate);
|
||||
// app('log')->debug(sprintf('Inverted rate for %s to EUR is %s.', $currency->code, $rate));
|
||||
// return $rate;
|
||||
}
|
||||
// grab backup values from config file:
|
||||
$backup = config(sprintf('cer.rates.%s', $currency->code));
|
||||
if (null !== $backup) {
|
||||
return bcdiv('1', (string)$backup);
|
||||
// app('log')->debug(sprintf('Backup rate for %s to EUR is %s.', $currency->code, $backup));
|
||||
// return $backup;
|
||||
}
|
||||
|
||||
// app('log')->debug(sprintf('No rate for %s to EUR.', $currency->code));
|
||||
return '0';
|
||||
}
|
||||
|
||||
private function getFromDB(int $from, int $to, string $date): ?string
|
||||
{
|
||||
if ($from === $to) {
|
||||
Log::debug('ExchangeRateConverter: From and to are the same, return "1".');
|
||||
|
||||
return '1';
|
||||
}
|
||||
$key = sprintf('cer-%d-%d-%s', $from, $to, $date);
|
||||
|
||||
// perhaps the rate has been cached during this particular run
|
||||
$preparedRate = $this->prepared[$date][$from][$to] ?? null;
|
||||
if (null !== $preparedRate && 0 !== bccomp('0', $preparedRate)) {
|
||||
Log::debug(sprintf('ExchangeRateConverter: Found prepared rate from #%d to #%d on %s.', $from, $to, $date));
|
||||
|
||||
return $preparedRate;
|
||||
}
|
||||
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
$rate = $cache->get();
|
||||
if ('' === $rate) {
|
||||
return null;
|
||||
}
|
||||
Log::debug(sprintf('ExchangeRateConverter: Found cached rate from #%d to #%d on %s.', $from, $to, $date));
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
/** @var null|CurrencyExchangeRate $result */
|
||||
$result = $this->userGroup->currencyExchangeRates()
|
||||
->where('from_currency_id', $from)
|
||||
->where('to_currency_id', $to)
|
||||
->where('date', '<=', $date)
|
||||
->orderBy('date', 'DESC')
|
||||
->first()
|
||||
;
|
||||
++$this->queryCount;
|
||||
$rate = (string)$result?->rate;
|
||||
|
||||
if ('' === $rate) {
|
||||
app('log')->debug(sprintf('ExchangeRateConverter: Found no rate for #%d->#%d (%s) in the DB.', $from, $to, $date));
|
||||
|
||||
return null;
|
||||
}
|
||||
if (0 === bccomp('0', $rate)) {
|
||||
app('log')->debug(sprintf('ExchangeRateConverter: Found rate for #%d->#%d (%s) in the DB, but it\'s zero.', $from, $to, $date));
|
||||
|
||||
return null;
|
||||
}
|
||||
app('log')->debug(sprintf('ExchangeRateConverter: Found rate for #%d->#%d (%s) in the DB: %s.', $from, $to, $date, $rate));
|
||||
$cache->store($rate);
|
||||
|
||||
// if the rate has not been cached during this particular run, save it
|
||||
$this->prepared[$date] ??= [
|
||||
$from => [
|
||||
$to => $rate,
|
||||
],
|
||||
];
|
||||
// also save the exchange rate the other way around:
|
||||
$this->prepared[$date] ??= [
|
||||
$to => [
|
||||
$from => bcdiv('1', $rate),
|
||||
],
|
||||
];
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -146,147 +289,4 @@ class ExchangeRateConverter
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
private function getCacheKey(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
|
||||
{
|
||||
return sprintf('cer-%d-%d-%s', $from->id, $to->id, $date->format('Y-m-d'));
|
||||
}
|
||||
|
||||
private function getFromDB(int $from, int $to, string $date): ?string
|
||||
{
|
||||
if ($from === $to) {
|
||||
Log::debug('ExchangeRateConverter: From and to are the same, return "1".');
|
||||
|
||||
return '1';
|
||||
}
|
||||
$key = sprintf('cer-%d-%d-%s', $from, $to, $date);
|
||||
|
||||
// perhaps the rate has been cached during this particular run
|
||||
$preparedRate = $this->prepared[$date][$from][$to] ?? null;
|
||||
if (null !== $preparedRate && 0 !== bccomp('0', $preparedRate)) {
|
||||
Log::debug(sprintf('ExchangeRateConverter: Found prepared rate from #%d to #%d on %s.', $from, $to, $date));
|
||||
|
||||
return $preparedRate;
|
||||
}
|
||||
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($key);
|
||||
if ($cache->has()) {
|
||||
$rate = $cache->get();
|
||||
if ('' === $rate) {
|
||||
return null;
|
||||
}
|
||||
Log::debug(sprintf('ExchangeRateConverter: Found cached rate from #%d to #%d on %s.', $from, $to, $date));
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
/** @var null|CurrencyExchangeRate $result */
|
||||
$result = $this->userGroup->currencyExchangeRates()
|
||||
->where('from_currency_id', $from)
|
||||
->where('to_currency_id', $to)
|
||||
->where('date', '<=', $date)
|
||||
->orderBy('date', 'DESC')
|
||||
->first()
|
||||
;
|
||||
++$this->queryCount;
|
||||
$rate = (string) $result?->rate;
|
||||
|
||||
if ('' === $rate) {
|
||||
app('log')->debug(sprintf('ExchangeRateConverter: Found no rate for #%d->#%d (%s) in the DB.', $from, $to, $date));
|
||||
|
||||
return null;
|
||||
}
|
||||
if (0 === bccomp('0', $rate)) {
|
||||
app('log')->debug(sprintf('ExchangeRateConverter: Found rate for #%d->#%d (%s) in the DB, but it\'s zero.', $from, $to, $date));
|
||||
|
||||
return null;
|
||||
}
|
||||
app('log')->debug(sprintf('ExchangeRateConverter: Found rate for #%d->#%d (%s) in the DB: %s.', $from, $to, $date, $rate));
|
||||
$cache->store($rate);
|
||||
|
||||
// if the rate has not been cached during this particular run, save it
|
||||
$this->prepared[$date] ??= [
|
||||
$from => [
|
||||
$to => $rate,
|
||||
],
|
||||
];
|
||||
// also save the exchange rate the other way around:
|
||||
$this->prepared[$date] ??= [
|
||||
$to => [
|
||||
$from => bcdiv('1', $rate),
|
||||
],
|
||||
];
|
||||
|
||||
return $rate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getEuroRate(TransactionCurrency $currency, Carbon $date): string
|
||||
{
|
||||
$euroId = $this->getEuroId();
|
||||
if ($euroId === $currency->id) {
|
||||
return '1';
|
||||
}
|
||||
$rate = $this->getFromDB($currency->id, $euroId, $date->format('Y-m-d'));
|
||||
|
||||
if (null !== $rate) {
|
||||
// app('log')->debug(sprintf('Rate for %s to EUR is %s.', $currency->code, $rate));
|
||||
return $rate;
|
||||
}
|
||||
$rate = $this->getFromDB($euroId, $currency->id, $date->format('Y-m-d'));
|
||||
if (null !== $rate) {
|
||||
return bcdiv('1', $rate);
|
||||
// app('log')->debug(sprintf('Inverted rate for %s to EUR is %s.', $currency->code, $rate));
|
||||
// return $rate;
|
||||
}
|
||||
// grab backup values from config file:
|
||||
$backup = config(sprintf('cer.rates.%s', $currency->code));
|
||||
if (null !== $backup) {
|
||||
return bcdiv('1', (string) $backup);
|
||||
// app('log')->debug(sprintf('Backup rate for %s to EUR is %s.', $currency->code, $backup));
|
||||
// return $backup;
|
||||
}
|
||||
|
||||
// app('log')->debug(sprintf('No rate for %s to EUR.', $currency->code));
|
||||
return '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getEuroId(): int
|
||||
{
|
||||
Log::debug('getEuroId()');
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty('cer-euro-id');
|
||||
if ($cache->has()) {
|
||||
return (int) $cache->get();
|
||||
}
|
||||
$euro = Amount::getTransactionCurrencyByCode('EUR');
|
||||
++$this->queryCount;
|
||||
$cache->store($euro->id);
|
||||
|
||||
return $euro->id;
|
||||
}
|
||||
|
||||
public function setIgnoreSettings(bool $ignoreSettings): void
|
||||
{
|
||||
$this->ignoreSettings = $ignoreSettings;
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
public function summarize(): void
|
||||
{
|
||||
if (false === $this->enabled()) {
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('ExchangeRateConverter ran %d queries.', $this->queryCount));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class SummaryBalanceGrouped
|
||||
$return[] = [
|
||||
'key' => sprintf('%s-in-pc', $title),
|
||||
'value' => $this->amounts[$key]['primary'] ?? '0',
|
||||
'currency_id' => (string) $this->default->id,
|
||||
'currency_id' => (string)$this->default->id,
|
||||
'currency_code' => $this->default->code,
|
||||
'currency_symbol' => $this->default->symbol,
|
||||
'currency_decimal_places' => $this->default->decimal_places,
|
||||
@@ -73,7 +73,7 @@ class SummaryBalanceGrouped
|
||||
// skip primary entries.
|
||||
continue;
|
||||
}
|
||||
$currencyId = (int) $currencyId;
|
||||
$currencyId = (int)$currencyId;
|
||||
$currency = $this->currencies[$currencyId] ?? $this->currencyRepository->find($currencyId);
|
||||
$this->currencies[$currencyId] = $currency;
|
||||
// create objects for big array.
|
||||
@@ -87,7 +87,7 @@ class SummaryBalanceGrouped
|
||||
$return[] = [
|
||||
'key' => sprintf('%s-in-%s', $title, $currency->code),
|
||||
'value' => $this->amounts[$key][$currencyId] ?? '0',
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_id' => (string)$currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
@@ -109,12 +109,12 @@ class SummaryBalanceGrouped
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
// transaction info:
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$amount = bcmul((string) $journal['amount'], $multiplier);
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$amount = bcmul((string)$journal['amount'], $multiplier);
|
||||
$currency = $this->currencies[$currencyId] ?? Amount::getTransactionCurrencyById($currencyId);
|
||||
$this->currencies[$currencyId] = $currency;
|
||||
$pcAmount = $converter->convert($currency, $this->default, $journal['date'], $amount);
|
||||
if ((int) $journal['foreign_currency_id'] === $this->default->id) {
|
||||
if ((int)$journal['foreign_currency_id'] === $this->default->id) {
|
||||
// use foreign amount instead
|
||||
$pcAmount = $journal['foreign_amount'];
|
||||
}
|
||||
@@ -126,10 +126,10 @@ class SummaryBalanceGrouped
|
||||
$this->amounts[self::SUM]['primary'] ??= '0';
|
||||
|
||||
// add values:
|
||||
$this->amounts[$key][$currencyId] = bcadd((string) $this->amounts[$key][$currencyId], $amount);
|
||||
$this->amounts[self::SUM][$currencyId] = bcadd((string) $this->amounts[self::SUM][$currencyId], $amount);
|
||||
$this->amounts[$key]['primary'] = bcadd((string) $this->amounts[$key]['primary'], (string) $pcAmount);
|
||||
$this->amounts[self::SUM]['primary'] = bcadd((string) $this->amounts[self::SUM]['primary'], (string) $pcAmount);
|
||||
$this->amounts[$key][$currencyId] = bcadd((string)$this->amounts[$key][$currencyId], $amount);
|
||||
$this->amounts[self::SUM][$currencyId] = bcadd((string)$this->amounts[self::SUM][$currencyId], $amount);
|
||||
$this->amounts[$key]['primary'] = bcadd((string)$this->amounts[$key]['primary'], (string)$pcAmount);
|
||||
$this->amounts[self::SUM]['primary'] = bcadd((string)$this->amounts[self::SUM]['primary'], (string)$pcAmount);
|
||||
}
|
||||
$converter->summarize();
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ use Illuminate\Support\Facades\Log;
|
||||
*/
|
||||
trait ValidatesUserGroupTrait
|
||||
{
|
||||
protected User $user;
|
||||
protected UserGroup $userGroup;
|
||||
protected User $user;
|
||||
|
||||
/**
|
||||
* An "undocumented" filter
|
||||
@@ -62,11 +62,11 @@ trait ValidatesUserGroupTrait
|
||||
$user = auth()->user();
|
||||
$groupId = 0;
|
||||
if (!$request->has('user_group_id')) {
|
||||
$groupId = (int) $user->user_group_id;
|
||||
$groupId = (int)$user->user_group_id;
|
||||
Log::debug(sprintf('validateUserGroup: no user group submitted, use default group #%d.', $groupId));
|
||||
}
|
||||
if ($request->has('user_group_id')) {
|
||||
$groupId = (int) $request->get('user_group_id');
|
||||
$groupId = (int)$request->get('user_group_id');
|
||||
Log::debug(sprintf('validateUserGroup: user group submitted, search for memberships in group #%d.', $groupId));
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ trait ValidatesUserGroupTrait
|
||||
if (0 === $memberships->count()) {
|
||||
Log::debug(sprintf('validateUserGroup: user has no access to group #%d.', $groupId));
|
||||
|
||||
throw new AuthorizationException((string) trans('validation.no_access_group'));
|
||||
throw new AuthorizationException((string)trans('validation.no_access_group'));
|
||||
}
|
||||
|
||||
// need to get the group from the membership:
|
||||
@@ -86,14 +86,14 @@ trait ValidatesUserGroupTrait
|
||||
if (null === $group) {
|
||||
Log::debug(sprintf('validateUserGroup: group #%d does not exist.', $groupId));
|
||||
|
||||
throw new AuthorizationException((string) trans('validation.belongs_user_or_user_group'));
|
||||
throw new AuthorizationException((string)trans('validation.belongs_user_or_user_group'));
|
||||
}
|
||||
Log::debug(sprintf('validateUserGroup: validate access of user to group #%d ("%s").', $groupId, $group->title));
|
||||
$roles = property_exists($this, 'acceptedRoles') ? $this->acceptedRoles : []; // @phpstan-ignore-line
|
||||
if (0 === count($roles)) {
|
||||
Log::debug('validateUserGroup: no roles defined, so no access.');
|
||||
|
||||
throw new AuthorizationException((string) trans('validation.no_accepted_roles_defined'));
|
||||
throw new AuthorizationException((string)trans('validation.no_accepted_roles_defined'));
|
||||
}
|
||||
Log::debug(sprintf('validateUserGroup: have %d roles to check.', count($roles)), $roles);
|
||||
|
||||
@@ -111,6 +111,6 @@ trait ValidatesUserGroupTrait
|
||||
|
||||
Log::debug('validateUserGroup: User does NOT have enough rights to access endpoint.');
|
||||
|
||||
throw new AuthorizationException((string) trans('validation.belongs_user_or_user_group'));
|
||||
throw new AuthorizationException((string)trans('validation.belongs_user_or_user_group'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,8 +110,8 @@ trait AugumentData
|
||||
$grouped = $accounts->groupBy('id')->toArray();
|
||||
$return = [];
|
||||
foreach ($accountIds as $combinedId) {
|
||||
$parts = explode('-', (string) $combinedId);
|
||||
$accountId = (int) $parts[0];
|
||||
$parts = explode('-', (string)$combinedId);
|
||||
$accountId = (int)$parts[0];
|
||||
if (array_key_exists($accountId, $grouped)) {
|
||||
$return[$accountId] = $grouped[$accountId][0]['name'];
|
||||
}
|
||||
@@ -136,7 +136,7 @@ trait AugumentData
|
||||
$return[$budgetId] = $grouped[$budgetId][0]['name'];
|
||||
}
|
||||
}
|
||||
$return[0] = (string) trans('firefly.no_budget');
|
||||
$return[0] = (string)trans('firefly.no_budget');
|
||||
|
||||
return $return;
|
||||
}
|
||||
@@ -152,13 +152,13 @@ trait AugumentData
|
||||
$grouped = $categories->groupBy('id')->toArray();
|
||||
$return = [];
|
||||
foreach ($categoryIds as $combinedId) {
|
||||
$parts = explode('-', (string) $combinedId);
|
||||
$categoryId = (int) $parts[0];
|
||||
$parts = explode('-', (string)$combinedId);
|
||||
$categoryId = (int)$parts[0];
|
||||
if (array_key_exists($categoryId, $grouped)) {
|
||||
$return[$categoryId] = $grouped[$categoryId][0]['name'];
|
||||
}
|
||||
}
|
||||
$return[0] = (string) trans('firefly.no_category');
|
||||
$return[0] = (string)trans('firefly.no_category');
|
||||
|
||||
return $return;
|
||||
}
|
||||
@@ -249,7 +249,7 @@ trait AugumentData
|
||||
}
|
||||
|
||||
$grouped[$name] ??= '0';
|
||||
$grouped[$name] = bcadd((string) $journal['amount'], $grouped[$name]);
|
||||
$grouped[$name] = bcadd((string)$journal['amount'], $grouped[$name]);
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
@@ -272,7 +272,7 @@ trait AugumentData
|
||||
];
|
||||
// loop to support multi currency
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
|
||||
// if not set, set to zero:
|
||||
if (!array_key_exists($currencyId, $sum['per_currency'])) {
|
||||
@@ -287,8 +287,8 @@ trait AugumentData
|
||||
}
|
||||
|
||||
// add amount
|
||||
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], (string) $journal['amount']);
|
||||
$sum['grand_sum'] = bcadd($sum['grand_sum'], (string) $journal['amount']);
|
||||
$sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], (string)$journal['amount']);
|
||||
$sum['grand_sum'] = bcadd($sum['grand_sum'], (string)$journal['amount']);
|
||||
}
|
||||
|
||||
return $sum;
|
||||
|
||||
@@ -92,7 +92,7 @@ trait ChartGeneration
|
||||
Log::debug(sprintf('Start balance for account #%d ("%s) is', $account->id, $account->name), $previous);
|
||||
while ($currentStart <= $end) {
|
||||
$format = $currentStart->format('Y-m-d');
|
||||
$label = trim($currentStart->isoFormat((string) trans('config.month_and_day_js', [], $locale)));
|
||||
$label = trim($currentStart->isoFormat((string)trans('config.month_and_day_js', [], $locale)));
|
||||
$balance = $range[$format] ?? $previous;
|
||||
$previous = $balance;
|
||||
$currentStart->addDay();
|
||||
|
||||
@@ -73,7 +73,7 @@ trait CreateStuff
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$assetAccount = [
|
||||
'name' => (string) trans('firefly.cash_wallet', [], $language),
|
||||
'name' => (string)trans('firefly.cash_wallet', [], $language),
|
||||
'iban' => null,
|
||||
'account_type_name' => 'asset',
|
||||
'virtual_balance' => 0,
|
||||
@@ -108,7 +108,7 @@ trait CreateStuff
|
||||
|
||||
Log::alert('NO OAuth keys were found. They have been created.');
|
||||
|
||||
file_put_contents($publicKey, (string) $key->getPublicKey());
|
||||
file_put_contents($publicKey, (string)$key->getPublicKey());
|
||||
file_put_contents($privateKey, $key->toString('PKCS1'));
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ trait CreateStuff
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$savingsAccount = [
|
||||
'name' => (string) trans('firefly.new_savings_account', ['bank_name' => $request->get('bank_name')], $language),
|
||||
'name' => (string)trans('firefly.new_savings_account', ['bank_name' => $request->get('bank_name')], $language),
|
||||
'iban' => null,
|
||||
'account_type_name' => 'asset',
|
||||
'account_type_id' => null,
|
||||
|
||||
@@ -63,32 +63,6 @@ trait CronRunner
|
||||
];
|
||||
}
|
||||
|
||||
protected function webhookCronJob(bool $force, Carbon $date): array
|
||||
{
|
||||
/** @var WebhookCronjob $webhook */
|
||||
$webhook = app(WebhookCronjob::class);
|
||||
$webhook->setForce($force);
|
||||
$webhook->setDate($date);
|
||||
|
||||
try {
|
||||
$webhook->fire();
|
||||
} catch (FireflyException $e) {
|
||||
return [
|
||||
'job_fired' => false,
|
||||
'job_succeeded' => false,
|
||||
'job_errored' => true,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'job_fired' => $webhook->jobFired,
|
||||
'job_succeeded' => $webhook->jobSucceeded,
|
||||
'job_errored' => $webhook->jobErrored,
|
||||
'message' => $webhook->message,
|
||||
];
|
||||
}
|
||||
|
||||
protected function exchangeRatesCronJob(bool $force, Carbon $date): array
|
||||
{
|
||||
/** @var ExchangeRatesCronjob $exchangeRates */
|
||||
@@ -166,4 +140,30 @@ trait CronRunner
|
||||
'message' => $recurring->message,
|
||||
];
|
||||
}
|
||||
|
||||
protected function webhookCronJob(bool $force, Carbon $date): array
|
||||
{
|
||||
/** @var WebhookCronjob $webhook */
|
||||
$webhook = app(WebhookCronjob::class);
|
||||
$webhook->setForce($force);
|
||||
$webhook->setDate($date);
|
||||
|
||||
try {
|
||||
$webhook->fire();
|
||||
} catch (FireflyException $e) {
|
||||
return [
|
||||
'job_fired' => false,
|
||||
'job_succeeded' => false,
|
||||
'job_errored' => true,
|
||||
'message' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'job_fired' => $webhook->jobFired,
|
||||
'job_succeeded' => $webhook->jobSucceeded,
|
||||
'job_errored' => $webhook->jobErrored,
|
||||
'message' => $webhook->message,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,13 +40,13 @@ trait DateCalculation
|
||||
*/
|
||||
public function activeDaysLeft(Carbon $start, Carbon $end): int
|
||||
{
|
||||
$difference = (int) ($start->diffInDays($end, true) + 1);
|
||||
$difference = (int)($start->diffInDays($end, true) + 1);
|
||||
$today = today(config('app.timezone'))->startOfDay();
|
||||
if ($start->lte($today) && $end->gte($today)) {
|
||||
$difference = $today->diffInDays($end) + 1;
|
||||
}
|
||||
|
||||
return (int) (0 === $difference ? 1 : $difference);
|
||||
return (int)(0 === $difference ? 1 : $difference);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +63,7 @@ trait DateCalculation
|
||||
$difference = $start->diffInDays($today, true) + 1;
|
||||
}
|
||||
|
||||
return (int) $difference;
|
||||
return (int)$difference;
|
||||
}
|
||||
|
||||
protected function calculateStep(Carbon $start, Carbon $end): string
|
||||
|
||||
@@ -48,7 +48,7 @@ trait GetConfigurationData
|
||||
E_COMPILE_ERROR | E_RECOVERABLE_ERROR | E_ERROR | E_CORE_ERROR => 'E_COMPILE_ERROR|E_RECOVERABLE_ERROR|E_ERROR|E_CORE_ERROR',
|
||||
];
|
||||
|
||||
return $array[$value] ?? (string) $value;
|
||||
return $array[$value] ?? (string)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +64,7 @@ trait GetConfigurationData
|
||||
$currentStep = $options;
|
||||
|
||||
// get the text:
|
||||
$currentStep['intro'] = (string) trans('intro.'.$route.'_'.$key);
|
||||
$currentStep['intro'] = (string)trans('intro.'.$route.'_'.$key);
|
||||
|
||||
// save in array:
|
||||
$steps[] = $currentStep;
|
||||
@@ -133,41 +133,41 @@ trait GetConfigurationData
|
||||
$todayEnd = app('navigation')->endOfPeriod($todayStart, $viewRange);
|
||||
|
||||
if ($todayStart->ne($start) || $todayEnd->ne($end)) {
|
||||
$ranges[ucfirst((string) trans('firefly.today'))] = [$todayStart, $todayEnd];
|
||||
$ranges[ucfirst((string)trans('firefly.today'))] = [$todayStart, $todayEnd];
|
||||
}
|
||||
|
||||
// last seven days:
|
||||
$seven = today(config('app.timezone'))->subDays(7);
|
||||
$index = (string) trans('firefly.last_seven_days');
|
||||
$index = (string)trans('firefly.last_seven_days');
|
||||
$ranges[$index] = [$seven, new Carbon()];
|
||||
|
||||
// last 30 days:
|
||||
$thirty = today(config('app.timezone'))->subDays(30);
|
||||
$index = (string) trans('firefly.last_thirty_days');
|
||||
$index = (string)trans('firefly.last_thirty_days');
|
||||
$ranges[$index] = [$thirty, new Carbon()];
|
||||
|
||||
// month to date:
|
||||
$monthBegin = today(config('app.timezone'))->startOfMonth();
|
||||
$index = (string) trans('firefly.month_to_date');
|
||||
$index = (string)trans('firefly.month_to_date');
|
||||
$ranges[$index] = [$monthBegin, new Carbon()];
|
||||
|
||||
// year to date:
|
||||
$yearBegin = today(config('app.timezone'))->startOfYear();
|
||||
$index = (string) trans('firefly.year_to_date');
|
||||
$index = (string)trans('firefly.year_to_date');
|
||||
$ranges[$index] = [$yearBegin, new Carbon()];
|
||||
|
||||
// everything
|
||||
$index = (string) trans('firefly.everything');
|
||||
$index = (string)trans('firefly.everything');
|
||||
$ranges[$index] = [$first, new Carbon()];
|
||||
|
||||
return [
|
||||
'title' => $title,
|
||||
'configuration' => [
|
||||
'apply' => (string) trans('firefly.apply'),
|
||||
'cancel' => (string) trans('firefly.cancel'),
|
||||
'from' => (string) trans('firefly.from'),
|
||||
'to' => (string) trans('firefly.to'),
|
||||
'customRange' => (string) trans('firefly.customRange'),
|
||||
'apply' => (string)trans('firefly.apply'),
|
||||
'cancel' => (string)trans('firefly.cancel'),
|
||||
'from' => (string)trans('firefly.from'),
|
||||
'to' => (string)trans('firefly.to'),
|
||||
'customRange' => (string)trans('firefly.customRange'),
|
||||
'start' => $start->format('Y-m-d'),
|
||||
'end' => $end->format('Y-m-d'),
|
||||
'ranges' => $ranges,
|
||||
@@ -192,7 +192,7 @@ trait GetConfigurationData
|
||||
$currentStep = $options;
|
||||
|
||||
// get the text:
|
||||
$currentStep['intro'] = (string) trans('intro.'.$route.'_'.$specificPage.'_'.$key);
|
||||
$currentStep['intro'] = (string)trans('intro.'.$route.'_'.$specificPage.'_'.$key);
|
||||
|
||||
// save in array:
|
||||
$steps[] = $currentStep;
|
||||
@@ -207,7 +207,7 @@ trait GetConfigurationData
|
||||
protected function verifyRecurringCronJob(): void
|
||||
{
|
||||
$config = FireflyConfig::get('last_rt_job', 0);
|
||||
$lastTime = (int) $config?->data;
|
||||
$lastTime = (int)$config?->data;
|
||||
$now = Carbon::now()->getTimestamp();
|
||||
app('log')->debug(sprintf('verifyRecurringCronJob: last time is %d ("%s"), now is %d', $lastTime, $config?->data, $now));
|
||||
if (0 === $lastTime) {
|
||||
|
||||
@@ -87,9 +87,9 @@ trait ModelInformation
|
||||
/** @var AccountType $mortgage */
|
||||
$mortgage = $repository->getAccountTypeByType(AccountTypeEnum::MORTGAGE->value);
|
||||
$liabilityTypes = [
|
||||
$debt->id => (string) trans(sprintf('firefly.account_type_%s', AccountTypeEnum::DEBT->value)),
|
||||
$loan->id => (string) trans(sprintf('firefly.account_type_%s', AccountTypeEnum::LOAN->value)),
|
||||
$mortgage->id => (string) trans(sprintf('firefly.account_type_%s', AccountTypeEnum::MORTGAGE->value)),
|
||||
$debt->id => (string)trans(sprintf('firefly.account_type_%s', AccountTypeEnum::DEBT->value)),
|
||||
$loan->id => (string)trans(sprintf('firefly.account_type_%s', AccountTypeEnum::LOAN->value)),
|
||||
$mortgage->id => (string)trans(sprintf('firefly.account_type_%s', AccountTypeEnum::MORTGAGE->value)),
|
||||
];
|
||||
asort($liabilityTypes);
|
||||
|
||||
@@ -100,7 +100,7 @@ trait ModelInformation
|
||||
{
|
||||
$roles = [];
|
||||
foreach (config('firefly.accountRoles') as $role) {
|
||||
$roles[$role] = (string) trans(sprintf('firefly.account_role_%s', $role));
|
||||
$roles[$role] = (string)trans(sprintf('firefly.account_role_%s', $role));
|
||||
}
|
||||
|
||||
return $roles;
|
||||
@@ -118,7 +118,7 @@ trait ModelInformation
|
||||
$triggers = [];
|
||||
foreach ($operators as $key => $operator) {
|
||||
if ('user_action' !== $key && false === $operator['alias']) {
|
||||
$triggers[$key] = (string) trans(sprintf('firefly.rule_trigger_%s_choice', $key));
|
||||
$triggers[$key] = (string)trans(sprintf('firefly.rule_trigger_%s_choice', $key));
|
||||
}
|
||||
}
|
||||
asort($triggers);
|
||||
@@ -169,7 +169,7 @@ trait ModelInformation
|
||||
$triggers = [];
|
||||
foreach ($operators as $key => $operator) {
|
||||
if ('user_action' !== $key && false === $operator['alias']) {
|
||||
$triggers[$key] = (string) trans(sprintf('firefly.rule_trigger_%s_choice', $key));
|
||||
$triggers[$key] = (string)trans(sprintf('firefly.rule_trigger_%s_choice', $key));
|
||||
}
|
||||
}
|
||||
asort($triggers);
|
||||
|
||||
@@ -30,13 +30,21 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\PeriodStatistic;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
|
||||
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Debug\Timer;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Navigation;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Trait PeriodOverview.
|
||||
@@ -67,8 +75,13 @@ use Illuminate\Support\Facades\Log;
|
||||
*/
|
||||
trait PeriodOverview
|
||||
{
|
||||
protected AccountRepositoryInterface $accountRepository;
|
||||
protected JournalRepositoryInterface $journalRepos;
|
||||
protected AccountRepositoryInterface $accountRepository;
|
||||
protected CategoryRepositoryInterface $categoryRepository;
|
||||
protected TagRepositoryInterface $tagRepository;
|
||||
protected JournalRepositoryInterface $journalRepos;
|
||||
protected PeriodStatisticRepositoryInterface $periodStatisticRepo;
|
||||
private Collection $statistics; // temp data holder
|
||||
private array $transactions; // temp data holder
|
||||
|
||||
/**
|
||||
* This method returns "period entries", so nov-2015, dec-2015, etc. (this depends on the users session range)
|
||||
@@ -79,163 +92,45 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getAccountPeriodOverview(Account $account, Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug('Now in getAccountPeriodOverview()');
|
||||
$timer = Timer::getInstance();
|
||||
$timer->start('account-period-total');
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
// properties for cache
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('account-show-period-entries');
|
||||
$cache->addProperty($account->id);
|
||||
if ($cache->has()) {
|
||||
Log::debug('Return CACHED in getAccountPeriodOverview()');
|
||||
|
||||
return $cache->get();
|
||||
}
|
||||
Log::debug(sprintf('Now in getAccountPeriodOverview(#%d, %s %s)', $account->id, $start->format('Y-m-d H:i:s.u'), $end->format('Y-m-d H:i:s.u')));
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$this->accountRepository->setUser($account->user);
|
||||
$this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
[$start, $end] = $this->getPeriodFromBlocks($dates, $start, $end);
|
||||
$this->statistics = $this->periodStatisticRepo->allInRangeForModel($account, $start, $end);
|
||||
|
||||
// run a custom query because doing this with the collector is MEGA slow.
|
||||
$timer->start('account-period-collect');
|
||||
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
|
||||
$timer->stop('account-period-collect');
|
||||
// loop dates
|
||||
$entries = [];
|
||||
Log::debug(sprintf('Count of loops: %d', count($dates)));
|
||||
$loops = 0;
|
||||
// stop after 10 loops for memory reasons.
|
||||
$timer->start('account-period-loop');
|
||||
foreach ($dates as $currentDate) {
|
||||
$title = Navigation::periodShow($currentDate['start'], $currentDate['period']);
|
||||
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferredAway) + count($transferredIn),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred_away' => $this->groupByCurrency($transferredAway),
|
||||
'transferred_in' => $this->groupByCurrency($transferredIn),
|
||||
];
|
||||
++$loops;
|
||||
$entries[] = $this->getSingleModelPeriod($account, $currentDate['period'], $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
$timer->stop('account-period-loop');
|
||||
$cache->store($entries);
|
||||
$timer->stop('account-period-total');
|
||||
Log::debug('End of getAccountPeriodOverview()');
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
private function filterTransactionsByType(TransactionTypeEnum $type, array $transactions, Carbon $start, Carbon $end): array
|
||||
private function getPeriodFromBlocks(array $dates, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
$filtered = [];
|
||||
|
||||
/**
|
||||
* @var int $index
|
||||
* @var array $item
|
||||
*/
|
||||
foreach ($transactions as $index => $item) {
|
||||
$date = Carbon::parse($item['date']);
|
||||
$fits = $item['type'] === $type->value && $date >= $start && $date <= $end;
|
||||
if ($fits) {
|
||||
$result[] = $item;
|
||||
unset($transactions[$index]);
|
||||
Log::debug('Filter generated periods to select the oldest and newest date.');
|
||||
foreach ($dates as $row) {
|
||||
$currentStart = clone $row['start'];
|
||||
$currentEnd = clone $row['end'];
|
||||
if ($currentStart->lt($start)) {
|
||||
Log::debug(sprintf('New start: was %s, now %s', $start->format('Y-m-d'), $currentStart->format('Y-m-d')));
|
||||
$start = $currentStart;
|
||||
}
|
||||
if (!$fits) {
|
||||
$filtered[] = $item;
|
||||
if ($currentEnd->gt($end)) {
|
||||
Log::debug(sprintf('New end: was %s, now %s', $end->format('Y-m-d'), $currentEnd->format('Y-m-d')));
|
||||
$end = $currentEnd;
|
||||
}
|
||||
}
|
||||
|
||||
return [$filtered, $result];
|
||||
}
|
||||
|
||||
private function filterTransfers(string $direction, array $transactions, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
$filtered = [];
|
||||
|
||||
/**
|
||||
* @var int $index
|
||||
* @var array $item
|
||||
*/
|
||||
foreach ($transactions as $index => $item) {
|
||||
$date = Carbon::parse($item['date']);
|
||||
if ($date >= $start && $date <= $end) {
|
||||
if ('away' === $direction && -1 === bccomp((string)$item['amount'], '0')) {
|
||||
$result[] = $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
if ('in' === $direction && 1 === bccomp((string)$item['amount'], '0')) {
|
||||
$result[] = $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$filtered[] = $item;
|
||||
}
|
||||
|
||||
return [$filtered, $result];
|
||||
}
|
||||
|
||||
private function groupByCurrency(array $journals): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
$currencyDecimalPlaces = $journal['currency_decimal_places'];
|
||||
$foreignCurrencyId = $journal['foreign_currency_id'];
|
||||
$amount = $journal['amount'] ?? '0';
|
||||
|
||||
if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId !== $this->primaryCurrency->id) {
|
||||
$amount = $journal['pc_amount'] ?? '0';
|
||||
$currencyId = $this->primaryCurrency->id;
|
||||
$currencyCode = $this->primaryCurrency->code;
|
||||
$currencyName = $this->primaryCurrency->name;
|
||||
$currencySymbol = $this->primaryCurrency->symbol;
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
}
|
||||
if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId === $this->primaryCurrency->id) {
|
||||
$currencyId = (int)$foreignCurrencyId;
|
||||
$currencyCode = $journal['foreign_currency_code'];
|
||||
$currencyName = $journal['foreign_currency_name'];
|
||||
$currencySymbol = $journal['foreign_currency_symbol'];
|
||||
$currencyDecimalPlaces = $journal['foreign_currency_decimal_places'];
|
||||
$amount = $journal['foreign_amount'] ?? '0';
|
||||
}
|
||||
$return[$currencyId] ??= [
|
||||
'amount' => '0',
|
||||
'count' => 0,
|
||||
'currency_id' => $currencyId,
|
||||
'currency_name' => $currencyName,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
|
||||
|
||||
$return[$currencyId]['amount'] = bcadd($return[$currencyId]['amount'], $amount);
|
||||
++$return[$currencyId]['count'];
|
||||
}
|
||||
|
||||
return $return;
|
||||
return [$start, $end];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,89 +140,28 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
$this->categoryRepository = app(CategoryRepositoryInterface::class);
|
||||
$this->categoryRepository->setUser($category->user);
|
||||
$this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);
|
||||
|
||||
// properties for entries with their amounts.
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($range);
|
||||
$cache->addProperty('category-show-period-entries');
|
||||
$cache->addProperty($category->id);
|
||||
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
}
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
[$start, $end] = $this->getPeriodFromBlocks($dates, $start, $end);
|
||||
$this->statistics = $this->periodStatisticRepo->allInRangeForModel($category, $start, $end);
|
||||
|
||||
// collect all expenses in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setCategory($category);
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
|
||||
$earnedSet = $collector->getExtractedJournals();
|
||||
|
||||
// collect all income in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setCategory($category);
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
$spentSet = $collector->getExtractedJournals();
|
||||
|
||||
// collect all transfers in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setCategory($category);
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
|
||||
$transferSet = $collector->getExtractedJournals();
|
||||
Log::debug(sprintf('Count of loops: %d', count($dates)));
|
||||
foreach ($dates as $currentDate) {
|
||||
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
|
||||
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'transactions' => 0,
|
||||
'title' => $title,
|
||||
'route' => route(
|
||||
'categories.show',
|
||||
[$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
|
||||
),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferred),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred' => $this->groupByCurrency($transferred),
|
||||
];
|
||||
$entries[] = $this->getSingleModelPeriod($category, $currentDate['period'], $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
$cache->store($entries);
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of journals by a set of dates, and then group them by currency.
|
||||
*/
|
||||
private function filterJournalsByDate(array $array, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($array as $journal) {
|
||||
if ($journal['date'] <= $end && $journal['date'] >= $start) {
|
||||
$result[] = $journal;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as above, but for lists that involve transactions without a budget.
|
||||
*
|
||||
@@ -335,117 +169,269 @@ trait PeriodOverview
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function getNoBudgetPeriodOverview(Carbon $start, Carbon $end): array
|
||||
protected function getNoModelPeriodOverview(string $model, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$range = Navigation::getViewRange(true);
|
||||
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty($this->convertToPrimary);
|
||||
$cache->addProperty('no-budget-period-entries');
|
||||
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
}
|
||||
Log::debug(sprintf('Now in getNoModelPeriodOverview(%s, %s %s)', $model, $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
$this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
// get all expenses without a budget.
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setRange($start, $end)->withoutBudget()->withAccountInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
[$start, $end] = $this->getPeriodFromBlocks($dates, $start, $end);
|
||||
$entries = [];
|
||||
$this->statistics = $this->periodStatisticRepo->allInRangeForPrefix(sprintf('no_%s', $model), $start, $end);
|
||||
Log::debug(sprintf('Collected %d stats', $this->statistics->count()));
|
||||
|
||||
foreach ($dates as $currentDate) {
|
||||
$set = $this->filterJournalsByDate($journals, $currentDate['start'], $currentDate['end']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'total_transactions' => count($set),
|
||||
'spent' => $this->groupByCurrency($set),
|
||||
'earned' => [],
|
||||
'transferred_away' => [],
|
||||
'transferred_in' => [],
|
||||
];
|
||||
$entries[] = $this->getSingleNoModelPeriodOverview($model, $currentDate['start'], $currentDate['end'], $currentDate['period']);
|
||||
}
|
||||
$cache->store($entries);
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO fix the date.
|
||||
*
|
||||
* Show period overview for no category view.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function getNoCategoryPeriodOverview(Carbon $theDate): array
|
||||
private function getSingleNoModelPeriodOverview(string $model, Carbon $start, Carbon $end, string $period): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d')));
|
||||
$range = Navigation::getViewRange(true);
|
||||
$first = $this->journalRepos->firstNull();
|
||||
$start = null === $first ? new Carbon() : $first->date;
|
||||
$end = clone $theDate;
|
||||
$end = Navigation::endOfPeriod($end, $range);
|
||||
Log::debug(sprintf('getSingleNoModelPeriodOverview(%s, %s, %s, %s)', $model, $start->format('Y-m-d'), $end->format('Y-m-d'), $period));
|
||||
$statistics = $this->filterPrefixedStatistics($start, $end, sprintf('no_%s', $model));
|
||||
$title = Navigation::periodShow($end, $period);
|
||||
|
||||
app('log')->debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d')));
|
||||
app('log')->debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d')));
|
||||
if (0 === $statistics->count()) {
|
||||
Log::debug(sprintf('Found no statistics in period %s - %s, regenerating them.', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
|
||||
// properties for cache
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
switch ($model) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot deal with model of type "%s"', $model));
|
||||
|
||||
// collect all expenses in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->withoutCategory();
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
|
||||
$earnedSet = $collector->getExtractedJournals();
|
||||
case 'budget':
|
||||
// get all expenses without a budget.
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setRange($start, $end)->withoutBudget()->withAccountInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
$spent = $collector->getExtractedJournals();
|
||||
$earned = [];
|
||||
$transferred = [];
|
||||
|
||||
// collect all income in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->withoutCategory();
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
$spentSet = $collector->getExtractedJournals();
|
||||
break;
|
||||
|
||||
// collect all transfers in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->withoutCategory();
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
|
||||
$transferSet = $collector->getExtractedJournals();
|
||||
case 'category':
|
||||
// collect all expenses in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->withoutCategory();
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
|
||||
$earned = $collector->getExtractedJournals();
|
||||
|
||||
/** @var array $currentDate */
|
||||
foreach ($dates as $currentDate) {
|
||||
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
|
||||
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route('categories.no-category', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferred),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred' => $this->groupByCurrency($transferred),
|
||||
];
|
||||
// collect all income in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->withoutCategory();
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
$spent = $collector->getExtractedJournals();
|
||||
|
||||
// collect all transfers in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->withoutCategory();
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
|
||||
$transferred = $collector->getExtractedJournals();
|
||||
|
||||
break;
|
||||
}
|
||||
$groupedSpent = $this->groupByCurrency($spent);
|
||||
$groupedEarned = $this->groupByCurrency($earned);
|
||||
$groupedTransferred = $this->groupByCurrency($transferred);
|
||||
$entry
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route(sprintf('%s.no-%s', Str::plural($model), $model), [$start->format('Y-m-d'), $end->format('Y-m-d')]),
|
||||
'total_transactions' => count($spent),
|
||||
'spent' => $groupedSpent,
|
||||
'earned' => $groupedEarned,
|
||||
'transferred' => $groupedTransferred,
|
||||
];
|
||||
$this->saveGroupedForPrefix(sprintf('no_%s', $model), $start, $end, 'spent', $groupedSpent);
|
||||
$this->saveGroupedForPrefix(sprintf('no_%s', $model), $start, $end, 'earned', $groupedEarned);
|
||||
$this->saveGroupedForPrefix(sprintf('no_%s', $model), $start, $end, 'transferred', $groupedTransferred);
|
||||
|
||||
return $entry;
|
||||
}
|
||||
app('log')->debug('End of loops');
|
||||
Log::debug(sprintf('Found %d statistics in period %s - %s.', count($statistics), $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
|
||||
return $entries;
|
||||
$entry
|
||||
= [
|
||||
'title' => $title,
|
||||
'route' => route(sprintf('%s.no-%s', Str::plural($model), $model), [$start->format('Y-m-d'), $end->format('Y-m-d')]),
|
||||
'total_transactions' => 0,
|
||||
'spent' => [],
|
||||
'earned' => [],
|
||||
'transferred' => [],
|
||||
];
|
||||
$grouped = [];
|
||||
|
||||
/** @var PeriodStatistic $statistic */
|
||||
foreach ($statistics as $statistic) {
|
||||
$type = str_replace(sprintf('no_%s_', $model), '', $statistic->type);
|
||||
$id = (int)$statistic->transaction_currency_id;
|
||||
$currency = Amount::getTransactionCurrencyById($id);
|
||||
$grouped[$type]['count'] ??= 0;
|
||||
$grouped[$type][$id] = [
|
||||
'amount' => (string)$statistic->amount,
|
||||
'count' => (int)$statistic->count,
|
||||
'currency_id' => $currency->id,
|
||||
'currency_name' => $currency->name,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
];
|
||||
$grouped[$type]['count'] += (int)$statistic->count;
|
||||
}
|
||||
$types = ['spent', 'earned', 'transferred'];
|
||||
foreach ($types as $type) {
|
||||
if (array_key_exists($type, $grouped)) {
|
||||
$entry['total_transactions'] += $grouped[$type]['count'];
|
||||
unset($grouped[$type]['count']);
|
||||
$entry[$type] = $grouped[$type];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
protected function getSingleModelPeriod(Model $model, string $period, Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug(sprintf('Now in getSingleModelPeriod(%s #%d, %s %s)', $model::class, $model->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
$types = ['spent', 'earned', 'transferred_in', 'transferred_away'];
|
||||
$return = [
|
||||
'title' => Navigation::periodShow($start, $period),
|
||||
'route' => route(sprintf('%s.show', strtolower(Str::plural(class_basename($model)))), [$model->id, $start->format('Y-m-d'), $end->format('Y-m-d')]),
|
||||
'total_transactions' => 0,
|
||||
];
|
||||
$this->transactions = [];
|
||||
foreach ($types as $type) {
|
||||
$set = $this->getSingleModelPeriodByType($model, $start, $end, $type);
|
||||
$return['total_transactions'] += $set['count'];
|
||||
unset($set['count']);
|
||||
$return[$type] = $set;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function filterStatistics(Carbon $start, Carbon $end, string $type): Collection
|
||||
{
|
||||
if (0 === $this->statistics->count()) {
|
||||
Log::debug('Have no statistic to filter!');
|
||||
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
return $this->statistics->filter(
|
||||
fn (PeriodStatistic $statistic) => $statistic->start->eq($start) && $statistic->end->eq($end) && $statistic->type === $type
|
||||
);
|
||||
}
|
||||
|
||||
private function filterPrefixedStatistics(Carbon $start, Carbon $end, string $prefix): Collection
|
||||
{
|
||||
if (0 === $this->statistics->count()) {
|
||||
Log::debug('Have no statistic to filter!');
|
||||
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
return $this->statistics->filter(
|
||||
fn (PeriodStatistic $statistic) => $statistic->start->eq($start) && $statistic->end->eq($end) && str_starts_with($statistic->type, $prefix)
|
||||
);
|
||||
}
|
||||
|
||||
private function getSingleModelPeriodByType(Model $model, Carbon $start, Carbon $end, string $type): array
|
||||
{
|
||||
Log::debug(sprintf('Now in getSingleModelPeriodByType(%s #%d, %s %s, %s)', $model::class, $model->id, $start->format('Y-m-d'), $end->format('Y-m-d'), $type));
|
||||
$statistics = $this->filterStatistics($start, $end, $type);
|
||||
|
||||
// nothing found, regenerate them.
|
||||
if (0 === $statistics->count()) {
|
||||
Log::debug(sprintf('Found nothing in this period for type "%s"', $type));
|
||||
if (0 === count($this->transactions)) {
|
||||
switch ($model::class) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot deal with model of type "%s"', $model::class));
|
||||
|
||||
case Category::class:
|
||||
$this->transactions = $this->categoryRepository->periodCollection($model, $start, $end);
|
||||
|
||||
break;
|
||||
|
||||
case Account::class:
|
||||
$this->transactions = $this->accountRepository->periodCollection($model, $start, $end);
|
||||
|
||||
break;
|
||||
|
||||
case Tag::class:
|
||||
$this->transactions = $this->tagRepository->periodCollection($model, $start, $end);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot deal with category period type %s', $type));
|
||||
|
||||
case 'spent':
|
||||
|
||||
$result = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $start, $end);
|
||||
|
||||
break;
|
||||
|
||||
case 'earned':
|
||||
$result = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $start, $end);
|
||||
|
||||
break;
|
||||
|
||||
case 'transferred_in':
|
||||
$result = $this->filterTransfers('in', $start, $end);
|
||||
|
||||
break;
|
||||
|
||||
case 'transferred_away':
|
||||
$result = $this->filterTransfers('away', $start, $end);
|
||||
|
||||
break;
|
||||
}
|
||||
// each result must be grouped by currency, then saved as period statistic.
|
||||
Log::debug(sprintf('Going to group %d found journal(s)', count($result)));
|
||||
$grouped = $this->groupByCurrency($result);
|
||||
|
||||
$this->saveGroupedAsStatistics($model, $start, $end, $type, $grouped);
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
$grouped = [
|
||||
'count' => 0,
|
||||
];
|
||||
|
||||
/** @var PeriodStatistic $statistic */
|
||||
foreach ($statistics as $statistic) {
|
||||
$id = (int)$statistic->transaction_currency_id;
|
||||
$currency = Amount::getTransactionCurrencyById($id);
|
||||
$grouped[$id] = [
|
||||
'amount' => (string)$statistic->amount,
|
||||
'count' => (int)$statistic->count,
|
||||
'currency_id' => $currency->id,
|
||||
'currency_name' => $currency->name,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
];
|
||||
$grouped['count'] += (int)$statistic->count;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -455,96 +441,28 @@ trait PeriodOverview
|
||||
*/
|
||||
protected function getTagPeriodOverview(Tag $tag, Carbon $start, Carbon $end): array // period overview for tags.
|
||||
{
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
$this->tagRepository = app(TagRepositoryInterface::class);
|
||||
$this->tagRepository->setUser($tag->user);
|
||||
$this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);
|
||||
|
||||
// properties for cache
|
||||
$cache = new CacheProperties();
|
||||
$cache->addProperty($start);
|
||||
$cache->addProperty($end);
|
||||
$cache->addProperty('tag-period-entries');
|
||||
$cache->addProperty($tag->id);
|
||||
if ($cache->has()) {
|
||||
return $cache->get();
|
||||
}
|
||||
$range = Navigation::getViewRange(true);
|
||||
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
|
||||
|
||||
/** @var array $dates */
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
$dates = Navigation::blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
[$start, $end] = $this->getPeriodFromBlocks($dates, $start, $end);
|
||||
$this->statistics = $this->periodStatisticRepo->allInRangeForModel($tag, $start, $end);
|
||||
|
||||
// collect all expenses in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setTag($tag);
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
|
||||
$earnedSet = $collector->getExtractedJournals();
|
||||
|
||||
// collect all income in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setTag($tag);
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
$spentSet = $collector->getExtractedJournals();
|
||||
|
||||
// collect all transfers in this period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setTag($tag);
|
||||
$collector->setRange($start, $end);
|
||||
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
|
||||
$transferSet = $collector->getExtractedJournals();
|
||||
|
||||
// filer all of them:
|
||||
$earnedSet = $this->filterJournalsByTag($earnedSet, $tag);
|
||||
$spentSet = $this->filterJournalsByTag($spentSet, $tag);
|
||||
$transferSet = $this->filterJournalsByTag($transferSet, $tag);
|
||||
|
||||
Log::debug(sprintf('Count of loops: %d', count($dates)));
|
||||
foreach ($dates as $currentDate) {
|
||||
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
|
||||
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
|
||||
$transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
|
||||
$title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
|
||||
$entries[]
|
||||
= [
|
||||
'transactions' => 0,
|
||||
'title' => $title,
|
||||
'route' => route(
|
||||
'tags.show',
|
||||
[$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
|
||||
),
|
||||
'total_transactions' => count($spent) + count($earned) + count($transferred),
|
||||
'spent' => $this->groupByCurrency($spent),
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred' => $this->groupByCurrency($transferred),
|
||||
];
|
||||
$entries[] = $this->getSingleModelPeriod($tag, $currentDate['period'], $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
private function filterJournalsByTag(array $set, Tag $tag): array
|
||||
{
|
||||
$return = [];
|
||||
foreach ($set as $entry) {
|
||||
$found = false;
|
||||
|
||||
/** @var array $localTag */
|
||||
foreach ($entry['tags'] as $localTag) {
|
||||
if ($localTag['id'] === $tag->id) {
|
||||
$found = true;
|
||||
}
|
||||
}
|
||||
if (false === $found) {
|
||||
continue;
|
||||
}
|
||||
$return[] = $entry;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -606,35 +524,148 @@ trait PeriodOverview
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only transactions where $account is the source.
|
||||
*/
|
||||
private function filterTransferredAway(Account $account, array $journals): array
|
||||
private function saveGroupedAsStatistics(Model $model, Carbon $start, Carbon $end, string $type, array $array): void
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if ($account->id === (int)$journal['source_account_id']) {
|
||||
$return[] = $journal;
|
||||
}
|
||||
unset($array['count']);
|
||||
Log::debug(sprintf('saveGroupedAsStatistics(%s #%d, %s, %s, "%s", array(%d))', $model::class, $model->id, $start->format('Y-m-d'), $end->format('Y-m-d'), $type, count($array)));
|
||||
foreach ($array as $entry) {
|
||||
$this->periodStatisticRepo->saveStatistic($model, $entry['currency_id'], $start, $end, $type, $entry['count'], $entry['amount']);
|
||||
}
|
||||
if (0 === count($array)) {
|
||||
Log::debug('Save empty statistic.');
|
||||
$this->periodStatisticRepo->saveStatistic($model, $this->primaryCurrency->id, $start, $end, $type, 0, '0');
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
private function saveGroupedForPrefix(string $prefix, Carbon $start, Carbon $end, string $type, array $array): void
|
||||
{
|
||||
unset($array['count']);
|
||||
Log::debug(sprintf('saveGroupedForPrefix("%s", %s, %s, "%s", array(%d))', $prefix, $start->format('Y-m-d'), $end->format('Y-m-d'), $type, count($array)));
|
||||
foreach ($array as $entry) {
|
||||
$this->periodStatisticRepo->savePrefixedStatistic($prefix, $entry['currency_id'], $start, $end, $type, $entry['count'], $entry['amount']);
|
||||
}
|
||||
if (0 === count($array)) {
|
||||
Log::debug('Save empty statistic.');
|
||||
$this->periodStatisticRepo->savePrefixedStatistic($prefix, $this->primaryCurrency->id, $start, $end, $type, 0, '0');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return only transactions where $account is the source.
|
||||
* Filter a list of journals by a set of dates, and then group them by currency.
|
||||
*/
|
||||
private function filterTransferredIn(Account $account, array $journals): array
|
||||
private function filterJournalsByDate(array $array, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
$result = [];
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($array as $journal) {
|
||||
if ($journal['date'] <= $end && $journal['date'] >= $start) {
|
||||
$result[] = $journal;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function filterTransactionsByType(TransactionTypeEnum $type, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
/**
|
||||
* @var int $index
|
||||
* @var array $item
|
||||
*/
|
||||
foreach ($this->transactions as $item) {
|
||||
$date = Carbon::parse($item['date']);
|
||||
$fits = $item['type'] === $type->value && $date >= $start && $date <= $end;
|
||||
if ($fits) {
|
||||
|
||||
// if type is withdrawal, negative amount:
|
||||
if (TransactionTypeEnum::WITHDRAWAL === $type && 1 === bccomp((string)$item['amount'], '0')) {
|
||||
$item['amount'] = Steam::negative($item['amount']);
|
||||
}
|
||||
$result[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function filterTransfers(string $direction, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
/**
|
||||
* @var int $index
|
||||
* @var array $item
|
||||
*/
|
||||
foreach ($this->transactions as $item) {
|
||||
$date = Carbon::parse($item['date']);
|
||||
if ($date >= $start && $date <= $end) {
|
||||
if ('Transfer' === $item['type'] && 'away' === $direction && -1 === bccomp((string)$item['amount'], '0')) {
|
||||
$result[] = $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
if ('Transfer' === $item['type'] && 'in' === $direction && 1 === bccomp((string)$item['amount'], '0')) {
|
||||
$result[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function groupByCurrency(array $journals): array
|
||||
{
|
||||
Log::debug('groupByCurrency()');
|
||||
$return = [
|
||||
'count' => 0,
|
||||
];
|
||||
if (0 === count($journals)) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if ($account->id === (int)$journal['destination_account_id']) {
|
||||
$return[] = $journal;
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
$currencyDecimalPlaces = $journal['currency_decimal_places'];
|
||||
$foreignCurrencyId = $journal['foreign_currency_id'];
|
||||
$amount = $journal['amount'] ?? '0';
|
||||
|
||||
if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId !== $this->primaryCurrency->id) {
|
||||
$amount = $journal['pc_amount'] ?? '0';
|
||||
$currencyId = $this->primaryCurrency->id;
|
||||
$currencyCode = $this->primaryCurrency->code;
|
||||
$currencyName = $this->primaryCurrency->name;
|
||||
$currencySymbol = $this->primaryCurrency->symbol;
|
||||
$currencyDecimalPlaces = $this->primaryCurrency->decimal_places;
|
||||
}
|
||||
if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId === $this->primaryCurrency->id) {
|
||||
$currencyId = (int)$foreignCurrencyId;
|
||||
$currencyCode = $journal['foreign_currency_code'];
|
||||
$currencyName = $journal['foreign_currency_name'];
|
||||
$currencySymbol = $journal['foreign_currency_symbol'];
|
||||
$currencyDecimalPlaces = $journal['foreign_currency_decimal_places'];
|
||||
$amount = $journal['foreign_amount'] ?? '0';
|
||||
}
|
||||
$return[$currencyId] ??= [
|
||||
'amount' => '0',
|
||||
'count' => 0,
|
||||
'currency_id' => $currencyId,
|
||||
'currency_name' => $currencyName,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
|
||||
|
||||
$return[$currencyId]['amount'] = bcadd((string)$return[$currencyId]['amount'], $amount);
|
||||
++$return[$currencyId]['count'];
|
||||
++$return['count'];
|
||||
}
|
||||
|
||||
return $return;
|
||||
|
||||
@@ -56,10 +56,10 @@ trait RenderPartialViews
|
||||
|
||||
/** @var BudgetRepositoryInterface $budgetRepository */
|
||||
$budgetRepository = app(BudgetRepositoryInterface::class);
|
||||
$budget = $budgetRepository->find((int) $attributes['budgetId']);
|
||||
$budget = $budgetRepository->find((int)$attributes['budgetId']);
|
||||
|
||||
$accountRepos = app(AccountRepositoryInterface::class);
|
||||
$account = $accountRepos->find((int) $attributes['accountId']);
|
||||
$account = $accountRepos->find((int)$attributes['accountId']);
|
||||
|
||||
if (null === $budget || null === $account) {
|
||||
throw new FireflyException('Could not render popup.report.balance-amount because budget or account is null.');
|
||||
@@ -115,7 +115,7 @@ trait RenderPartialViews
|
||||
/** @var PopupReportInterface $popupHelper */
|
||||
$popupHelper = app(PopupReportInterface::class);
|
||||
|
||||
$budget = $budgetRepository->find((int) $attributes['budgetId']);
|
||||
$budget = $budgetRepository->find((int)$attributes['budgetId']);
|
||||
if (null === $budget) {
|
||||
// transactions without a budget.
|
||||
$budget = new Budget();
|
||||
@@ -146,7 +146,7 @@ trait RenderPartialViews
|
||||
|
||||
/** @var CategoryRepositoryInterface $categoryRepository */
|
||||
$categoryRepository = app(CategoryRepositoryInterface::class);
|
||||
$category = $categoryRepository->find((int) $attributes['categoryId']);
|
||||
$category = $categoryRepository->find((int)$attributes['categoryId']);
|
||||
$journals = $popupHelper->byCategory($category, $attributes);
|
||||
|
||||
try {
|
||||
@@ -239,7 +239,7 @@ trait RenderPartialViews
|
||||
/** @var PopupReportInterface $popupHelper */
|
||||
$popupHelper = app(PopupReportInterface::class);
|
||||
|
||||
$account = $accountRepository->find((int) $attributes['accountId']);
|
||||
$account = $accountRepository->find((int)$attributes['accountId']);
|
||||
|
||||
if (null === $account) {
|
||||
return 'This is an unknown account. Apologies.';
|
||||
@@ -310,7 +310,7 @@ trait RenderPartialViews
|
||||
$triggers = [];
|
||||
foreach ($operators as $key => $operator) {
|
||||
if ('user_action' !== $key && false === $operator['alias']) {
|
||||
$triggers[$key] = (string) trans(sprintf('firefly.rule_trigger_%s_choice', $key));
|
||||
$triggers[$key] = (string)trans(sprintf('firefly.rule_trigger_%s_choice', $key));
|
||||
}
|
||||
}
|
||||
asort($triggers);
|
||||
@@ -325,7 +325,7 @@ trait RenderPartialViews
|
||||
$count = ($index + 1);
|
||||
|
||||
try {
|
||||
$rootOperator = OperatorQuerySearch::getRootOperator((string) $entry->trigger_type);
|
||||
$rootOperator = OperatorQuerySearch::getRootOperator((string)$entry->trigger_type);
|
||||
if (str_starts_with($rootOperator, '-')) {
|
||||
$rootOperator = substr($rootOperator, 1);
|
||||
}
|
||||
@@ -335,7 +335,7 @@ trait RenderPartialViews
|
||||
'oldTrigger' => $rootOperator,
|
||||
'oldValue' => $entry->trigger_value,
|
||||
'oldChecked' => $entry->stop_processing,
|
||||
'oldProhibited' => str_starts_with((string) $entry->trigger_type, '-'),
|
||||
'oldProhibited' => str_starts_with((string)$entry->trigger_type, '-'),
|
||||
'count' => $count,
|
||||
'triggers' => $triggers,
|
||||
]
|
||||
@@ -366,7 +366,7 @@ trait RenderPartialViews
|
||||
|
||||
/** @var PopupReportInterface $popupHelper */
|
||||
$popupHelper = app(PopupReportInterface::class);
|
||||
$account = $accountRepository->find((int) $attributes['accountId']);
|
||||
$account = $accountRepository->find((int)$attributes['accountId']);
|
||||
|
||||
if (null === $account) {
|
||||
return 'This is an unknown category. Apologies.';
|
||||
|
||||
@@ -32,9 +32,9 @@ use FireflyIII\Support\Binder\AccountList;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Validation\Validator as ValidatorContract;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
use function Safe\parse_url;
|
||||
|
||||
@@ -54,6 +54,22 @@ trait RequestInformation
|
||||
return $parts['host'] ?? '';
|
||||
}
|
||||
|
||||
final protected function getPageName(): string // get request info
|
||||
{
|
||||
return str_replace('.', '_', RouteFacade::currentRouteName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specific name of a page for intro.
|
||||
*/
|
||||
final protected function getSpecificPageName(): string // get request info
|
||||
{
|
||||
/** @var null|string $param */
|
||||
$param = RouteFacade::current()->parameter('objectType');
|
||||
|
||||
return null === $param ? '' : sprintf('_%s', $param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of triggers.
|
||||
*/
|
||||
@@ -67,7 +83,7 @@ trait RequestInformation
|
||||
'type' => $triggerInfo['type'] ?? '',
|
||||
'value' => $triggerInfo['value'] ?? '',
|
||||
'prohibited' => $triggerInfo['prohibited'] ?? false,
|
||||
'stop_processing' => 1 === (int) ($triggerInfo['stop_processing'] ?? '0'),
|
||||
'stop_processing' => 1 === (int)($triggerInfo['stop_processing'] ?? '0'),
|
||||
];
|
||||
$current = RuleFormRequest::replaceAmountTrigger($current);
|
||||
$triggers[] = $current;
|
||||
@@ -103,22 +119,6 @@ trait RequestInformation
|
||||
return $shownDemo;
|
||||
}
|
||||
|
||||
final protected function getPageName(): string // get request info
|
||||
{
|
||||
return str_replace('.', '_', RouteFacade::currentRouteName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specific name of a page for intro.
|
||||
*/
|
||||
final protected function getSpecificPageName(): string // get request info
|
||||
{
|
||||
/** @var null|string $param */
|
||||
$param = RouteFacade::current()->parameter('objectType');
|
||||
|
||||
return null === $param ? '' : sprintf('_%s', $param);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date is outside session range.
|
||||
*/
|
||||
@@ -172,11 +172,11 @@ trait RequestInformation
|
||||
final protected function validatePassword(User $user, string $current, string $new): bool // get request info
|
||||
{
|
||||
if (!Hash::check($current, $user->password)) {
|
||||
throw new ValidationException((string) trans('firefly.invalid_current_password'));
|
||||
throw new ValidationException((string)trans('firefly.invalid_current_password'));
|
||||
}
|
||||
|
||||
if ($current === $new) {
|
||||
throw new ValidationException((string) trans('firefly.should_change'));
|
||||
throw new ValidationException((string)trans('firefly.should_change'));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -51,7 +51,7 @@ trait RuleManagement
|
||||
[
|
||||
'oldAction' => $oldAction['type'],
|
||||
'oldValue' => $oldAction['value'] ?? '',
|
||||
'oldChecked' => 1 === (int) ($oldAction['stop_processing'] ?? '0'),
|
||||
'oldChecked' => 1 === (int)($oldAction['stop_processing'] ?? '0'),
|
||||
'count' => $index + 1,
|
||||
]
|
||||
)->render();
|
||||
@@ -78,7 +78,7 @@ trait RuleManagement
|
||||
$triggers = [];
|
||||
foreach ($operators as $key => $operator) {
|
||||
if ('user_action' !== $key && false === $operator['alias']) {
|
||||
$triggers[$key] = (string) trans(sprintf('firefly.rule_trigger_%s_choice', $key));
|
||||
$triggers[$key] = (string)trans(sprintf('firefly.rule_trigger_%s_choice', $key));
|
||||
}
|
||||
}
|
||||
asort($triggers);
|
||||
@@ -94,8 +94,8 @@ trait RuleManagement
|
||||
[
|
||||
'oldTrigger' => OperatorQuerySearch::getRootOperator($oldTrigger['type']),
|
||||
'oldValue' => $oldTrigger['value'] ?? '',
|
||||
'oldChecked' => 1 === (int) ($oldTrigger['stop_processing'] ?? '0'),
|
||||
'oldProhibited' => 1 === (int) ($oldTrigger['prohibited'] ?? '0'),
|
||||
'oldChecked' => 1 === (int)($oldTrigger['stop_processing'] ?? '0'),
|
||||
'oldProhibited' => 1 === (int)($oldTrigger['prohibited'] ?? '0'),
|
||||
'count' => $index + 1,
|
||||
'triggers' => $triggers,
|
||||
]
|
||||
@@ -124,7 +124,7 @@ trait RuleManagement
|
||||
$triggers = [];
|
||||
foreach ($operators as $key => $operator) {
|
||||
if ('user_action' !== $key && false === $operator['alias']) {
|
||||
$triggers[$key] = (string) trans(sprintf('firefly.rule_trigger_%s_choice', $key));
|
||||
$triggers[$key] = (string)trans(sprintf('firefly.rule_trigger_%s_choice', $key));
|
||||
}
|
||||
}
|
||||
asort($triggers);
|
||||
@@ -132,7 +132,7 @@ trait RuleManagement
|
||||
$index = 0;
|
||||
foreach ($submittedOperators as $operator) {
|
||||
$rootOperator = OperatorQuerySearch::getRootOperator($operator['type']);
|
||||
$needsContext = (bool) config(sprintf('search.operators.%s.needs_context', $rootOperator));
|
||||
$needsContext = (bool)config(sprintf('search.operators.%s.needs_context', $rootOperator));
|
||||
|
||||
try {
|
||||
$renderedEntries[] = view(
|
||||
@@ -164,8 +164,8 @@ trait RuleManagement
|
||||
$repository = app(RuleGroupRepositoryInterface::class);
|
||||
if (0 === $repository->count()) {
|
||||
$data = [
|
||||
'title' => (string) trans('firefly.default_rule_group_name'),
|
||||
'description' => (string) trans('firefly.default_rule_group_description'),
|
||||
'title' => (string)trans('firefly.default_rule_group_name'),
|
||||
'description' => (string)trans('firefly.default_rule_group_description'),
|
||||
'active' => true,
|
||||
];
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ trait UserNavigation
|
||||
final protected function getPreviousUrl(string $identifier): string
|
||||
{
|
||||
app('log')->debug(sprintf('Trying to retrieve URL stored under "%s"', $identifier));
|
||||
$url = (string) session($identifier);
|
||||
$url = (string)session($identifier);
|
||||
app('log')->debug(sprintf('The URL is %s', $url));
|
||||
|
||||
return app('steam')->getSafeUrl($url, route('index'));
|
||||
|
||||
@@ -53,29 +53,29 @@ use Override;
|
||||
*/
|
||||
class AccountEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private array $ids = [];
|
||||
private array $accountTypeIds = [];
|
||||
private array $accountTypes = [];
|
||||
private Collection $collection;
|
||||
private array $currencies = [];
|
||||
private array $locations = [];
|
||||
private array $meta = [];
|
||||
private readonly TransactionCurrency $primaryCurrency;
|
||||
private array $notes = [];
|
||||
private array $openingBalances = [];
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private array $lastActivities = [];
|
||||
private ?Carbon $date = null;
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $accountTypeIds = [];
|
||||
private array $accountTypes = [];
|
||||
private array $balances = [];
|
||||
private Collection $collection;
|
||||
private readonly bool $convertToPrimary;
|
||||
private array $balances = [];
|
||||
private array $startBalances = [];
|
||||
private array $endBalances = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $sort = [];
|
||||
private array $currencies = [];
|
||||
private ?Carbon $date = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $endBalances = [];
|
||||
private array $ids = [];
|
||||
private array $lastActivities = [];
|
||||
private array $locations = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $meta = [];
|
||||
private array $notes = [];
|
||||
private array $objectGroups = [];
|
||||
private array $openingBalances = [];
|
||||
private readonly TransactionCurrency $primaryCurrency;
|
||||
private array $sort = [];
|
||||
private ?Carbon $start = null;
|
||||
private array $startBalances = [];
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
|
||||
/**
|
||||
* TODO The account enricher must do conversion from and to the primary currency.
|
||||
@@ -86,16 +86,6 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
$this->convertToPrimary = Amount::convertToPrimary();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function enrichSingle(array|Model $model): Account|array
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$collection = new Collection()->push($model);
|
||||
$collection = $this->enrich($collection);
|
||||
|
||||
return $collection->first();
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* Do the actual enrichment.
|
||||
@@ -121,114 +111,47 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
#[Override]
|
||||
public function enrichSingle(array|Model $model): Account|array
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($this->collection as $account) {
|
||||
$this->ids[] = (int)$account->id;
|
||||
$this->accountTypeIds[] = (int)$account->account_type_id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
$this->accountTypeIds = array_unique($this->accountTypeIds);
|
||||
Log::debug(__METHOD__);
|
||||
$collection = new Collection()->push($model);
|
||||
$collection = $this->enrich($collection);
|
||||
|
||||
return $collection->first();
|
||||
}
|
||||
|
||||
private function getAccountTypes(): void
|
||||
public function getDate(): Carbon
|
||||
{
|
||||
$types = AccountType::whereIn('id', $this->accountTypeIds)->get();
|
||||
|
||||
/** @var AccountType $type */
|
||||
foreach ($types as $type) {
|
||||
$this->accountTypes[(int)$type->id] = $type->type;
|
||||
if (!$this->date instanceof Carbon) {
|
||||
return now();
|
||||
}
|
||||
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
private function collectMetaData(): void
|
||||
public function setDate(?Carbon $date): void
|
||||
{
|
||||
$set = AccountMeta::whereIn('name', ['is_multi_currency', 'include_net_worth', 'currency_id', 'account_role', 'account_number', 'BIC', 'liability_direction', 'interest', 'interest_period', 'current_debt'])
|
||||
->whereIn('account_id', $this->ids)
|
||||
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray()
|
||||
;
|
||||
|
||||
/** @var array $entry */
|
||||
foreach ($set as $entry) {
|
||||
$this->meta[(int)$entry['account_id']][$entry['name']] = (string)$entry['data'];
|
||||
if ('currency_id' === $entry['name']) {
|
||||
$this->currencies[(int)$entry['data']] = true;
|
||||
}
|
||||
}
|
||||
if (count($this->currencies) > 0) {
|
||||
$currencies = TransactionCurrency::whereIn('id', array_keys($this->currencies))->get();
|
||||
foreach ($currencies as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
}
|
||||
$this->currencies[0] = $this->primaryCurrency;
|
||||
foreach ($this->currencies as $id => $currency) {
|
||||
if (true === $currency) {
|
||||
throw new FireflyException(sprintf('Currency #%d not found.', $id));
|
||||
}
|
||||
if ($date instanceof Carbon) {
|
||||
$date->endOfDay();
|
||||
Log::debug(sprintf('Date is now %s', $date->toW3cString()));
|
||||
}
|
||||
$this->date = $date;
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
public function setEnd(?Carbon $end): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->ids)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
private function collectLocations(): void
|
||||
public function setSort(array $sort): void
|
||||
{
|
||||
$locations = Location::query()->whereIn('locatable_id', $this->ids)
|
||||
->where('locatable_type', Account::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray()
|
||||
;
|
||||
foreach ($locations as $location) {
|
||||
$this->locations[(int)$location['locatable_id']]
|
||||
= [
|
||||
'latitude' => (float)$location['latitude'],
|
||||
'longitude' => (float)$location['longitude'],
|
||||
'zoom_level' => (int)$location['zoom_level'],
|
||||
];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
|
||||
$this->sort = $sort;
|
||||
}
|
||||
|
||||
private function collectOpeningBalances(): void
|
||||
public function setStart(?Carbon $start): void
|
||||
{
|
||||
// use new group collector:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector
|
||||
->setUser($this->user)
|
||||
->setUserGroup($this->userGroup)
|
||||
->setAccounts($this->collection)
|
||||
->withAccountInformation()
|
||||
->setTypes([TransactionTypeEnum::OPENING_BALANCE->value])
|
||||
;
|
||||
$journals = $collector->getExtractedJournals();
|
||||
foreach ($journals as $journal) {
|
||||
$this->openingBalances[(int)$journal['source_account_id']]
|
||||
= [
|
||||
'amount' => Steam::negative($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
$this->openingBalances[(int)$journal['destination_account_id']]
|
||||
= [
|
||||
'amount' => Steam::positive($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
@@ -237,6 +160,11 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
$this->userGroup = $user->userGroup;
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (Account $item) {
|
||||
@@ -357,11 +285,6 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
});
|
||||
}
|
||||
|
||||
private function collectLastActivities(): void
|
||||
{
|
||||
$this->lastActivities = Steam::getLastActivities($this->ids);
|
||||
}
|
||||
|
||||
private function collectBalances(): void
|
||||
{
|
||||
$this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
|
||||
@@ -371,6 +294,79 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($this->collection as $account) {
|
||||
$this->ids[] = (int)$account->id;
|
||||
$this->accountTypeIds[] = (int)$account->account_type_id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
$this->accountTypeIds = array_unique($this->accountTypeIds);
|
||||
}
|
||||
|
||||
private function collectLastActivities(): void
|
||||
{
|
||||
$this->lastActivities = Steam::getLastActivities($this->ids);
|
||||
}
|
||||
|
||||
private function collectLocations(): void
|
||||
{
|
||||
$locations = Location::query()->whereIn('locatable_id', $this->ids)
|
||||
->where('locatable_type', Account::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray()
|
||||
;
|
||||
foreach ($locations as $location) {
|
||||
$this->locations[(int)$location['locatable_id']]
|
||||
= [
|
||||
'latitude' => (float)$location['latitude'],
|
||||
'longitude' => (float)$location['longitude'],
|
||||
'zoom_level' => (int)$location['zoom_level'],
|
||||
];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
|
||||
}
|
||||
|
||||
private function collectMetaData(): void
|
||||
{
|
||||
$set = AccountMeta::whereIn('name', ['is_multi_currency', 'include_net_worth', 'currency_id', 'account_role', 'account_number', 'BIC', 'liability_direction', 'interest', 'interest_period', 'current_debt'])
|
||||
->whereIn('account_id', $this->ids)
|
||||
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray()
|
||||
;
|
||||
|
||||
/** @var array $entry */
|
||||
foreach ($set as $entry) {
|
||||
$this->meta[(int)$entry['account_id']][$entry['name']] = (string)$entry['data'];
|
||||
if ('currency_id' === $entry['name']) {
|
||||
$this->currencies[(int)$entry['data']] = true;
|
||||
}
|
||||
}
|
||||
if (count($this->currencies) > 0) {
|
||||
$currencies = TransactionCurrency::whereIn('id', array_keys($this->currencies))->get();
|
||||
foreach ($currencies as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
}
|
||||
$this->currencies[0] = $this->primaryCurrency;
|
||||
foreach ($this->currencies as $id => $currency) {
|
||||
if (true === $currency) {
|
||||
throw new FireflyException(sprintf('Currency #%d not found.', $id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->ids)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
}
|
||||
|
||||
private function collectObjectGroups(): void
|
||||
{
|
||||
$set = DB::table('object_groupables')
|
||||
@@ -393,32 +389,41 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
}
|
||||
}
|
||||
|
||||
public function setDate(?Carbon $date): void
|
||||
private function collectOpeningBalances(): void
|
||||
{
|
||||
if ($date instanceof Carbon) {
|
||||
$date->endOfDay();
|
||||
Log::debug(sprintf('Date is now %s', $date->toW3cString()));
|
||||
// use new group collector:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector
|
||||
->setUser($this->user)
|
||||
->setUserGroup($this->userGroup)
|
||||
->setAccounts($this->collection)
|
||||
->withAccountInformation()
|
||||
->setTypes([TransactionTypeEnum::OPENING_BALANCE->value])
|
||||
;
|
||||
$journals = $collector->getExtractedJournals();
|
||||
foreach ($journals as $journal) {
|
||||
$this->openingBalances[(int)$journal['source_account_id']]
|
||||
= [
|
||||
'amount' => Steam::negative($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
$this->openingBalances[(int)$journal['destination_account_id']]
|
||||
= [
|
||||
'amount' => Steam::positive($journal['amount']),
|
||||
'date' => $journal['date'],
|
||||
];
|
||||
}
|
||||
$this->date = $date;
|
||||
}
|
||||
|
||||
public function getDate(): Carbon
|
||||
private function getAccountTypes(): void
|
||||
{
|
||||
if (!$this->date instanceof Carbon) {
|
||||
return now();
|
||||
$types = AccountType::whereIn('id', $this->accountTypeIds)->get();
|
||||
|
||||
/** @var AccountType $type */
|
||||
foreach ($types as $type) {
|
||||
$this->accountTypes[(int)$type->id] = $type->type;
|
||||
}
|
||||
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function setStart(?Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function setEnd(?Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
private function getBalanceDifference(int $id, TransactionCurrency $currency): ?string
|
||||
@@ -437,11 +442,6 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
return bcsub($end, $start);
|
||||
}
|
||||
|
||||
public function setSort(array $sort): void
|
||||
{
|
||||
$this->sort = $sort;
|
||||
}
|
||||
|
||||
private function sortData(): void
|
||||
{
|
||||
$dbParams = config('firefly.allowed_db_sort_parameters.Account', []);
|
||||
|
||||
@@ -40,20 +40,20 @@ use Override;
|
||||
|
||||
class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private User $user; // @phpstan-ignore-line
|
||||
private UserGroup $userGroup; // @phpstan-ignore-line
|
||||
private readonly bool $convertToPrimary;
|
||||
private array $ids = [];
|
||||
private array $currencyIds = [];
|
||||
private Collection $collection; // @phpstan-ignore-line
|
||||
private readonly bool $convertToPrimary; // @phpstan-ignore-line
|
||||
private array $currencies = [];
|
||||
private Collection $collection;
|
||||
private array $spentInBudgets = [];
|
||||
private array $spentOutsideBudgets = [];
|
||||
private array $pcSpentInBudgets = [];
|
||||
private array $pcSpentOutsideBudgets = [];
|
||||
private array $currencyIds = [];
|
||||
private array $ids = [];
|
||||
private readonly NoBudgetRepositoryInterface $noBudgetRepository;
|
||||
private readonly OperationsRepositoryInterface $opsRepository;
|
||||
private array $pcSpentInBudgets = [];
|
||||
private array $pcSpentOutsideBudgets = [];
|
||||
private readonly BudgetRepositoryInterface $repository;
|
||||
private array $spentInBudgets = [];
|
||||
private array $spentOutsideBudgets = [];
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -104,6 +104,34 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
$this->repository->setUserGroup($userGroup);
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (AvailableBudget $item) {
|
||||
$id = (int)$item->id;
|
||||
$currencyId = $this->currencyIds[$id];
|
||||
$currency = $this->currencies[$currencyId];
|
||||
$meta = [
|
||||
'currency' => $currency,
|
||||
'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] ?? [],
|
||||
];
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
}
|
||||
|
||||
private function collectCurrencies(): void
|
||||
{
|
||||
$ids = array_unique(array_values($this->currencyIds));
|
||||
$set = TransactionCurrency::whereIn('id', $ids)->get();
|
||||
foreach ($set as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var AvailableBudget $availableBudget */
|
||||
@@ -138,32 +166,4 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (AvailableBudget $item) {
|
||||
$id = (int)$item->id;
|
||||
$currencyId = $this->currencyIds[$id];
|
||||
$currency = $this->currencies[$currencyId];
|
||||
$meta = [
|
||||
'currency' => $currency,
|
||||
'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] ?? [],
|
||||
];
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
}
|
||||
|
||||
private function collectCurrencies(): void
|
||||
{
|
||||
$ids = array_unique(array_values($this->currencyIds));
|
||||
$set = TransactionCurrency::whereIn('id', $ids)->get();
|
||||
foreach ($set as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,19 +40,19 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BudgetEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection;
|
||||
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 $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $autoBudgets = [];
|
||||
private Collection $collection;
|
||||
private array $currencies = [];
|
||||
private ?Carbon $end = null;
|
||||
private array $ids = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $notes = [];
|
||||
private array $objectGroups = [];
|
||||
private array $pcSpent = [];
|
||||
private array $spent = [];
|
||||
private ?Carbon $start = null;
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
@@ -79,6 +79,16 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
return $collection->first();
|
||||
}
|
||||
|
||||
public function setEnd(?Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function setStart(?Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
@@ -90,28 +100,6 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var Budget $budget */
|
||||
foreach ($this->collection as $budget) {
|
||||
$this->ids[] = (int)$budget->id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->ids)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', Budget::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (Budget $item) {
|
||||
@@ -130,7 +118,7 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = (string) $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_id'] = (string)$this->objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
|
||||
}
|
||||
@@ -177,14 +165,26 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
}
|
||||
}
|
||||
|
||||
public function setEnd(?Carbon $end): void
|
||||
private function collectIds(): void
|
||||
{
|
||||
$this->end = $end;
|
||||
/** @var Budget $budget */
|
||||
foreach ($this->collection as $budget) {
|
||||
$this->ids[] = (int)$budget->id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
}
|
||||
|
||||
public function setStart(?Carbon $start): void
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$this->start = $start;
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->ids)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', Budget::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
}
|
||||
|
||||
private function collectObjectGroups(): void
|
||||
|
||||
@@ -40,19 +40,19 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private User $user;
|
||||
private UserGroup $userGroup; // @phpstan-ignore-line
|
||||
private Collection $collection;
|
||||
private array $ids = [];
|
||||
private array $notes = [];
|
||||
private Carbon $start;
|
||||
private bool $convertToPrimary = true; // @phpstan-ignore-line
|
||||
private array $currencies = [];
|
||||
private array $currencyIds = [];
|
||||
private Carbon $end;
|
||||
private array $expenses = [];
|
||||
private array $ids = [];
|
||||
private array $notes = [];
|
||||
private array $pcExpenses = [];
|
||||
private array $currencyIds = [];
|
||||
private array $currencies = [];
|
||||
private bool $convertToPrimary = true;
|
||||
private readonly TransactionCurrency $primaryCurrency;
|
||||
private Carbon $start;
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -93,36 +93,6 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
$this->start = $this->collection->min('start_date') ?? Carbon::now()->startOfMonth();
|
||||
$this->end = $this->collection->max('end_date') ?? Carbon::now()->endOfMonth();
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($this->collection as $limit) {
|
||||
$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->currencyIds = array_unique($this->currencyIds);
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->ids)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', BudgetLimit::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (BudgetLimit $item) {
|
||||
@@ -179,6 +149,44 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
$this->start = $this->collection->min('start_date') ?? Carbon::now()->startOfMonth();
|
||||
$this->end = $this->collection->max('end_date') ?? Carbon::now()->endOfMonth();
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($this->collection as $limit) {
|
||||
$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->currencyIds = array_unique($this->currencyIds);
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->ids)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', BudgetLimit::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
}
|
||||
|
||||
private function filterToBudget(array $expenses, int $budget): array
|
||||
{
|
||||
$result = array_filter($expenses, fn (array $item) => (int)$item['budget_id'] === $budget);
|
||||
Log::debug(sprintf('filterToBudget for budget #%d, from %d to %d items', $budget, count($expenses), count($result)));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function stringifyIds(): void
|
||||
{
|
||||
$this->expenses = array_map(fn ($first) => array_map(function ($second) {
|
||||
@@ -193,12 +201,4 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
return $second;
|
||||
}, $first), $this->expenses);
|
||||
}
|
||||
|
||||
private function filterToBudget(array $expenses, int $budget): array
|
||||
{
|
||||
$result = array_filter($expenses, fn (array $item) => (int)$item['budget_id'] === $budget);
|
||||
Log::debug(sprintf('filterToBudget for budget #%d, from %d to %d items', $budget, count($expenses), count($result)));
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,18 +38,18 @@ use Illuminate\Support\Facades\Log;
|
||||
class CategoryEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection;
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private array $earned = [];
|
||||
private ?Carbon $end = null;
|
||||
private array $ids = [];
|
||||
private array $notes = [];
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $spent = [];
|
||||
private array $pcSpent = [];
|
||||
private array $earned = [];
|
||||
private array $pcEarned = [];
|
||||
private array $transfers = [];
|
||||
private array $pcSpent = [];
|
||||
private array $pcTransfers = [];
|
||||
private array $spent = [];
|
||||
private ?Carbon $start = null;
|
||||
private array $transfers = [];
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
|
||||
public function enrich(Collection $collection): Collection
|
||||
{
|
||||
@@ -71,6 +71,16 @@ class CategoryEnrichment implements EnrichmentInterface
|
||||
return $collection->first();
|
||||
}
|
||||
|
||||
public function setEnd(?Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function setStart(?Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
@@ -82,15 +92,6 @@ class CategoryEnrichment implements EnrichmentInterface
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var Category $category */
|
||||
foreach ($this->collection as $category) {
|
||||
$this->ids[] = (int)$category->id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (Category $item) {
|
||||
@@ -110,14 +111,13 @@ class CategoryEnrichment implements EnrichmentInterface
|
||||
});
|
||||
}
|
||||
|
||||
public function setEnd(?Carbon $end): void
|
||||
private function collectIds(): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
public function setStart(?Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
/** @var Category $category */
|
||||
foreach ($this->collection as $category) {
|
||||
$this->ids[] = (int)$category->id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
@@ -135,7 +135,7 @@ class CategoryEnrichment implements EnrichmentInterface
|
||||
|
||||
private function collectTransactions(): void
|
||||
{
|
||||
if (null !== $this->start && null !== $this->end) {
|
||||
if ($this->start instanceof Carbon && $this->end instanceof Carbon) {
|
||||
/** @var OperationsRepositoryInterface $opsRepository */
|
||||
$opsRepository = app(OperationsRepositoryInterface::class);
|
||||
$opsRepository->setUser($this->user);
|
||||
|
||||
@@ -43,24 +43,26 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PiggyBankEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private User $user; // @phpstan-ignore-line
|
||||
private UserGroup $userGroup; // @phpstan-ignore-line
|
||||
private Collection $collection;
|
||||
private array $ids = [];
|
||||
private array $currencyIds = [];
|
||||
private array $currencies = [];
|
||||
private array $accountIds = [];
|
||||
private array $accountIds = []; // @phpstan-ignore-line
|
||||
private array $accounts = []; // @phpstan-ignore-line
|
||||
private array $amounts = [];
|
||||
private Collection $collection;
|
||||
private array $currencies = [];
|
||||
private array $currencyIds = [];
|
||||
private array $ids = [];
|
||||
// private array $accountCurrencies = [];
|
||||
private array $notes = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $notes = [];
|
||||
private array $objectGroups = [];
|
||||
private readonly TransactionCurrency $primaryCurrency;
|
||||
private array $amounts = [];
|
||||
private array $accounts = [];
|
||||
private array $objectGroups = [];
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private ?Carbon $date;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->primaryCurrency = Amount::getPrimaryCurrency();
|
||||
$this->date = now(config('app.timezone'));
|
||||
}
|
||||
|
||||
public function enrich(Collection $collection): Collection
|
||||
@@ -69,7 +71,6 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
$this->collectIds();
|
||||
$this->collectObjectGroups();
|
||||
$this->collectNotes();
|
||||
$this->collectCurrentAmounts();
|
||||
|
||||
|
||||
$this->appendCollectedData();
|
||||
@@ -97,69 +98,6 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var PiggyBank $piggy */
|
||||
foreach ($this->collection as $piggy) {
|
||||
$id = (int)$piggy->id;
|
||||
$this->ids[] = $id;
|
||||
$this->currencyIds[$id] = (int)$piggy->transaction_currency_id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
|
||||
// collect currencies.
|
||||
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->get();
|
||||
foreach ($currencies as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
|
||||
// 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;
|
||||
$this->amounts[$id] ??= [];
|
||||
if (!array_key_exists($id, $this->accountIds)) {
|
||||
$this->accountIds[$id] = (int)$item->account_id;
|
||||
}
|
||||
if (!array_key_exists($accountId, $this->amounts[$id])) {
|
||||
$this->amounts[$id][$accountId] = [
|
||||
'current_amount' => '0',
|
||||
'pc_current_amount' => '0',
|
||||
];
|
||||
}
|
||||
$this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], (string) $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.
|
||||
$set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
|
||||
|
||||
/** @var AccountMeta $item */
|
||||
foreach ($set as $item) {
|
||||
$accountId = (int)$item->account_id;
|
||||
$currencyId = (int)$item->data;
|
||||
if (!array_key_exists($currencyId, $this->currencies)) {
|
||||
$this->currencies[$currencyId] = Amount::getTransactionCurrencyById($currencyId);
|
||||
}
|
||||
// $this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
|
||||
}
|
||||
|
||||
// get account info.
|
||||
$set = Account::whereIn('id', array_values($this->accountIds))->get();
|
||||
|
||||
/** @var Account $item */
|
||||
foreach ($set as $item) {
|
||||
$id = (int)$item->id;
|
||||
$this->accounts[$id] = [
|
||||
'id' => $id,
|
||||
'name' => $item->name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (PiggyBank $item) {
|
||||
@@ -193,7 +131,7 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = (string) $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_id'] = (string)$this->objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
|
||||
}
|
||||
@@ -205,9 +143,9 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
'current_amount' => Steam::bcround($row['current_amount'], $currency->decimal_places),
|
||||
'pc_current_amount' => Steam::bcround($row['pc_current_amount'], $this->primaryCurrency->decimal_places),
|
||||
];
|
||||
$meta['current_amount'] = bcadd($meta['current_amount'], $row['current_amount']);
|
||||
$meta['current_amount'] = bcadd($meta['current_amount'], (string) $row['current_amount']);
|
||||
// only add pc_current_amount when the pc_current_amount is set
|
||||
$meta['pc_current_amount'] = null === $row['pc_current_amount'] ? null : bcadd($meta['pc_current_amount'], $row['pc_current_amount']);
|
||||
$meta['pc_current_amount'] = null === $row['pc_current_amount'] ? null : bcadd((string) $meta['pc_current_amount'], (string) $row['pc_current_amount']);
|
||||
}
|
||||
$meta['current_amount'] = Steam::bcround($meta['current_amount'], $currency->decimal_places);
|
||||
// only round this number when pc_current_amount is set.
|
||||
@@ -215,13 +153,13 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
|
||||
// calculate left to save, only when there is a target amount.
|
||||
if (null !== $targetAmount) {
|
||||
$meta['left_to_save'] = bcsub($meta['target_amount'], $meta['current_amount']);
|
||||
$meta['pc_left_to_save'] = null === $meta['pc_target_amount'] ? null : bcsub($meta['pc_target_amount'], $meta['pc_current_amount']);
|
||||
$meta['left_to_save'] = bcsub((string) $meta['target_amount'], (string) $meta['current_amount']);
|
||||
$meta['pc_left_to_save'] = null === $meta['pc_target_amount'] ? null : bcsub((string) $meta['pc_target_amount'], (string) $meta['pc_current_amount']);
|
||||
}
|
||||
|
||||
// get suggested per month.
|
||||
$meta['save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($item->start_date, $item->target_date, $meta['target_amount'], $meta['current_amount']), $currency->decimal_places);
|
||||
$meta['pc_save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($item->start_date, $item->target_date, $meta['pc_target_amount'], $meta['pc_current_amount']), $currency->decimal_places);
|
||||
$meta['save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($this->date, $item->target_date, $meta['target_amount'], $meta['current_amount']), $currency->decimal_places);
|
||||
$meta['pc_save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($this->date, $item->target_date, $meta['pc_target_amount'], $meta['pc_current_amount']), $currency->decimal_places);
|
||||
|
||||
$item->meta = $meta;
|
||||
|
||||
@@ -229,6 +167,74 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
});
|
||||
}
|
||||
|
||||
public function setDate(?Carbon $date): void
|
||||
{
|
||||
$this->date = $date;
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var PiggyBank $piggy */
|
||||
foreach ($this->collection as $piggy) {
|
||||
$id = (int)$piggy->id;
|
||||
$this->ids[] = $id;
|
||||
$this->currencyIds[$id] = (int)$piggy->transaction_currency_id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
|
||||
// collect currencies.
|
||||
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->get();
|
||||
foreach ($currencies as $currency) {
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
|
||||
// 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;
|
||||
$this->amounts[$id] ??= [];
|
||||
if (!array_key_exists($id, $this->accountIds)) {
|
||||
$this->accountIds[$id] = (int)$item->account_id;
|
||||
}
|
||||
if (!array_key_exists($accountId, $this->amounts[$id])) {
|
||||
$this->amounts[$id][$accountId] = [
|
||||
'current_amount' => '0',
|
||||
'pc_current_amount' => '0',
|
||||
];
|
||||
}
|
||||
$this->amounts[$id][$accountId]['current_amount'] = bcadd((string) $this->amounts[$id][$accountId]['current_amount'], (string)$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'], (string)$item->native_current_amount);
|
||||
}
|
||||
}
|
||||
|
||||
// get account currency preference for ALL.
|
||||
$set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
|
||||
|
||||
/** @var AccountMeta $item */
|
||||
foreach ($set as $item) {
|
||||
$accountId = (int)$item->account_id;
|
||||
$currencyId = (int)$item->data;
|
||||
if (!array_key_exists($currencyId, $this->currencies)) {
|
||||
$this->currencies[$currencyId] = Amount::getTransactionCurrencyById($currencyId);
|
||||
}
|
||||
// $this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
|
||||
}
|
||||
|
||||
// get account info.
|
||||
$set = Account::whereIn('id', array_values($this->accountIds))->get();
|
||||
|
||||
/** @var Account $item */
|
||||
foreach ($set as $item) {
|
||||
$id = (int)$item->id;
|
||||
$this->accounts[$id] = [
|
||||
'id' => $id,
|
||||
'name' => $item->name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->ids)
|
||||
@@ -264,8 +270,6 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
}
|
||||
}
|
||||
|
||||
private function collectCurrentAmounts(): void {}
|
||||
|
||||
/**
|
||||
* Returns the suggested amount the user should save per month, or "".
|
||||
*/
|
||||
|
||||
@@ -38,16 +38,16 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PiggyBankEventEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private User $user; // @phpstan-ignore-line
|
||||
private UserGroup $userGroup; // @phpstan-ignore-line
|
||||
private array $accountCurrencies = []; // @phpstan-ignore-line
|
||||
private array $accountIds = []; // @phpstan-ignore-line
|
||||
private Collection $collection;
|
||||
private array $currencies = [];
|
||||
private array $groupIds = [];
|
||||
private array $ids = [];
|
||||
private array $journalIds = [];
|
||||
private array $groupIds = [];
|
||||
private array $accountIds = [];
|
||||
private array $piggyBankIds = [];
|
||||
private array $accountCurrencies = [];
|
||||
private array $currencies = [];
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
// private bool $convertToPrimary = false;
|
||||
// private TransactionCurrency $primaryCurrency;
|
||||
|
||||
@@ -86,6 +86,30 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (PiggyBankEvent $item) {
|
||||
$id = (int)$item->id;
|
||||
$piggyId = (int)$item->piggy_bank_id;
|
||||
$journalId = (int)$item->transaction_journal_id;
|
||||
$currency = null;
|
||||
if (array_key_exists($piggyId, $this->accountIds)) {
|
||||
$accountId = $this->accountIds[$piggyId];
|
||||
if (array_key_exists($accountId, $this->accountCurrencies)) {
|
||||
$currency = $this->accountCurrencies[$accountId];
|
||||
}
|
||||
}
|
||||
$meta = [
|
||||
'transaction_group_id' => array_key_exists($journalId, $this->groupIds) ? (string)$this->groupIds[$journalId] : null,
|
||||
'currency' => $currency,
|
||||
];
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var PiggyBankEvent $event */
|
||||
@@ -125,28 +149,4 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
|
||||
$this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
|
||||
}
|
||||
}
|
||||
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (PiggyBankEvent $item) {
|
||||
$id = (int)$item->id;
|
||||
$piggyId = (int)$item->piggy_bank_id;
|
||||
$journalId = (int)$item->transaction_journal_id;
|
||||
$currency = null;
|
||||
if (array_key_exists($piggyId, $this->accountIds)) {
|
||||
$accountId = $this->accountIds[$piggyId];
|
||||
if (array_key_exists($accountId, $this->accountCurrencies)) {
|
||||
$currency = $this->accountCurrencies[$accountId];
|
||||
}
|
||||
}
|
||||
$meta = [
|
||||
'transaction_group_id' => array_key_exists($journalId, $this->groupIds) ? (string)$this->groupIds[$journalId] : null,
|
||||
'currency' => $currency,
|
||||
];
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user