mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2026-02-08 05:04:19 +00:00
Compare commits
125 Commits
develop-20
...
develop-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e74163a7ec | ||
|
|
c60094d231 | ||
|
|
39d46d469c | ||
|
|
6caea5ffa3 | ||
|
|
4024f76a51 | ||
|
|
de84946371 | ||
|
|
6d4aca54de | ||
|
|
256262b2ba | ||
|
|
fb035ba594 | ||
|
|
20776949a6 | ||
|
|
ad5a8a2934 | ||
|
|
e37ef69491 | ||
|
|
df8a406c58 | ||
|
|
88d3e01065 | ||
|
|
7a1c32f1aa | ||
|
|
54df0d44f7 | ||
|
|
1f7775032b | ||
|
|
8cfe1e8047 | ||
|
|
f0be634829 | ||
|
|
485e1138f8 | ||
|
|
9abf08b3be | ||
|
|
f08943b926 | ||
|
|
2de9926db8 | ||
|
|
b4d01d464d | ||
|
|
229b45c7ad | ||
|
|
8e89c5af62 | ||
|
|
264fec7a6a | ||
|
|
d4b1d097fe | ||
|
|
bbd6acb824 | ||
|
|
4e7d12f06b | ||
|
|
9811583379 | ||
|
|
0063cab690 | ||
|
|
d3add7c92b | ||
|
|
a491e4921f | ||
|
|
171bc03668 | ||
|
|
dd5476bfc7 | ||
|
|
bc0769358d | ||
|
|
ccf33f1db6 | ||
|
|
35f611b3f2 | ||
|
|
e5d394533c | ||
|
|
831d39a41e | ||
|
|
2920a9b9e3 | ||
|
|
5c8204e963 | ||
|
|
d25283f193 | ||
|
|
20986e6426 | ||
|
|
9cd0ebe37e | ||
|
|
9c2b83a971 | ||
|
|
e1d32da409 | ||
|
|
c51df8cd83 | ||
|
|
9f016aed16 | ||
|
|
27df5ea800 | ||
|
|
2d7cdd36f0 | ||
|
|
7888023c1a | ||
|
|
3032118788 | ||
|
|
89d96ddc17 | ||
|
|
97c9937571 | ||
|
|
c46aca0594 | ||
|
|
0b33e1ff09 | ||
|
|
d267d2a0b0 | ||
|
|
f2996dcebe | ||
|
|
ebb6a186cc | ||
|
|
304fae439a | ||
|
|
9ca81cf305 | ||
|
|
cad5fb6d6b | ||
|
|
53efafdbb2 | ||
|
|
7922017288 | ||
|
|
4e910a33dd | ||
|
|
bb031cdeb6 | ||
|
|
60026bbcba | ||
|
|
e96a1850da | ||
|
|
bd2a746b8a | ||
|
|
e4a3cbc9da | ||
|
|
18734b0edd | ||
|
|
f52b3bf5f5 | ||
|
|
3abba71f8d | ||
|
|
95bdc87ed7 | ||
|
|
9a66b4017b | ||
|
|
610e3f3ae5 | ||
|
|
a58e70c08b | ||
|
|
24c96d40c9 | ||
|
|
74ae59910f | ||
|
|
6d49815be9 | ||
|
|
ae680cd41f | ||
|
|
5e9ea1ca10 | ||
|
|
6f558f424d | ||
|
|
4387876203 | ||
|
|
1b1ce3e04e | ||
|
|
ff64675122 | ||
|
|
1c545b7a74 | ||
|
|
a433ddcd7e | ||
|
|
b0e1b6fe51 | ||
|
|
f5523e60b6 | ||
|
|
e300efe640 | ||
|
|
a97f227ddb | ||
|
|
5f8d7049b5 | ||
|
|
7e80f78f2e | ||
|
|
ad922745c4 | ||
|
|
40abe74dc1 | ||
|
|
e5d2c4d163 | ||
|
|
2851053900 | ||
|
|
b2f6ce1277 | ||
|
|
340b0661ba | ||
|
|
be18f11f8c | ||
|
|
2f8ee67b31 | ||
|
|
1ecf55165e | ||
|
|
5aceccde4a | ||
|
|
abfaee5a55 | ||
|
|
fa65cc7ee2 | ||
|
|
d64f1d0c18 | ||
|
|
31206ce56c | ||
|
|
e4e9a09522 | ||
|
|
11303dc6e2 | ||
|
|
993f5cd292 | ||
|
|
cc0854c712 | ||
|
|
5c6aee0037 | ||
|
|
391f8c34cc | ||
|
|
db6ed26d5a | ||
|
|
eece951036 | ||
|
|
3d7a62293b | ||
|
|
2691dbe438 | ||
|
|
fe971ec611 | ||
|
|
9e4c5435f0 | ||
|
|
cdb2b91813 | ||
|
|
f4cf158d21 | ||
|
|
b19f1d0353 |
@@ -26,6 +26,7 @@ $paths = [
|
||||
$current . '/../../config',
|
||||
$current . '/../../routes',
|
||||
$current . '/../../tests',
|
||||
$current . '/../../resources/lang/en_US',
|
||||
];
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
|
||||
36
.ci/php-cs-fixer/composer.lock
generated
36
.ci/php-cs-fixer/composer.lock
generated
@@ -402,16 +402,16 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.93.0",
|
||||
"version": "v3.93.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "50895a07cface1385082e4caa6a6786c4e033468"
|
||||
"reference": "b3546ab487c0762c39f308dc1ec0ea2c461fc21a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/50895a07cface1385082e4caa6a6786c4e033468",
|
||||
"reference": "50895a07cface1385082e4caa6a6786c4e033468",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/b3546ab487c0762c39f308dc1ec0ea2c461fc21a",
|
||||
"reference": "b3546ab487c0762c39f308dc1ec0ea2c461fc21a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -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.93.0"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.93.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -502,7 +502,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-23T17:33:21+00:00"
|
||||
"time": "2026-01-28T23:50:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -1640,16 +1640,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v8.0.4",
|
||||
"version": "v8.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
"reference": "42e48eb02e07d5f3771d194d67da117eb824c8c1"
|
||||
"reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/42e48eb02e07d5f3771d194d67da117eb824c8c1",
|
||||
"reference": "42e48eb02e07d5f3771d194d67da117eb824c8c1",
|
||||
"url": "https://api.github.com/repos/symfony/finder/zipball/8bd576e97c67d45941365bf824e18dc8538e6eb0",
|
||||
"reference": "8bd576e97c67d45941365bf824e18dc8538e6eb0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1684,7 +1684,7 @@
|
||||
"description": "Finds files and directories via an intuitive fluent interface",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/finder/tree/v8.0.4"
|
||||
"source": "https://github.com/symfony/finder/tree/v8.0.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1704,7 +1704,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-12T12:37:40+00:00"
|
||||
"time": "2026-01-26T15:08:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
@@ -2358,16 +2358,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v8.0.4",
|
||||
"version": "v8.0.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "10df72602d88c0a3fa685b822976a052611dd607"
|
||||
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/10df72602d88c0a3fa685b822976a052611dd607",
|
||||
"reference": "10df72602d88c0a3fa685b822976a052611dd607",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
|
||||
"reference": "b5f3aa6762e33fd95efbaa2ec4f4bc9fdd16d674",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2399,7 +2399,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v8.0.4"
|
||||
"source": "https://github.com/symfony/process/tree/v8.0.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2419,7 +2419,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-23T11:07:10+00:00"
|
||||
"time": "2026-01-26T15:08:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
|
||||
@@ -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.
|
||||
|
||||
## 2026
|
||||
- Nick Huang
|
||||
- mateuszkulapl
|
||||
- Gianluca Martino
|
||||
- embedded
|
||||
|
||||
@@ -31,7 +31,6 @@ use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Debug\Timer;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
@@ -80,7 +79,7 @@ class AccountController extends Controller
|
||||
*/
|
||||
public function accounts(AutocompleteApiRequest $request): JsonResponse
|
||||
{
|
||||
Log::debug('Before All.');
|
||||
// Log::debug('Before All.');
|
||||
['types' => $types, 'query' => $query, 'date' => $date, 'limit' => $limit] = $request->attributes->all();
|
||||
|
||||
$date ??= today(config('app.timezone'));
|
||||
@@ -89,8 +88,6 @@ class AccountController extends Controller
|
||||
$date->endOfDay();
|
||||
|
||||
$return = [];
|
||||
$timer = Timer::getInstance();
|
||||
$timer->start(sprintf('AC accounts "%s"', $query));
|
||||
$result = $this->repository->searchAccount((string) $query, $types, $limit);
|
||||
$allBalances = Steam::accountsBalancesOptimized($result, $date, $this->primaryCurrency, $this->convertToPrimary);
|
||||
|
||||
@@ -136,7 +133,6 @@ class AccountController extends Controller
|
||||
|
||||
return $posA - $posB;
|
||||
});
|
||||
$timer->stop(sprintf('AC accounts "%s"', $query));
|
||||
|
||||
return response()->api($return);
|
||||
}
|
||||
|
||||
@@ -104,6 +104,48 @@ class BudgetController extends Controller
|
||||
return response()->json($this->clean($data));
|
||||
}
|
||||
|
||||
private function filterLimit(int $currencyId, Collection $limits): ?BudgetLimit
|
||||
{
|
||||
$amount = '0';
|
||||
$limit = null;
|
||||
$converter = new ExchangeRateConverter();
|
||||
|
||||
/** @var BudgetLimit $current */
|
||||
foreach ($limits as $current) {
|
||||
if ($this->convertToPrimary) {
|
||||
if ($current->transaction_currency_id === $this->primaryCurrency->id) {
|
||||
// simply add it.
|
||||
$amount = bcadd($amount, (string) $current->amount);
|
||||
Log::debug(sprintf('Set amount in limit to %s', $amount));
|
||||
}
|
||||
if ($current->transaction_currency_id !== $this->primaryCurrency->id) {
|
||||
// convert and then add it.
|
||||
$converted = $converter->convert($current->transactionCurrency, $this->primaryCurrency, $current->start_date, $current->amount);
|
||||
$amount = bcadd($amount, $converted);
|
||||
Log::debug(sprintf(
|
||||
'Budgeted in limit #%d: %s %s, converted to %s %s',
|
||||
$current->id,
|
||||
$current->transactionCurrency->code,
|
||||
$current->amount,
|
||||
$this->primaryCurrency->code,
|
||||
$converted
|
||||
));
|
||||
Log::debug(sprintf('Set amount in limit to %s', $amount));
|
||||
}
|
||||
}
|
||||
if ($current->transaction_currency_id === $currencyId) {
|
||||
$limit = $current;
|
||||
}
|
||||
}
|
||||
if (null !== $limit && $this->convertToPrimary) {
|
||||
// convert and add all amounts.
|
||||
$limit->amount = Steam::positive($amount);
|
||||
Log::debug(sprintf('Final amount in limit with converted amount %s', $limit->amount));
|
||||
}
|
||||
|
||||
return $limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -250,46 +292,4 @@ class BudgetController extends Controller
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function filterLimit(int $currencyId, Collection $limits): ?BudgetLimit
|
||||
{
|
||||
$amount = '0';
|
||||
$limit = null;
|
||||
$converter = new ExchangeRateConverter();
|
||||
|
||||
/** @var BudgetLimit $current */
|
||||
foreach ($limits as $current) {
|
||||
if ($this->convertToPrimary) {
|
||||
if ($current->transaction_currency_id === $this->primaryCurrency->id) {
|
||||
// simply add it.
|
||||
$amount = bcadd($amount, (string) $current->amount);
|
||||
Log::debug(sprintf('Set amount in limit to %s', $amount));
|
||||
}
|
||||
if ($current->transaction_currency_id !== $this->primaryCurrency->id) {
|
||||
// convert and then add it.
|
||||
$converted = $converter->convert($current->transactionCurrency, $this->primaryCurrency, $current->start_date, $current->amount);
|
||||
$amount = bcadd($amount, $converted);
|
||||
Log::debug(sprintf(
|
||||
'Budgeted in limit #%d: %s %s, converted to %s %s',
|
||||
$current->id,
|
||||
$current->transactionCurrency->code,
|
||||
$current->amount,
|
||||
$this->primaryCurrency->code,
|
||||
$converted
|
||||
));
|
||||
Log::debug(sprintf('Set amount in limit to %s', $amount));
|
||||
}
|
||||
}
|
||||
if ($current->transaction_currency_id === $currencyId) {
|
||||
$limit = $current;
|
||||
}
|
||||
}
|
||||
if (null !== $limit && $this->convertToPrimary) {
|
||||
// convert and add all amounts.
|
||||
$limit->amount = Steam::positive($amount);
|
||||
Log::debug(sprintf('Final amount in limit with converted amount %s', $limit->amount));
|
||||
}
|
||||
|
||||
return $limit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,77 @@ abstract class Controller extends BaseController
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to help build URL's.
|
||||
*/
|
||||
final protected function buildParams(): string
|
||||
{
|
||||
$return = '?';
|
||||
$params = [];
|
||||
foreach ($this->parameters as $key => $value) {
|
||||
if ('page' === $key) {
|
||||
continue;
|
||||
}
|
||||
if ($value instanceof Carbon) {
|
||||
$params[$key] = $value->format('Y-m-d');
|
||||
|
||||
continue;
|
||||
}
|
||||
$params[$key] = $value;
|
||||
}
|
||||
|
||||
return $return.http_build_query($params);
|
||||
}
|
||||
|
||||
final protected function getManager(): Manager
|
||||
{
|
||||
// create some objects:
|
||||
$manager = new Manager();
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v1';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
return $manager;
|
||||
}
|
||||
|
||||
final protected function jsonApiList(string $key, LengthAwarePaginator $paginator, AbstractTransformer $transformer): array
|
||||
{
|
||||
$manager = new Manager();
|
||||
$baseUrl = sprintf('%s/api/v1/', request()->getSchemeAndHttpHost());
|
||||
|
||||
// TODO add stuff to path?
|
||||
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$objects = $paginator->getCollection();
|
||||
|
||||
// the transformer, at this point, needs to collect information that ALL items in the collection
|
||||
// require, like meta-data and stuff like that, and save it for later.
|
||||
// $objects = $transformer->collectMetaData($objects);
|
||||
$paginator->setCollection($objects);
|
||||
|
||||
$resource = new FractalCollection($objects, $transformer, $key);
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return $manager->createData($resource)->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON API object and returns it.
|
||||
*
|
||||
* @param array<int, mixed>|Model $object
|
||||
*/
|
||||
final protected function jsonApiObject(string $key, array|Model $object, AbstractTransformer $transformer): array
|
||||
{
|
||||
// create some objects:
|
||||
$manager = new Manager();
|
||||
$baseUrl = sprintf('%s/api/v1', request()->getSchemeAndHttpHost());
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$resource = new Item($object, $transformer, $key);
|
||||
|
||||
return $manager->createData($resource)->toArray();
|
||||
}
|
||||
|
||||
#[Deprecated(message: <<<'TXT'
|
||||
use Request classes
|
||||
Method to grab all parameters from the URL
|
||||
@@ -170,75 +241,4 @@ abstract class Controller extends BaseController
|
||||
|
||||
// return $this->getSortParameters($bag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to help build URL's.
|
||||
*/
|
||||
final protected function buildParams(): string
|
||||
{
|
||||
$return = '?';
|
||||
$params = [];
|
||||
foreach ($this->parameters as $key => $value) {
|
||||
if ('page' === $key) {
|
||||
continue;
|
||||
}
|
||||
if ($value instanceof Carbon) {
|
||||
$params[$key] = $value->format('Y-m-d');
|
||||
|
||||
continue;
|
||||
}
|
||||
$params[$key] = $value;
|
||||
}
|
||||
|
||||
return $return.http_build_query($params);
|
||||
}
|
||||
|
||||
final protected function getManager(): Manager
|
||||
{
|
||||
// create some objects:
|
||||
$manager = new Manager();
|
||||
$baseUrl = request()->getSchemeAndHttpHost().'/api/v1';
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
return $manager;
|
||||
}
|
||||
|
||||
final protected function jsonApiList(string $key, LengthAwarePaginator $paginator, AbstractTransformer $transformer): array
|
||||
{
|
||||
$manager = new Manager();
|
||||
$baseUrl = sprintf('%s/api/v1/', request()->getSchemeAndHttpHost());
|
||||
|
||||
// TODO add stuff to path?
|
||||
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$objects = $paginator->getCollection();
|
||||
|
||||
// the transformer, at this point, needs to collect information that ALL items in the collection
|
||||
// require, like meta-data and stuff like that, and save it for later.
|
||||
// $objects = $transformer->collectMetaData($objects);
|
||||
$paginator->setCollection($objects);
|
||||
|
||||
$resource = new FractalCollection($objects, $transformer, $key);
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return $manager->createData($resource)->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON API object and returns it.
|
||||
*
|
||||
* @param array<int, mixed>|Model $object
|
||||
*/
|
||||
final protected function jsonApiObject(string $key, array|Model $object, AbstractTransformer $transformer): array
|
||||
{
|
||||
// create some objects:
|
||||
$manager = new Manager();
|
||||
$baseUrl = sprintf('%s/api/v1', request()->getSchemeAndHttpHost());
|
||||
$manager->setSerializer(new JsonApiSerializer($baseUrl));
|
||||
|
||||
$resource = new Item($object, $transformer, $key);
|
||||
|
||||
return $manager->createData($resource)->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,70 +137,6 @@ class DestroyController extends Controller
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
||||
private function destroyBudgets(): void
|
||||
{
|
||||
/** @var AvailableBudgetRepositoryInterface $abRepository */
|
||||
$abRepository = app(AvailableBudgetRepositoryInterface::class);
|
||||
$abRepository->destroyAll();
|
||||
|
||||
/** @var BudgetLimitRepositoryInterface $blRepository */
|
||||
$blRepository = app(BudgetLimitRepositoryInterface::class);
|
||||
$blRepository->destroyAll();
|
||||
|
||||
/** @var BudgetRepositoryInterface $budgetRepository */
|
||||
$budgetRepository = app(BudgetRepositoryInterface::class);
|
||||
$budgetRepository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyBills(): void
|
||||
{
|
||||
/** @var BillRepositoryInterface $repository */
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
$repository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyPiggyBanks(): void
|
||||
{
|
||||
/** @var PiggyBankRepositoryInterface $repository */
|
||||
$repository = app(PiggyBankRepositoryInterface::class);
|
||||
$repository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyRules(): void
|
||||
{
|
||||
/** @var RuleGroupRepositoryInterface $repository */
|
||||
$repository = app(RuleGroupRepositoryInterface::class);
|
||||
$repository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyRecurringTransactions(): void
|
||||
{
|
||||
/** @var RecurringRepositoryInterface $repository */
|
||||
$repository = app(RecurringRepositoryInterface::class);
|
||||
$repository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyCategories(): void
|
||||
{
|
||||
/** @var CategoryRepositoryInterface $categoryRepos */
|
||||
$categoryRepos = app(CategoryRepositoryInterface::class);
|
||||
$categoryRepos->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyTags(): void
|
||||
{
|
||||
/** @var TagRepositoryInterface $tagRepository */
|
||||
$tagRepository = app(TagRepositoryInterface::class);
|
||||
$tagRepository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyObjectGroups(): void
|
||||
{
|
||||
/** @var ObjectGroupRepositoryInterface $repository */
|
||||
$repository = app(ObjectGroupRepositoryInterface::class);
|
||||
$repository->deleteAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $types
|
||||
*/
|
||||
@@ -229,6 +165,70 @@ class DestroyController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
private function destroyBills(): void
|
||||
{
|
||||
/** @var BillRepositoryInterface $repository */
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
$repository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyBudgets(): void
|
||||
{
|
||||
/** @var AvailableBudgetRepositoryInterface $abRepository */
|
||||
$abRepository = app(AvailableBudgetRepositoryInterface::class);
|
||||
$abRepository->destroyAll();
|
||||
|
||||
/** @var BudgetLimitRepositoryInterface $blRepository */
|
||||
$blRepository = app(BudgetLimitRepositoryInterface::class);
|
||||
$blRepository->destroyAll();
|
||||
|
||||
/** @var BudgetRepositoryInterface $budgetRepository */
|
||||
$budgetRepository = app(BudgetRepositoryInterface::class);
|
||||
$budgetRepository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyCategories(): void
|
||||
{
|
||||
/** @var CategoryRepositoryInterface $categoryRepos */
|
||||
$categoryRepos = app(CategoryRepositoryInterface::class);
|
||||
$categoryRepos->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyObjectGroups(): void
|
||||
{
|
||||
/** @var ObjectGroupRepositoryInterface $repository */
|
||||
$repository = app(ObjectGroupRepositoryInterface::class);
|
||||
$repository->deleteAll();
|
||||
}
|
||||
|
||||
private function destroyPiggyBanks(): void
|
||||
{
|
||||
/** @var PiggyBankRepositoryInterface $repository */
|
||||
$repository = app(PiggyBankRepositoryInterface::class);
|
||||
$repository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyRecurringTransactions(): void
|
||||
{
|
||||
/** @var RecurringRepositoryInterface $repository */
|
||||
$repository = app(RecurringRepositoryInterface::class);
|
||||
$repository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyRules(): void
|
||||
{
|
||||
/** @var RuleGroupRepositoryInterface $repository */
|
||||
$repository = app(RuleGroupRepositoryInterface::class);
|
||||
$repository->destroyAll();
|
||||
}
|
||||
|
||||
private function destroyTags(): void
|
||||
{
|
||||
/** @var TagRepositoryInterface $tagRepository */
|
||||
$tagRepository = app(TagRepositoryInterface::class);
|
||||
$tagRepository->destroyAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $types
|
||||
*/
|
||||
|
||||
@@ -72,33 +72,6 @@ class ExportController extends Controller
|
||||
return $this->returnExport('accounts');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
* @throws DatetimeException
|
||||
*/
|
||||
private function returnExport(string $key): LaravelResponse
|
||||
{
|
||||
$date = date('Y-m-d-H-i-s');
|
||||
$fileName = sprintf('%s-export-%s.csv', $date, $key);
|
||||
$data = $this->exporter->export();
|
||||
|
||||
/** @var LaravelResponse $response */
|
||||
$response = response($data[$key]);
|
||||
$response
|
||||
->header('Content-Description', 'File Transfer')
|
||||
->header('Content-Type', 'application/octet-stream')
|
||||
->header('Content-Disposition', 'attachment; filename='.$fileName)
|
||||
->header('Content-Transfer-Encoding', 'binary')
|
||||
->header('Connection', 'Keep-Alive')
|
||||
->header('Expires', '0')
|
||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||
->header('Pragma', 'public')
|
||||
->header('Content-Length', (string) strlen((string) $data[$key]))
|
||||
;
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DatetimeException
|
||||
* @throws FireflyException
|
||||
@@ -204,4 +177,31 @@ class ExportController extends Controller
|
||||
|
||||
return $this->returnExport('transactions');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
* @throws DatetimeException
|
||||
*/
|
||||
private function returnExport(string $key): LaravelResponse
|
||||
{
|
||||
$date = date('Y-m-d-H-i-s');
|
||||
$fileName = sprintf('%s-export-%s.csv', $date, $key);
|
||||
$data = $this->exporter->export();
|
||||
|
||||
/** @var LaravelResponse $response */
|
||||
$response = response($data[$key]);
|
||||
$response
|
||||
->header('Content-Description', 'File Transfer')
|
||||
->header('Content-Type', 'application/octet-stream')
|
||||
->header('Content-Disposition', 'attachment; filename='.$fileName)
|
||||
->header('Content-Transfer-Encoding', 'binary')
|
||||
->header('Connection', 'Keep-Alive')
|
||||
->header('Expires', '0')
|
||||
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
|
||||
->header('Pragma', 'public')
|
||||
->header('Content-Length', (string) strlen((string) $data[$key]))
|
||||
;
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,13 +63,6 @@ class DestroyController extends Controller
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
||||
public function destroySingleById(CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
{
|
||||
$this->repository->deleteRate($exchangeRate);
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
||||
public function destroySingleByDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
|
||||
{
|
||||
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
@@ -82,4 +75,11 @@ class DestroyController extends Controller
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
||||
public function destroySingleById(CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
{
|
||||
$this->repository->deleteRate($exchangeRate);
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,14 +75,6 @@ class ShowController extends Controller
|
||||
return response()->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function showSingleById(CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
{
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function showSingleByDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
|
||||
{
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
@@ -95,4 +87,12 @@ class ShowController extends Controller
|
||||
|
||||
return response()->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function showSingleById(CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
{
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,30 @@ class StoreController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
public function store(StoreRequest $request): JsonResponse
|
||||
{
|
||||
$date = $request->getDate();
|
||||
$rate = $request->getRate();
|
||||
$from = $request->getFromCurrency();
|
||||
$to = $request->getToCurrency();
|
||||
|
||||
// already has rate?
|
||||
$object = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if ($object instanceof CurrencyExchangeRate) {
|
||||
// just update it, no matter.
|
||||
$rate = $this->repository->updateExchangeRate($object, $rate, $date);
|
||||
}
|
||||
if (!$object instanceof CurrencyExchangeRate) {
|
||||
// store new
|
||||
$rate = $this->repository->storeExchangeRate($from, $to, $rate, $date);
|
||||
}
|
||||
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()->api($this->jsonApiObject(self::RESOURCE_KEY, $rate, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function storeByCurrencies(StoreByCurrenciesRequest $request, TransactionCurrency $from, TransactionCurrency $to): JsonResponse
|
||||
{
|
||||
$data = $request->getAll();
|
||||
@@ -114,28 +138,4 @@ class StoreController extends Controller
|
||||
|
||||
return response()->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function store(StoreRequest $request): JsonResponse
|
||||
{
|
||||
$date = $request->getDate();
|
||||
$rate = $request->getRate();
|
||||
$from = $request->getFromCurrency();
|
||||
$to = $request->getToCurrency();
|
||||
|
||||
// already has rate?
|
||||
$object = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
if ($object instanceof CurrencyExchangeRate) {
|
||||
// just update it, no matter.
|
||||
$rate = $this->repository->updateExchangeRate($object, $rate, $date);
|
||||
}
|
||||
if (!$object instanceof CurrencyExchangeRate) {
|
||||
// store new
|
||||
$rate = $this->repository->storeExchangeRate($from, $to, $rate, $date);
|
||||
}
|
||||
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()->api($this->jsonApiObject(self::RESOURCE_KEY, $rate, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,17 +57,6 @@ class UpdateController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
public function updateById(UpdateRequest $request, CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
{
|
||||
$date = $request->getDate();
|
||||
$rate = $request->getRate();
|
||||
$exchangeRate = $this->repository->updateExchangeRate($exchangeRate, $rate, $date);
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function updateByDate(UpdateRequest $request, TransactionCurrency $from, TransactionCurrency $to, Carbon $date): JsonResponse
|
||||
{
|
||||
$exchangeRate = $this->repository->getSpecificRateOnDate($from, $to, $date);
|
||||
@@ -82,4 +71,15 @@ class UpdateController extends Controller
|
||||
|
||||
return response()->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function updateById(UpdateRequest $request, CurrencyExchangeRate $exchangeRate): JsonResponse
|
||||
{
|
||||
$date = $request->getDate();
|
||||
$rate = $request->getRate();
|
||||
$exchangeRate = $this->repository->updateExchangeRate($exchangeRate, $rate, $date);
|
||||
$transformer = new ExchangeRateTransformer();
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
return response()->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,30 +63,28 @@ class TriggerController extends Controller
|
||||
{
|
||||
// find recurrence occurrence for this date and trigger it.
|
||||
// grab the date from the last time the recurrence fired:
|
||||
$backupDate = $recurrence->latest_date;
|
||||
$date = $request->getDate();
|
||||
$backupDate = $recurrence->latest_date;
|
||||
$date = $request->getDate();
|
||||
|
||||
// fire the recurring cron job on the given date, then post-date the created transaction.
|
||||
Log::info(sprintf('Trigger: will now fire recurring cron job task for date "%s".', $date->format('Y-m-d H:i:s')));
|
||||
|
||||
/** @var CreateRecurringTransactions $job */
|
||||
$job = app(CreateRecurringTransactions::class);
|
||||
$job = app(CreateRecurringTransactions::class);
|
||||
$job->setRecurrences(new Collection()->push($recurrence));
|
||||
$job->setDate($date);
|
||||
$job->setForce(false);
|
||||
$job->handle();
|
||||
Log::debug('Done with recurrence.');
|
||||
|
||||
$groups = $job->getGroups();
|
||||
$groups = $job->getGroups();
|
||||
$this->repository->markGroupsAsNow($groups);
|
||||
$recurrence->latest_date = $backupDate;
|
||||
$recurrence->latest_date_tz = $backupDate?->format('e');
|
||||
$recurrence->save();
|
||||
$recurrence = $this->repository->setLatestDate($recurrence, $backupDate);
|
||||
Preferences::mark();
|
||||
|
||||
// enrich groups and return them:
|
||||
|
||||
$paginator = new LengthAwarePaginator(new Collection(), 0, 1);
|
||||
$paginator = new LengthAwarePaginator(new Collection(), 0, 1);
|
||||
if ($groups->count() > 0) {
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
@@ -98,20 +96,20 @@ class TriggerController extends Controller
|
||||
$paginator = $collector->getPaginatedGroups();
|
||||
}
|
||||
|
||||
$manager = $this->getManager();
|
||||
$manager = $this->getManager();
|
||||
$paginator->setPath(route('api.v1.recurrences.trigger', [$recurrence->id]).$this->buildParams());
|
||||
|
||||
// enrich
|
||||
$admin = auth()->user();
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$admin = auth()->user();
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$transactions = $enrichment->enrich($paginator->getCollection());
|
||||
$transactions = $enrichment->enrich($paginator->getCollection());
|
||||
|
||||
/** @var TransactionGroupTransformer $transformer */
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new FractalCollection($transactions, $transformer, 'transactions');
|
||||
$resource = new FractalCollection($transactions, $transformer, 'transactions');
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
|
||||
@@ -25,9 +25,6 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Controllers\Models\Transaction;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Events\UpdatedAccount;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
@@ -74,31 +71,9 @@ class DestroyController extends Controller
|
||||
public function destroy(TransactionGroup $transactionGroup): JsonResponse
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
// grab asset account(s) from group:
|
||||
$accounts = [];
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($transactionGroup->transactionJournals as $journal) {
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journal->transactions as $transaction) {
|
||||
$type = $transaction->account->accountType->type;
|
||||
// if is valid liability, trigger event!
|
||||
if (in_array($type, config('firefly.valid_liabilities'), true)) {
|
||||
$accounts[] = $transaction->account;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->groupRepository->destroy($transactionGroup);
|
||||
|
||||
Preferences::mark();
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Now going to trigger updated account event for account #%d', $account->id));
|
||||
event(new UpdatedAccount($account));
|
||||
}
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
||||
|
||||
@@ -99,17 +99,6 @@ class ShowController extends Controller
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/transactions/getTransactionByJournal
|
||||
*
|
||||
* Show a single transaction, by transaction journal.
|
||||
*/
|
||||
public function showJournal(TransactionJournal $transactionJournal): JsonResponse
|
||||
{
|
||||
return $this->show($transactionJournal->transactionGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/transactions/getTransaction
|
||||
@@ -151,4 +140,15 @@ class ShowController extends Controller
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/transactions/getTransactionByJournal
|
||||
*
|
||||
* Show a single transaction, by transaction journal.
|
||||
*/
|
||||
public function showJournal(TransactionJournal $transactionJournal): JsonResponse
|
||||
{
|
||||
return $this->show($transactionJournal->transactionGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,11 @@ namespace FireflyIII\Api\V1\Controllers\Models\Transaction;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Transaction\StoreRequest;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
|
||||
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
|
||||
use FireflyIII\Exceptions\DuplicateTransactionException;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
|
||||
use FireflyIII\Rules\IsDuplicateTransaction;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
@@ -88,9 +85,9 @@ class StoreController extends Controller
|
||||
public function store(StoreRequest $request): JsonResponse
|
||||
{
|
||||
Log::debug('Now in API StoreController::store()');
|
||||
$data = $request->getAll();
|
||||
$data['user'] = auth()->user();
|
||||
$data['user_group'] = $this->userGroup;
|
||||
$data = $request->getAll();
|
||||
$data['user'] = auth()->user();
|
||||
$data['user_group'] = $this->userGroup;
|
||||
|
||||
Log::channel('audit')->info('Store new transaction over API.', $data);
|
||||
|
||||
@@ -109,22 +106,15 @@ class StoreController extends Controller
|
||||
|
||||
throw new ValidationException($validator);
|
||||
}
|
||||
Preferences::mark();
|
||||
$flags = new TransactionGroupEventFlags();
|
||||
$flags->applyRules = $data['apply_rules'] ?? true;
|
||||
$flags->fireWebhooks = $data['fire_webhooks'] ?? true;
|
||||
$flags->batchSubmission = $data['batch_submission'] ?? false;
|
||||
Log::debug('CreatedSingleTransactionGroup');
|
||||
event(new CreatedSingleTransactionGroup($transactionGroup, $flags));
|
||||
|
||||
$manager = $this->getManager();
|
||||
$manager = $this->getManager();
|
||||
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$admin = auth()->user();
|
||||
|
||||
// use new group collector:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector
|
||||
->setUser($admin)
|
||||
->setUserGroup($this->userGroup)
|
||||
@@ -134,20 +124,20 @@ class StoreController extends Controller
|
||||
->withAPIInformation()
|
||||
;
|
||||
|
||||
$selectedGroup = $collector->getGroups()->first();
|
||||
$selectedGroup = $collector->getGroups()->first();
|
||||
if (null === $selectedGroup) {
|
||||
throw HttpException::fromStatusCode(410, '200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
|
||||
}
|
||||
|
||||
// enrich
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
|
||||
/** @var TransactionGroupTransformer $transformer */
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
$resource = new Item($selectedGroup, $transformer, 'transactions');
|
||||
$resource = new Item($selectedGroup, $transformer, 'transactions');
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@ namespace FireflyIII\Api\V1\Controllers\Models\Transaction;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Transaction\UpdateRequest;
|
||||
use FireflyIII\Events\UpdatedTransactionGroup;
|
||||
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
|
||||
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventObjects;
|
||||
use FireflyIII\Events\Model\TransactionGroup\UpdatedSingleTransactionGroup;
|
||||
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
|
||||
@@ -72,27 +75,32 @@ class UpdateController extends Controller
|
||||
public function update(UpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse
|
||||
{
|
||||
Log::debug('Now in update routine for transaction group');
|
||||
$data = $request->getAll();
|
||||
$oldHash = $this->groupRepository->getCompareHash($transactionGroup);
|
||||
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
|
||||
$newHash = $this->groupRepository->getCompareHash($transactionGroup);
|
||||
$manager = $this->getManager();
|
||||
$data = $request->getAll();
|
||||
$oldHash = $this->groupRepository->getCompareHash($transactionGroup);
|
||||
$objects = TransactionGroupEventObjects::collectFromTransactionGroup($transactionGroup);
|
||||
$transactionGroup = $this->groupRepository->update($transactionGroup, $data);
|
||||
$objects->appendFromTransactionGroup($transactionGroup);
|
||||
$newHash = $this->groupRepository->getCompareHash($transactionGroup);
|
||||
$manager = $this->getManager();
|
||||
|
||||
Preferences::mark();
|
||||
$applyRules = $data['apply_rules'] ?? true;
|
||||
$fireWebhooks = $data['fire_webhooks'] ?? true;
|
||||
$runRecalculations = $oldHash !== $newHash;
|
||||
$applyRules = $data['apply_rules'] ?? true;
|
||||
$fireWebhooks = $data['fire_webhooks'] ?? true;
|
||||
$runRecalculations = $oldHash !== $newHash;
|
||||
|
||||
// FIXME responds to a single event.
|
||||
// flags in array?
|
||||
event(new UpdatedTransactionGroup($transactionGroup, $applyRules, $fireWebhooks, $runRecalculations));
|
||||
$flags = new TransactionGroupEventFlags();
|
||||
$flags->applyRules = $applyRules;
|
||||
$flags->fireWebhooks = $fireWebhooks;
|
||||
$flags->recalculateCredit = $runRecalculations;
|
||||
event(new UpdatedSingleTransactionGroup($flags, $objects));
|
||||
event(new WebhookMessagesRequestSending());
|
||||
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$admin = auth()->user();
|
||||
|
||||
// use new group collector:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector
|
||||
->setUser($admin)
|
||||
// filter on transaction group.
|
||||
@@ -101,20 +109,20 @@ class UpdateController extends Controller
|
||||
->withAPIInformation()
|
||||
;
|
||||
|
||||
$selectedGroup = $collector->getGroups()->first();
|
||||
$selectedGroup = $collector->getGroups()->first();
|
||||
if (null === $selectedGroup) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
// enrich
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
|
||||
/** @var TransactionGroupTransformer $transformer */
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
$resource = new Item($selectedGroup, $transformer, 'transactions');
|
||||
$resource = new Item($selectedGroup, $transformer, 'transactions');
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
@@ -97,27 +97,6 @@ class UpdateController extends Controller
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function makePrimary(TransactionCurrency $currency): JsonResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$this->repository->enable($currency);
|
||||
$this->repository->makePrimary($currency);
|
||||
|
||||
Preferences::mark();
|
||||
|
||||
$manager = $this->getManager();
|
||||
$currency->refreshForUser($user);
|
||||
|
||||
/** @var CurrencyTransformer $transformer */
|
||||
$transformer = app(CurrencyTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new Item($currency, $transformer, 'currencies');
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/currencies/enableCurrency
|
||||
@@ -143,6 +122,27 @@ class UpdateController extends Controller
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public function makePrimary(TransactionCurrency $currency): JsonResponse
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$this->repository->enable($currency);
|
||||
$this->repository->makePrimary($currency);
|
||||
|
||||
Preferences::mark();
|
||||
|
||||
$manager = $this->getManager();
|
||||
$currency->refreshForUser($user);
|
||||
|
||||
/** @var CurrencyTransformer $transformer */
|
||||
$transformer = app(CurrencyTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new Item($currency, $transformer, 'currencies');
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/currencies/updateCurrency
|
||||
|
||||
@@ -112,6 +112,19 @@ class BasicController extends Controller
|
||||
return response()->json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date is outside session range.
|
||||
*/
|
||||
protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool
|
||||
{ // Validate a preference
|
||||
if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// start and end in the past? use $end
|
||||
return $start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date);
|
||||
}
|
||||
|
||||
private function getBalanceInformation(Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug('getBalanceInformation');
|
||||
@@ -312,145 +325,6 @@ class BasicController extends Controller
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getSubscriptionInformation(Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug(sprintf('Now in getBillInformation("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d-')));
|
||||
/*
|
||||
* Since both this method and the chart use the exact same data, we can suffice
|
||||
* with calling the one method in the bill repository that will get this amount.
|
||||
*/
|
||||
$paidAmount = $this->billRepository->sumPaidInRange($start, $end);
|
||||
$unpaidAmount = $this->billRepository->sumUnpaidInRange($start, $end);
|
||||
$currencies = [$this->primaryCurrency->id => $this->primaryCurrency];
|
||||
|
||||
if ($this->convertToPrimary) {
|
||||
$converter = new ExchangeRateConverter();
|
||||
$newPaidAmount = [[
|
||||
'id' => $this->primaryCurrency->id,
|
||||
'name' => $this->primaryCurrency->name,
|
||||
'symbol' => $this->primaryCurrency->symbol,
|
||||
'code' => $this->primaryCurrency->code,
|
||||
'decimal_places' => $this->primaryCurrency->decimal_places,
|
||||
'sum' => '0',
|
||||
]];
|
||||
|
||||
$newUnpaidAmount = [[
|
||||
'id' => $this->primaryCurrency->id,
|
||||
'name' => $this->primaryCurrency->name,
|
||||
'symbol' => $this->primaryCurrency->symbol,
|
||||
'code' => $this->primaryCurrency->code,
|
||||
'decimal_places' => $this->primaryCurrency->decimal_places,
|
||||
'sum' => '0',
|
||||
]];
|
||||
foreach ([$paidAmount, $unpaidAmount] as $index => $array) {
|
||||
foreach ($array as $item) {
|
||||
$currencyId = (int) $item['id'];
|
||||
if (0 === $index) {
|
||||
// paid amount
|
||||
if ($currencyId === $this->primaryCurrency->id) {
|
||||
$newPaidAmount[0]['sum'] = bcadd($newPaidAmount[0]['sum'], (string) $item['sum']);
|
||||
|
||||
continue;
|
||||
}
|
||||
$currencies[$currencyId] ??= $this->currencyRepos->find($currencyId);
|
||||
$convertedAmount = $converter->convert($currencies[$currencyId], $this->primaryCurrency, $start, $item['sum']);
|
||||
$newPaidAmount[0]['sum'] = bcadd($newPaidAmount[0]['sum'], $convertedAmount);
|
||||
|
||||
continue;
|
||||
}
|
||||
// unpaid amount
|
||||
if ($currencyId === $this->primaryCurrency->id) {
|
||||
$newUnpaidAmount[0]['sum'] = bcadd($newUnpaidAmount[0]['sum'], (string) $item['sum']);
|
||||
|
||||
continue;
|
||||
}
|
||||
$currencies[$currencyId] ??= $this->currencyRepos->find($currencyId);
|
||||
$convertedAmount = $converter->convert($currencies[$currencyId], $this->primaryCurrency, $start, $item['sum']);
|
||||
$newUnpaidAmount[0]['sum'] = bcadd($newUnpaidAmount[0]['sum'], $convertedAmount);
|
||||
}
|
||||
}
|
||||
$paidAmount = $newPaidAmount;
|
||||
$unpaidAmount = $newUnpaidAmount;
|
||||
}
|
||||
|
||||
// var_dump($paidAmount);
|
||||
// var_dump($unpaidAmount);
|
||||
// exit;
|
||||
|
||||
$return = [];
|
||||
|
||||
/**
|
||||
* @var array $info
|
||||
*/
|
||||
foreach ($paidAmount as $info) {
|
||||
$amount = bcmul((string) $info['sum'], '-1');
|
||||
$return[] = [
|
||||
'key' => sprintf('bills-paid-in-%s', $info['code']),
|
||||
'title' => trans('firefly.box_bill_paid_in_currency', ['currency' => $info['symbol']]),
|
||||
'monetary_value' => $amount,
|
||||
'currency_id' => (string) $info['id'],
|
||||
'currency_code' => $info['code'],
|
||||
'currency_symbol' => $info['symbol'],
|
||||
'currency_decimal_places' => $info['decimal_places'],
|
||||
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'local_icon' => 'check',
|
||||
'sub_title' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array $info
|
||||
*/
|
||||
foreach ($unpaidAmount as $info) {
|
||||
$amount = bcmul((string) $info['sum'], '-1');
|
||||
$return[] = [
|
||||
'key' => sprintf('bills-unpaid-in-%s', $info['code']),
|
||||
'title' => trans('firefly.box_bill_unpaid_in_currency', ['currency' => $info['symbol']]),
|
||||
'monetary_value' => $amount,
|
||||
'currency_id' => (string) $info['id'],
|
||||
'currency_code' => $info['code'],
|
||||
'currency_symbol' => $info['symbol'],
|
||||
'currency_decimal_places' => $info['decimal_places'],
|
||||
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'local_icon' => 'calendar-o',
|
||||
'sub_title' => '',
|
||||
];
|
||||
}
|
||||
Log::debug(sprintf('Done with getBillInformation("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d-')));
|
||||
|
||||
if (0 === count($return)) {
|
||||
$currency = $this->primaryCurrency;
|
||||
unset($info, $amount);
|
||||
|
||||
$return[] = [
|
||||
'key' => sprintf('bills-paid-in-%s', $currency->code),
|
||||
'title' => trans('firefly.box_bill_paid_in_currency', ['currency' => $currency->symbol]),
|
||||
'monetary_value' => '0',
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'local_icon' => 'check',
|
||||
'sub_title' => '',
|
||||
];
|
||||
$return[] = [
|
||||
'key' => sprintf('bills-unpaid-in-%s', $currency->code),
|
||||
'title' => trans('firefly.box_bill_unpaid_in_currency', ['currency' => $currency->symbol]),
|
||||
'monetary_value' => '0',
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'local_icon' => 'calendar-o',
|
||||
'sub_title' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -650,16 +524,142 @@ class BasicController extends Controller
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if date is outside session range.
|
||||
*/
|
||||
protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool
|
||||
{ // Validate a preference
|
||||
if ($start->greaterThanOrEqualTo($date) && $end->greaterThanOrEqualTo($date)) {
|
||||
return true;
|
||||
private function getSubscriptionInformation(Carbon $start, Carbon $end): array
|
||||
{
|
||||
Log::debug(sprintf('Now in getBillInformation("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d-')));
|
||||
/*
|
||||
* Since both this method and the chart use the exact same data, we can suffice
|
||||
* with calling the one method in the bill repository that will get this amount.
|
||||
*/
|
||||
$paidAmount = $this->billRepository->sumPaidInRange($start, $end);
|
||||
$unpaidAmount = $this->billRepository->sumUnpaidInRange($start, $end);
|
||||
$currencies = [$this->primaryCurrency->id => $this->primaryCurrency];
|
||||
|
||||
if ($this->convertToPrimary) {
|
||||
$converter = new ExchangeRateConverter();
|
||||
$newPaidAmount = [[
|
||||
'id' => $this->primaryCurrency->id,
|
||||
'name' => $this->primaryCurrency->name,
|
||||
'symbol' => $this->primaryCurrency->symbol,
|
||||
'code' => $this->primaryCurrency->code,
|
||||
'decimal_places' => $this->primaryCurrency->decimal_places,
|
||||
'sum' => '0',
|
||||
]];
|
||||
|
||||
$newUnpaidAmount = [[
|
||||
'id' => $this->primaryCurrency->id,
|
||||
'name' => $this->primaryCurrency->name,
|
||||
'symbol' => $this->primaryCurrency->symbol,
|
||||
'code' => $this->primaryCurrency->code,
|
||||
'decimal_places' => $this->primaryCurrency->decimal_places,
|
||||
'sum' => '0',
|
||||
]];
|
||||
foreach ([$paidAmount, $unpaidAmount] as $index => $array) {
|
||||
foreach ($array as $item) {
|
||||
$currencyId = (int) $item['id'];
|
||||
if (0 === $index) {
|
||||
// paid amount
|
||||
if ($currencyId === $this->primaryCurrency->id) {
|
||||
$newPaidAmount[0]['sum'] = bcadd($newPaidAmount[0]['sum'], (string) $item['sum']);
|
||||
|
||||
continue;
|
||||
}
|
||||
$currencies[$currencyId] ??= $this->currencyRepos->find($currencyId);
|
||||
$convertedAmount = $converter->convert($currencies[$currencyId], $this->primaryCurrency, $start, $item['sum']);
|
||||
$newPaidAmount[0]['sum'] = bcadd($newPaidAmount[0]['sum'], $convertedAmount);
|
||||
|
||||
continue;
|
||||
}
|
||||
// unpaid amount
|
||||
if ($currencyId === $this->primaryCurrency->id) {
|
||||
$newUnpaidAmount[0]['sum'] = bcadd($newUnpaidAmount[0]['sum'], (string) $item['sum']);
|
||||
|
||||
continue;
|
||||
}
|
||||
$currencies[$currencyId] ??= $this->currencyRepos->find($currencyId);
|
||||
$convertedAmount = $converter->convert($currencies[$currencyId], $this->primaryCurrency, $start, $item['sum']);
|
||||
$newUnpaidAmount[0]['sum'] = bcadd($newUnpaidAmount[0]['sum'], $convertedAmount);
|
||||
}
|
||||
}
|
||||
$paidAmount = $newPaidAmount;
|
||||
$unpaidAmount = $newUnpaidAmount;
|
||||
}
|
||||
|
||||
// start and end in the past? use $end
|
||||
return $start->lessThanOrEqualTo($date) && $end->lessThanOrEqualTo($date);
|
||||
// var_dump($paidAmount);
|
||||
// var_dump($unpaidAmount);
|
||||
// exit;
|
||||
|
||||
$return = [];
|
||||
|
||||
/**
|
||||
* @var array $info
|
||||
*/
|
||||
foreach ($paidAmount as $info) {
|
||||
$amount = bcmul((string) $info['sum'], '-1');
|
||||
$return[] = [
|
||||
'key' => sprintf('bills-paid-in-%s', $info['code']),
|
||||
'title' => trans('firefly.box_bill_paid_in_currency', ['currency' => $info['symbol']]),
|
||||
'monetary_value' => $amount,
|
||||
'currency_id' => (string) $info['id'],
|
||||
'currency_code' => $info['code'],
|
||||
'currency_symbol' => $info['symbol'],
|
||||
'currency_decimal_places' => $info['decimal_places'],
|
||||
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'local_icon' => 'check',
|
||||
'sub_title' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array $info
|
||||
*/
|
||||
foreach ($unpaidAmount as $info) {
|
||||
$amount = bcmul((string) $info['sum'], '-1');
|
||||
$return[] = [
|
||||
'key' => sprintf('bills-unpaid-in-%s', $info['code']),
|
||||
'title' => trans('firefly.box_bill_unpaid_in_currency', ['currency' => $info['symbol']]),
|
||||
'monetary_value' => $amount,
|
||||
'currency_id' => (string) $info['id'],
|
||||
'currency_code' => $info['code'],
|
||||
'currency_symbol' => $info['symbol'],
|
||||
'currency_decimal_places' => $info['decimal_places'],
|
||||
'value_parsed' => Amount::formatFlat($info['symbol'], $info['decimal_places'], $amount, false),
|
||||
'local_icon' => 'calendar-o',
|
||||
'sub_title' => '',
|
||||
];
|
||||
}
|
||||
Log::debug(sprintf('Done with getBillInformation("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d-')));
|
||||
|
||||
if (0 === count($return)) {
|
||||
$currency = $this->primaryCurrency;
|
||||
unset($info, $amount);
|
||||
|
||||
$return[] = [
|
||||
'key' => sprintf('bills-paid-in-%s', $currency->code),
|
||||
'title' => trans('firefly.box_bill_paid_in_currency', ['currency' => $currency->symbol]),
|
||||
'monetary_value' => '0',
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'local_icon' => 'check',
|
||||
'sub_title' => '',
|
||||
];
|
||||
$return[] = [
|
||||
'key' => sprintf('bills-unpaid-in-%s', $currency->code),
|
||||
'title' => trans('firefly.box_bill_unpaid_in_currency', ['currency' => $currency->symbol]),
|
||||
'monetary_value' => '0',
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'value_parsed' => Amount::formatFlat($currency->symbol, $currency->decimal_places, '0', false),
|
||||
'local_icon' => 'calendar-o',
|
||||
'sub_title' => '',
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Controllers\System;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
|
||||
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
|
||||
use FireflyIII\Events\Model\TransactionGroup\UserRequestedBatchProcessing;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -44,6 +44,7 @@ class BatchController extends Controller
|
||||
parent::__construct();
|
||||
$this->middleware(function ($request, $next) {
|
||||
$this->repository = app(JournalRepositoryInterface::class);
|
||||
$this->repository->setUser(auth()->user()); // should not have to do this.
|
||||
|
||||
return $next($request);
|
||||
});
|
||||
@@ -64,7 +65,8 @@ class BatchController extends Controller
|
||||
}
|
||||
$flags = new TransactionGroupEventFlags();
|
||||
$flags->applyRules = 'true' === $request->get('apply_rules');
|
||||
event(new CreatedSingleTransactionGroup($group, $flags));
|
||||
event(new UserRequestedBatchProcessing($flags));
|
||||
// event(new CreatedSingleTransactionGroup($group, $flags));
|
||||
|
||||
return response()->json([], 204);
|
||||
}
|
||||
|
||||
@@ -86,37 +86,6 @@ class ConfigurationController extends Controller
|
||||
return response()->api($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all config values.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getDynamicConfiguration(): array
|
||||
{
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site');
|
||||
$updateCheck = FireflyConfig::get('permission_update_check');
|
||||
$lastCheck = FireflyConfig::get('last_update_check');
|
||||
$singleUser = FireflyConfig::get('single_user_mode');
|
||||
|
||||
return [
|
||||
'is_demo_site' => $isDemoSite?->data,
|
||||
'permission_update_check' => null === $updateCheck ? null : (int) $updateCheck->data,
|
||||
'last_update_check' => null === $lastCheck ? null : (int) $lastCheck->data,
|
||||
'single_user_mode' => $singleUser?->data,
|
||||
];
|
||||
}
|
||||
|
||||
private function getStaticConfiguration(): array
|
||||
{
|
||||
$list = EitherConfigKey::$static;
|
||||
$return = [];
|
||||
foreach ($list as $key) {
|
||||
$return[$key] = config($key);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/configuration/getSingleConfiguration
|
||||
@@ -170,6 +139,37 @@ class ConfigurationController extends Controller
|
||||
return response()->api(['data' => $data])->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all config values.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getDynamicConfiguration(): array
|
||||
{
|
||||
$isDemoSite = FireflyConfig::get('is_demo_site');
|
||||
$updateCheck = FireflyConfig::get('permission_update_check');
|
||||
$lastCheck = FireflyConfig::get('last_update_check');
|
||||
$singleUser = FireflyConfig::get('single_user_mode');
|
||||
|
||||
return [
|
||||
'is_demo_site' => $isDemoSite?->data,
|
||||
'permission_update_check' => null === $updateCheck ? null : (int) $updateCheck->data,
|
||||
'last_update_check' => null === $lastCheck ? null : (int) $lastCheck->data,
|
||||
'single_user_mode' => $singleUser?->data,
|
||||
];
|
||||
}
|
||||
|
||||
private function getStaticConfiguration(): array
|
||||
{
|
||||
$list = EitherConfigKey::$static;
|
||||
$return = [];
|
||||
foreach ($list as $key) {
|
||||
$return[$key] = config($key);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getWebhookConfiguration(string $configKey): array
|
||||
{
|
||||
switch ($configKey) {
|
||||
|
||||
@@ -58,8 +58,6 @@ class UserController extends Controller
|
||||
});
|
||||
}
|
||||
|
||||
public function finishBatch(): JsonResponse {}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/users/deleteUser
|
||||
@@ -85,6 +83,8 @@ class UserController extends Controller
|
||||
throw new FireflyException('200025: No access to function.');
|
||||
}
|
||||
|
||||
public function finishBatch(): JsonResponse {}
|
||||
|
||||
/**
|
||||
* This endpoint is documented at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/users/listUser
|
||||
|
||||
@@ -37,9 +37,6 @@ abstract class AggregateFormRequest extends ApiRequest
|
||||
*/
|
||||
protected array $requests = [];
|
||||
|
||||
/** @return array<array|string> */
|
||||
abstract protected function getRequests(): array;
|
||||
|
||||
#[Override]
|
||||
public function initialize(
|
||||
array $query = [],
|
||||
@@ -53,7 +50,7 @@ abstract class AggregateFormRequest extends ApiRequest
|
||||
parent::initialize($query, $request, $attributes, $cookies, $files, $server, $content);
|
||||
|
||||
// instantiate all subrequests and share current requests' bags with them
|
||||
Log::debug('Initializing AggregateFormRequest.');
|
||||
// Log::debug('Initializing AggregateFormRequest.');
|
||||
|
||||
/** @var array|string $config */
|
||||
foreach ($this->getRequests() as $config) {
|
||||
@@ -62,7 +59,7 @@ abstract class AggregateFormRequest extends ApiRequest
|
||||
if (!is_a($requestClass, Request::class, true)) {
|
||||
throw new RuntimeException('getRequests() must return class-strings of subclasses of Request');
|
||||
}
|
||||
Log::debug(sprintf('Initializing subrequest %s', $requestClass));
|
||||
// Log::debug(sprintf('Initializing subrequest %s', $requestClass));
|
||||
|
||||
$instance = $this->requests[] = new $requestClass();
|
||||
$instance->request = $this->request;
|
||||
@@ -77,7 +74,8 @@ abstract class AggregateFormRequest extends ApiRequest
|
||||
$instance->handleConfig(is_array($config) ? $config : []);
|
||||
}
|
||||
}
|
||||
Log::debug('Done initializing AggregateFormRequest.');
|
||||
|
||||
// Log::debug('Done initializing AggregateFormRequest.');
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
@@ -95,9 +93,12 @@ abstract class AggregateFormRequest extends ApiRequest
|
||||
// register all subrequests' validators
|
||||
foreach ($this->requests as $request) {
|
||||
if (method_exists($request, 'withValidator')) {
|
||||
Log::debug(sprintf('Process withValidator from class %s', $request::class));
|
||||
// Log::debug(sprintf('Process withValidator from class %s', $request::class));
|
||||
$request->withValidator($validator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @return array<array|string> */
|
||||
abstract protected function getRequests(): array;
|
||||
}
|
||||
|
||||
@@ -79,25 +79,6 @@ class GenericRequest extends FormRequest
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function parseAccounts(): void
|
||||
{
|
||||
if (0 !== $this->accounts->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('accounts');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $accountId) {
|
||||
$accountId = (int) $accountId;
|
||||
$account = $repository->find($accountId);
|
||||
if (null !== $account) {
|
||||
$this->accounts->push($account);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getBills(): Collection
|
||||
{
|
||||
$this->parseBills();
|
||||
@@ -105,25 +86,6 @@ class GenericRequest extends FormRequest
|
||||
return $this->bills;
|
||||
}
|
||||
|
||||
private function parseBills(): void
|
||||
{
|
||||
if (0 !== $this->bills->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('bills');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $billId) {
|
||||
$billId = (int) $billId;
|
||||
$bill = $repository->find($billId);
|
||||
if (null !== $bill) {
|
||||
$this->bills->push($bill);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getBudgets(): Collection
|
||||
{
|
||||
$this->parseBudgets();
|
||||
@@ -131,25 +93,6 @@ class GenericRequest extends FormRequest
|
||||
return $this->budgets;
|
||||
}
|
||||
|
||||
private function parseBudgets(): void
|
||||
{
|
||||
if (0 !== $this->budgets->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('budgets');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $budgetId) {
|
||||
$budgetId = (int) $budgetId;
|
||||
$budget = $repository->find($budgetId);
|
||||
if (null !== $budget) {
|
||||
$this->budgets->push($budget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getCategories(): Collection
|
||||
{
|
||||
$this->parseCategories();
|
||||
@@ -157,25 +100,6 @@ class GenericRequest extends FormRequest
|
||||
return $this->categories;
|
||||
}
|
||||
|
||||
private function parseCategories(): void
|
||||
{
|
||||
if (0 !== $this->categories->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(CategoryRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('categories');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $categoryId) {
|
||||
$categoryId = (int) $categoryId;
|
||||
$category = $repository->find($categoryId);
|
||||
if (null !== $category) {
|
||||
$this->categories->push($category);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getEnd(): Carbon
|
||||
{
|
||||
$date = $this->getCarbonDate('end');
|
||||
@@ -231,6 +155,97 @@ class GenericRequest extends FormRequest
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
// this is cheating, but it works to initialize the collections.
|
||||
$this->accounts = new Collection();
|
||||
$this->budgets = new Collection();
|
||||
$this->categories = new Collection();
|
||||
$this->bills = new Collection();
|
||||
$this->tags = new Collection();
|
||||
|
||||
return ['start' => 'required|date', 'end' => 'required|date|after_or_equal:start'];
|
||||
}
|
||||
|
||||
private function parseAccounts(): void
|
||||
{
|
||||
if (0 !== $this->accounts->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('accounts');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $accountId) {
|
||||
$accountId = (int) $accountId;
|
||||
$account = $repository->find($accountId);
|
||||
if (null !== $account) {
|
||||
$this->accounts->push($account);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseBills(): void
|
||||
{
|
||||
if (0 !== $this->bills->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(BillRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('bills');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $billId) {
|
||||
$billId = (int) $billId;
|
||||
$bill = $repository->find($billId);
|
||||
if (null !== $bill) {
|
||||
$this->bills->push($bill);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseBudgets(): void
|
||||
{
|
||||
if (0 !== $this->budgets->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(BudgetRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('budgets');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $budgetId) {
|
||||
$budgetId = (int) $budgetId;
|
||||
$budget = $repository->find($budgetId);
|
||||
if (null !== $budget) {
|
||||
$this->budgets->push($budget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseCategories(): void
|
||||
{
|
||||
if (0 !== $this->categories->count()) {
|
||||
return;
|
||||
}
|
||||
$repository = app(CategoryRepositoryInterface::class);
|
||||
$repository->setUser(auth()->user());
|
||||
$array = $this->get('categories');
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $categoryId) {
|
||||
$categoryId = (int) $categoryId;
|
||||
$category = $repository->find($categoryId);
|
||||
if (null !== $category) {
|
||||
$this->categories->push($category);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function parseTags(): void
|
||||
{
|
||||
if (0 !== $this->tags->count()) {
|
||||
@@ -249,19 +264,4 @@ class GenericRequest extends FormRequest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
// this is cheating, but it works to initialize the collections.
|
||||
$this->accounts = new Collection();
|
||||
$this->budgets = new Collection();
|
||||
$this->categories = new Collection();
|
||||
$this->bills = new Collection();
|
||||
$this->tags = new Collection();
|
||||
|
||||
return ['start' => 'required|date', 'end' => 'required|date|after_or_equal:start'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Requests\Models\BudgetLimit;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Factory\TransactionCurrencyFactory;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Rules\IsValidPositiveAmount;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
@@ -89,24 +90,24 @@ class StoreRequest extends FormRequest
|
||||
if (0 !== count($validator->failed())) {
|
||||
return;
|
||||
}
|
||||
$data = $validator->getData();
|
||||
$data = $validator->getData();
|
||||
|
||||
// if no currency has been provided, use the user's default currency:
|
||||
/** @var TransactionCurrencyFactory $factory */
|
||||
$factory = app(TransactionCurrencyFactory::class);
|
||||
$currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null);
|
||||
$factory = app(TransactionCurrencyFactory::class);
|
||||
$currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null);
|
||||
if (null === $currency) {
|
||||
$currency = Amount::getPrimaryCurrency();
|
||||
}
|
||||
$currency->enabled = true;
|
||||
$currency->save();
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$repository->enable($currency);
|
||||
|
||||
// validator already concluded start and end are valid dates:
|
||||
$start = Carbon::parse($data['start'], config('app.timezone'));
|
||||
$end = Carbon::parse($data['end'], config('app.timezone'));
|
||||
$start = Carbon::parse($data['start'], config('app.timezone'));
|
||||
$end = Carbon::parse($data['end'], config('app.timezone'));
|
||||
|
||||
// find limit with same date range and currency.
|
||||
$limit = $budget
|
||||
$limit = $budget
|
||||
->budgetlimits()
|
||||
->where('budget_limits.start_date', $start->format('Y-m-d'))
|
||||
->where('budget_limits.end_date', $end->format('Y-m-d'))
|
||||
|
||||
@@ -70,65 +70,6 @@ class StoreRequest extends FormRequest
|
||||
return ['recurrence' => $recurrence, 'transactions' => $this->getTransactionData(), 'repetitions' => $this->getRepetitionData()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transaction data as it is found in the submitted data. It's a complex method according to code
|
||||
* standards, but it just has a lot of ??-statements because of the fields that may or may not exist.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// transaction data:
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
if (null === $transactions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$return[] = $this->getSingleTransactionData($transaction);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repetition data as it is found in the submitted data.
|
||||
*/
|
||||
private function getRepetitionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// repetition data:
|
||||
/** @var null|array $repetitions */
|
||||
$repetitions = $this->get('repetitions');
|
||||
if (null === $repetitions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $repetition */
|
||||
foreach ($repetitions as $repetition) {
|
||||
$current = [];
|
||||
if (array_key_exists('type', $repetition)) {
|
||||
$current['type'] = $repetition['type'];
|
||||
}
|
||||
if (array_key_exists('moment', $repetition)) {
|
||||
$current['moment'] = $repetition['moment'];
|
||||
}
|
||||
if (array_key_exists('skip', $repetition)) {
|
||||
$current['skip'] = (int) $repetition['skip'];
|
||||
}
|
||||
if (array_key_exists('weekend', $repetition)) {
|
||||
$current['weekend'] = (int) $repetition['weekend'];
|
||||
}
|
||||
|
||||
$return[] = $current;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -190,4 +131,63 @@ class StoreRequest extends FormRequest
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', self::class), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repetition data as it is found in the submitted data.
|
||||
*/
|
||||
private function getRepetitionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// repetition data:
|
||||
/** @var null|array $repetitions */
|
||||
$repetitions = $this->get('repetitions');
|
||||
if (null === $repetitions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $repetition */
|
||||
foreach ($repetitions as $repetition) {
|
||||
$current = [];
|
||||
if (array_key_exists('type', $repetition)) {
|
||||
$current['type'] = $repetition['type'];
|
||||
}
|
||||
if (array_key_exists('moment', $repetition)) {
|
||||
$current['moment'] = $repetition['moment'];
|
||||
}
|
||||
if (array_key_exists('skip', $repetition)) {
|
||||
$current['skip'] = (int) $repetition['skip'];
|
||||
}
|
||||
if (array_key_exists('weekend', $repetition)) {
|
||||
$current['weekend'] = (int) $repetition['weekend'];
|
||||
}
|
||||
|
||||
$return[] = $current;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transaction data as it is found in the submitted data. It's a complex method according to code
|
||||
* standards, but it just has a lot of ??-statements because of the fields that may or may not exist.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// transaction data:
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
if (null === $transactions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$return[] = $this->getSingleTransactionData($transaction);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,70 +77,6 @@ class UpdateRequest extends FormRequest
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repetition data as it is found in the submitted data.
|
||||
*/
|
||||
private function getRepetitionData(): ?array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// repetition data:
|
||||
/** @var null|array $repetitions */
|
||||
$repetitions = $this->get('repetitions');
|
||||
if (null === $repetitions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var array $repetition */
|
||||
foreach ($repetitions as $repetition) {
|
||||
$current = [];
|
||||
if (array_key_exists('type', $repetition)) {
|
||||
$current['type'] = $repetition['type'];
|
||||
}
|
||||
|
||||
if (array_key_exists('moment', $repetition)) {
|
||||
$current['moment'] = (string) $repetition['moment'];
|
||||
}
|
||||
|
||||
if (array_key_exists('skip', $repetition)) {
|
||||
$current['skip'] = (int) $repetition['skip'];
|
||||
}
|
||||
|
||||
if (array_key_exists('weekend', $repetition)) {
|
||||
$current['weekend'] = (int) $repetition['weekend'];
|
||||
}
|
||||
$return[] = $current;
|
||||
}
|
||||
if (0 === count($return)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transaction data as it is found in the submitted data. It's a complex method according to code
|
||||
* standards, but it just has a lot of ??-statements because of the fields that may or may not exist.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// transaction data:
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
if (null === $transactions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$return[] = $this->getSingleTransactionData($transaction);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -207,4 +143,68 @@ class UpdateRequest extends FormRequest
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', self::class), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repetition data as it is found in the submitted data.
|
||||
*/
|
||||
private function getRepetitionData(): ?array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// repetition data:
|
||||
/** @var null|array $repetitions */
|
||||
$repetitions = $this->get('repetitions');
|
||||
if (null === $repetitions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var array $repetition */
|
||||
foreach ($repetitions as $repetition) {
|
||||
$current = [];
|
||||
if (array_key_exists('type', $repetition)) {
|
||||
$current['type'] = $repetition['type'];
|
||||
}
|
||||
|
||||
if (array_key_exists('moment', $repetition)) {
|
||||
$current['moment'] = (string) $repetition['moment'];
|
||||
}
|
||||
|
||||
if (array_key_exists('skip', $repetition)) {
|
||||
$current['skip'] = (int) $repetition['skip'];
|
||||
}
|
||||
|
||||
if (array_key_exists('weekend', $repetition)) {
|
||||
$current['weekend'] = (int) $repetition['weekend'];
|
||||
}
|
||||
$return[] = $current;
|
||||
}
|
||||
if (0 === count($return)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transaction data as it is found in the submitted data. It's a complex method according to code
|
||||
* standards, but it just has a lot of ??-statements because of the fields that may or may not exist.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
// transaction data:
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
if (null === $transactions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/** @var array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$return[] = $this->getSingleTransactionData($transaction);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,43 +65,6 @@ class StoreRequest extends FormRequest
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getRuleTriggers(): array
|
||||
{
|
||||
$triggers = $this->get('triggers');
|
||||
$return = [];
|
||||
if (is_array($triggers)) {
|
||||
foreach ($triggers as $trigger) {
|
||||
$return[] = [
|
||||
'type' => $trigger['type'] ?? '',
|
||||
'value' => $trigger['value'] ?? null,
|
||||
'prohibited' => $this->convertBoolean((string) ($trigger['prohibited'] ?? 'false')),
|
||||
'active' => $this->convertBoolean((string) ($trigger['active'] ?? 'true')),
|
||||
'stop_processing' => $this->convertBoolean((string) ($trigger['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleActions(): array
|
||||
{
|
||||
$actions = $this->get('actions');
|
||||
$return = [];
|
||||
if (is_array($actions)) {
|
||||
foreach ($actions as $action) {
|
||||
$return[] = [
|
||||
'type' => $action['type'],
|
||||
'value' => $action['value'],
|
||||
'active' => $this->convertBoolean((string) ($action['active'] ?? 'true')),
|
||||
'stop_processing' => $this->convertBoolean((string) ($action['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -150,19 +113,6 @@ class StoreRequest extends FormRequest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no triggers in the array of data.
|
||||
*/
|
||||
protected function atLeastOneTrigger(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$triggers = $data['triggers'] ?? [];
|
||||
// need at least one trigger
|
||||
if (!is_countable($triggers) || 0 === count($triggers)) {
|
||||
$validator->errors()->add('title', (string) trans('validation.at_least_one_trigger'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no repetitions in the array of data.
|
||||
*/
|
||||
@@ -176,6 +126,35 @@ class StoreRequest extends FormRequest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no ACTIVE actions in the array of data.
|
||||
*/
|
||||
protected function atLeastOneActiveAction(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
|
||||
/** @var null|array|int|string $actions */
|
||||
$actions = $data['actions'] ?? [];
|
||||
// need at least one trigger
|
||||
if (!is_countable($actions) || 0 === count($actions)) {
|
||||
return;
|
||||
}
|
||||
$allInactive = true;
|
||||
$inactiveIndex = 0;
|
||||
foreach ($actions as $index => $action) {
|
||||
$active = array_key_exists('active', $action) ? $action['active'] : true; // assume true
|
||||
if (true === $active) {
|
||||
$allInactive = false;
|
||||
}
|
||||
if (false === $active) {
|
||||
$inactiveIndex = $index;
|
||||
}
|
||||
}
|
||||
if ($allInactive) {
|
||||
$validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string) trans('validation.at_least_one_active_action'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no ACTIVE triggers in the array of data.
|
||||
*/
|
||||
@@ -206,31 +185,52 @@ class StoreRequest extends FormRequest
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no ACTIVE actions in the array of data.
|
||||
* Adds an error to the validator when there are no triggers in the array of data.
|
||||
*/
|
||||
protected function atLeastOneActiveAction(Validator $validator): void
|
||||
protected function atLeastOneTrigger(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
|
||||
/** @var null|array|int|string $actions */
|
||||
$actions = $data['actions'] ?? [];
|
||||
$data = $validator->getData();
|
||||
$triggers = $data['triggers'] ?? [];
|
||||
// need at least one trigger
|
||||
if (!is_countable($actions) || 0 === count($actions)) {
|
||||
return;
|
||||
}
|
||||
$allInactive = true;
|
||||
$inactiveIndex = 0;
|
||||
foreach ($actions as $index => $action) {
|
||||
$active = array_key_exists('active', $action) ? $action['active'] : true; // assume true
|
||||
if (true === $active) {
|
||||
$allInactive = false;
|
||||
}
|
||||
if (false === $active) {
|
||||
$inactiveIndex = $index;
|
||||
}
|
||||
}
|
||||
if ($allInactive) {
|
||||
$validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string) trans('validation.at_least_one_active_action'));
|
||||
if (!is_countable($triggers) || 0 === count($triggers)) {
|
||||
$validator->errors()->add('title', (string) trans('validation.at_least_one_trigger'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getRuleActions(): array
|
||||
{
|
||||
$actions = $this->get('actions');
|
||||
$return = [];
|
||||
if (is_array($actions)) {
|
||||
foreach ($actions as $action) {
|
||||
$return[] = [
|
||||
'type' => $action['type'],
|
||||
'value' => $action['value'],
|
||||
'active' => $this->convertBoolean((string) ($action['active'] ?? 'true')),
|
||||
'stop_processing' => $this->convertBoolean((string) ($action['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleTriggers(): array
|
||||
{
|
||||
$triggers = $this->get('triggers');
|
||||
$return = [];
|
||||
if (is_array($triggers)) {
|
||||
foreach ($triggers as $trigger) {
|
||||
$return[] = [
|
||||
'type' => $trigger['type'] ?? '',
|
||||
'value' => $trigger['value'] ?? null,
|
||||
'prohibited' => $this->convertBoolean((string) ($trigger['prohibited'] ?? 'false')),
|
||||
'active' => $this->convertBoolean((string) ($trigger['active'] ?? 'true')),
|
||||
'stop_processing' => $this->convertBoolean((string) ($trigger['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,9 +42,19 @@ class TestRequest extends FormRequest
|
||||
return ['page' => $this->getPage(), 'start' => $this->getDate('start'), 'end' => $this->getDate('end'), 'accounts' => $this->getAccounts()];
|
||||
}
|
||||
|
||||
private function getPage(): int
|
||||
public function rules(): array
|
||||
{
|
||||
return 0 === (int) $this->query('page') ? 1 : (int) $this->query('page');
|
||||
return [
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
{
|
||||
return $this->get('accounts') ?? [];
|
||||
}
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
@@ -58,18 +68,8 @@ class TestRequest extends FormRequest
|
||||
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
private function getPage(): int
|
||||
{
|
||||
return $this->get('accounts') ?? [];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
return 0 === (int) $this->query('page') ? 1 : (int) $this->query('page');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,21 @@ class TriggerRequest extends FormRequest
|
||||
return ['start' => $this->getDate('start'), 'end' => $this->getDate('end'), 'accounts' => $this->getAccounts()];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
{
|
||||
return $this->get('accounts') ?? [];
|
||||
}
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
{
|
||||
$value = $this->query($field);
|
||||
@@ -52,19 +67,4 @@ class TriggerRequest extends FormRequest
|
||||
|
||||
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
{
|
||||
return $this->get('accounts') ?? [];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,52 +72,6 @@ class UpdateRequest extends FormRequest
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleTriggers(): ?array
|
||||
{
|
||||
if (!$this->has('triggers')) {
|
||||
return null;
|
||||
}
|
||||
$triggers = $this->get('triggers');
|
||||
$return = [];
|
||||
if (is_array($triggers)) {
|
||||
foreach ($triggers as $trigger) {
|
||||
$active = array_key_exists('active', $trigger) ? $trigger['active'] : true;
|
||||
$prohibited = array_key_exists('prohibited', $trigger) ? $trigger['prohibited'] : false;
|
||||
$stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false;
|
||||
$return[] = [
|
||||
'type' => $trigger['type'],
|
||||
'value' => $trigger['value'],
|
||||
'prohibited' => $prohibited,
|
||||
'active' => $active,
|
||||
'stop_processing' => $stopProcessing,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleActions(): ?array
|
||||
{
|
||||
if (!$this->has('actions')) {
|
||||
return null;
|
||||
}
|
||||
$actions = $this->get('actions');
|
||||
$return = [];
|
||||
if (is_array($actions)) {
|
||||
foreach ($actions as $action) {
|
||||
$return[] = [
|
||||
'type' => $action['type'],
|
||||
'value' => $action['value'],
|
||||
'active' => $this->convertBoolean((string) ($action['active'] ?? 'false')),
|
||||
'stop_processing' => $this->convertBoolean((string) ($action['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -170,46 +124,6 @@ class UpdateRequest extends FormRequest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no repetitions in the array of data.
|
||||
*/
|
||||
protected function atLeastOneTrigger(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$triggers = $data['triggers'] ?? null;
|
||||
// need at least one trigger
|
||||
if (is_array($triggers) && 0 === count($triggers)) {
|
||||
$validator->errors()->add('title', (string) trans('validation.at_least_one_trigger'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no repetitions in the array of data.
|
||||
*/
|
||||
protected function atLeastOneValidTrigger(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$triggers = $data['triggers'] ?? [];
|
||||
$allInactive = true;
|
||||
$inactiveIndex = 0;
|
||||
// need at least one trigger
|
||||
if (is_array($triggers) && 0 === count($triggers)) {
|
||||
return;
|
||||
}
|
||||
foreach ($triggers as $index => $trigger) {
|
||||
$active = array_key_exists('active', $trigger) ? $trigger['active'] : true; // assume true
|
||||
if (true === $active) {
|
||||
$allInactive = false;
|
||||
}
|
||||
if (false === $active) {
|
||||
$inactiveIndex = $index;
|
||||
}
|
||||
}
|
||||
if ($allInactive) {
|
||||
$validator->errors()->add(sprintf('triggers.%d.active', $inactiveIndex), (string) trans('validation.at_least_one_active_trigger'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no repetitions in the array of data.
|
||||
*/
|
||||
@@ -223,6 +137,19 @@ class UpdateRequest extends FormRequest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no repetitions in the array of data.
|
||||
*/
|
||||
protected function atLeastOneTrigger(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$triggers = $data['triggers'] ?? null;
|
||||
// need at least one trigger
|
||||
if (is_array($triggers) && 0 === count($triggers)) {
|
||||
$validator->errors()->add('title', (string) trans('validation.at_least_one_trigger'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no repetitions in the array of data.
|
||||
*/
|
||||
@@ -250,4 +177,77 @@ class UpdateRequest extends FormRequest
|
||||
$validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string) trans('validation.at_least_one_active_action'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an error to the validator when there are no repetitions in the array of data.
|
||||
*/
|
||||
protected function atLeastOneValidTrigger(Validator $validator): void
|
||||
{
|
||||
$data = $validator->getData();
|
||||
$triggers = $data['triggers'] ?? [];
|
||||
$allInactive = true;
|
||||
$inactiveIndex = 0;
|
||||
// need at least one trigger
|
||||
if (is_array($triggers) && 0 === count($triggers)) {
|
||||
return;
|
||||
}
|
||||
foreach ($triggers as $index => $trigger) {
|
||||
$active = array_key_exists('active', $trigger) ? $trigger['active'] : true; // assume true
|
||||
if (true === $active) {
|
||||
$allInactive = false;
|
||||
}
|
||||
if (false === $active) {
|
||||
$inactiveIndex = $index;
|
||||
}
|
||||
}
|
||||
if ($allInactive) {
|
||||
$validator->errors()->add(sprintf('triggers.%d.active', $inactiveIndex), (string) trans('validation.at_least_one_active_trigger'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getRuleActions(): ?array
|
||||
{
|
||||
if (!$this->has('actions')) {
|
||||
return null;
|
||||
}
|
||||
$actions = $this->get('actions');
|
||||
$return = [];
|
||||
if (is_array($actions)) {
|
||||
foreach ($actions as $action) {
|
||||
$return[] = [
|
||||
'type' => $action['type'],
|
||||
'value' => $action['value'],
|
||||
'active' => $this->convertBoolean((string) ($action['active'] ?? 'false')),
|
||||
'stop_processing' => $this->convertBoolean((string) ($action['stop_processing'] ?? 'false')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function getRuleTriggers(): ?array
|
||||
{
|
||||
if (!$this->has('triggers')) {
|
||||
return null;
|
||||
}
|
||||
$triggers = $this->get('triggers');
|
||||
$return = [];
|
||||
if (is_array($triggers)) {
|
||||
foreach ($triggers as $trigger) {
|
||||
$active = array_key_exists('active', $trigger) ? $trigger['active'] : true;
|
||||
$prohibited = array_key_exists('prohibited', $trigger) ? $trigger['prohibited'] : false;
|
||||
$stopProcessing = array_key_exists('stop_processing', $trigger) ? $trigger['stop_processing'] : false;
|
||||
$return[] = [
|
||||
'type' => $trigger['type'],
|
||||
'value' => $trigger['value'],
|
||||
'prohibited' => $prohibited,
|
||||
'active' => $active,
|
||||
'stop_processing' => $stopProcessing,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,21 @@ class TestRequest extends FormRequest
|
||||
return ['start' => $this->getDate('start'), 'end' => $this->getDate('end'), 'accounts' => $this->getAccounts()];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
{
|
||||
return $this->get('accounts') ?? [];
|
||||
}
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
{
|
||||
$value = $this->query($field);
|
||||
@@ -52,19 +67,4 @@ class TestRequest extends FormRequest
|
||||
|
||||
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
{
|
||||
return $this->get('accounts') ?? [];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,15 +42,9 @@ class TriggerRequest extends FormRequest
|
||||
return ['start' => $this->getDate('start'), 'end' => $this->getDate('end'), 'accounts' => $this->getAccounts()];
|
||||
}
|
||||
|
||||
private function getDate(string $field): ?Carbon
|
||||
public function rules(): array
|
||||
{
|
||||
$value = $this->query($field);
|
||||
if (is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
$value = (string) $value;
|
||||
|
||||
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
return ['start' => 'date|after:1970-01-02|before:2038-01-17', 'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17'];
|
||||
}
|
||||
|
||||
private function getAccounts(): array
|
||||
@@ -62,8 +56,14 @@ class TriggerRequest extends FormRequest
|
||||
return $this->get('accounts');
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
private function getDate(string $field): ?Carbon
|
||||
{
|
||||
return ['start' => 'date|after:1970-01-02|before:2038-01-17', 'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17'];
|
||||
$value = $this->query($field);
|
||||
if (is_array($value)) {
|
||||
return null;
|
||||
}
|
||||
$value = (string) $value;
|
||||
|
||||
return null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', substr($value, 0, 10));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,106 +73,6 @@ class StoreRequest extends FormRequest
|
||||
// TODO include location and ability to process it.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/**
|
||||
* @var array $transaction
|
||||
*/
|
||||
foreach ($this->get('transactions') as $transaction) {
|
||||
$object = new NullArrayObject($transaction);
|
||||
$return[] = [
|
||||
'type' => $this->clearString($object['type']),
|
||||
'date' => $this->dateFromValue($object['date']),
|
||||
'order' => $this->integerFromValue((string) $object['order']),
|
||||
|
||||
'currency_id' => $this->integerFromValue((string) $object['currency_id']),
|
||||
'currency_code' => $this->clearString((string) $object['currency_code']),
|
||||
|
||||
// location
|
||||
'latitude' => $this->floatFromValue((string) $object['latitude']),
|
||||
'longitude' => $this->floatFromValue((string) $object['longitude']),
|
||||
'zoom_level' => $this->integerFromValue((string) $object['zoom_level']),
|
||||
|
||||
// foreign currency info:
|
||||
'foreign_currency_id' => $this->integerFromValue((string) $object['foreign_currency_id']),
|
||||
'foreign_currency_code' => $this->clearString((string) $object['foreign_currency_code']),
|
||||
|
||||
// amount and foreign amount. Cannot be 0.
|
||||
'amount' => $this->clearString((string) $object['amount']),
|
||||
'foreign_amount' => $this->clearString((string) $object['foreign_amount']),
|
||||
|
||||
// description.
|
||||
'description' => $this->clearString($object['description']),
|
||||
|
||||
// source of transaction. If everything is null, assume cash account.
|
||||
'source_id' => $this->integerFromValue((string) $object['source_id']),
|
||||
'source_name' => $this->clearString((string) $object['source_name']),
|
||||
'source_iban' => $this->clearIban((string) $object['source_iban']),
|
||||
'source_number' => $this->clearString((string) $object['source_number']),
|
||||
'source_bic' => $this->clearString((string) $object['source_bic']),
|
||||
|
||||
// destination of transaction. If everything is null, assume cash account.
|
||||
'destination_id' => $this->integerFromValue((string) $object['destination_id']),
|
||||
'destination_name' => $this->clearString((string) $object['destination_name']),
|
||||
'destination_iban' => $this->clearIban((string) $object['destination_iban']),
|
||||
'destination_number' => $this->clearString((string) $object['destination_number']),
|
||||
'destination_bic' => $this->clearString((string) $object['destination_bic']),
|
||||
|
||||
// budget info
|
||||
'budget_id' => $this->integerFromValue((string) $object['budget_id']),
|
||||
'budget_name' => $this->clearString((string) $object['budget_name']),
|
||||
|
||||
// category info
|
||||
'category_id' => $this->integerFromValue((string) $object['category_id']),
|
||||
'category_name' => $this->clearString((string) $object['category_name']),
|
||||
|
||||
// journal bill reference. Optional. Will only work for withdrawals
|
||||
'bill_id' => $this->integerFromValue((string) $object['bill_id']),
|
||||
'bill_name' => $this->clearString((string) $object['bill_name']),
|
||||
|
||||
// piggy bank reference. Optional. Will only work for transfers
|
||||
'piggy_bank_id' => $this->integerFromValue((string) $object['piggy_bank_id']),
|
||||
'piggy_bank_name' => $this->clearString((string) $object['piggy_bank_name']),
|
||||
|
||||
// some other interesting properties
|
||||
'reconciled' => $this->convertBoolean((string) $object['reconciled']),
|
||||
'notes' => $this->clearStringKeepNewlines((string) $object['notes']),
|
||||
'tags' => $this->arrayFromValue($object['tags']),
|
||||
|
||||
// all custom fields:
|
||||
'internal_reference' => $this->clearString((string) $object['internal_reference']),
|
||||
'external_id' => $this->clearString((string) $object['external_id']),
|
||||
'original_source' => sprintf('ff3-v%s', config('firefly.version')),
|
||||
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
|
||||
'bunq_payment_id' => $this->clearString((string) $object['bunq_payment_id']),
|
||||
'external_url' => $this->clearString((string) $object['external_url']),
|
||||
|
||||
'sepa_cc' => $this->clearString((string) $object['sepa_cc']),
|
||||
'sepa_ct_op' => $this->clearString((string) $object['sepa_ct_op']),
|
||||
'sepa_ct_id' => $this->clearString((string) $object['sepa_ct_id']),
|
||||
'sepa_db' => $this->clearString((string) $object['sepa_db']),
|
||||
'sepa_country' => $this->clearString((string) $object['sepa_country']),
|
||||
'sepa_ep' => $this->clearString((string) $object['sepa_ep']),
|
||||
'sepa_ci' => $this->clearString((string) $object['sepa_ci']),
|
||||
'sepa_batch_id' => $this->clearString((string) $object['sepa_batch_id']),
|
||||
// custom date fields. Must be Carbon objects. Presence is optional.
|
||||
'interest_date' => $this->dateFromValue($object['interest_date']),
|
||||
'book_date' => $this->dateFromValue($object['book_date']),
|
||||
'process_date' => $this->dateFromValue($object['process_date']),
|
||||
'due_date' => $this->dateFromValue($object['due_date']),
|
||||
'payment_date' => $this->dateFromValue($object['payment_date']),
|
||||
'invoice_date' => $this->dateFromValue($object['invoice_date']),
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -305,4 +205,104 @@ class StoreRequest extends FormRequest
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', self::class), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/**
|
||||
* @var array $transaction
|
||||
*/
|
||||
foreach ($this->get('transactions') as $transaction) {
|
||||
$object = new NullArrayObject($transaction);
|
||||
$return[] = [
|
||||
'type' => $this->clearString($object['type']),
|
||||
'date' => $this->dateFromValue($object['date']),
|
||||
'order' => $this->integerFromValue((string) $object['order']),
|
||||
|
||||
'currency_id' => $this->integerFromValue((string) $object['currency_id']),
|
||||
'currency_code' => $this->clearString((string) $object['currency_code']),
|
||||
|
||||
// location
|
||||
'latitude' => $this->floatFromValue((string) $object['latitude']),
|
||||
'longitude' => $this->floatFromValue((string) $object['longitude']),
|
||||
'zoom_level' => $this->integerFromValue((string) $object['zoom_level']),
|
||||
|
||||
// foreign currency info:
|
||||
'foreign_currency_id' => $this->integerFromValue((string) $object['foreign_currency_id']),
|
||||
'foreign_currency_code' => $this->clearString((string) $object['foreign_currency_code']),
|
||||
|
||||
// amount and foreign amount. Cannot be 0.
|
||||
'amount' => $this->clearString((string) $object['amount']),
|
||||
'foreign_amount' => $this->clearString((string) $object['foreign_amount']),
|
||||
|
||||
// description.
|
||||
'description' => $this->clearString($object['description']),
|
||||
|
||||
// source of transaction. If everything is null, assume cash account.
|
||||
'source_id' => $this->integerFromValue((string) $object['source_id']),
|
||||
'source_name' => $this->clearString((string) $object['source_name']),
|
||||
'source_iban' => $this->clearIban((string) $object['source_iban']),
|
||||
'source_number' => $this->clearString((string) $object['source_number']),
|
||||
'source_bic' => $this->clearString((string) $object['source_bic']),
|
||||
|
||||
// destination of transaction. If everything is null, assume cash account.
|
||||
'destination_id' => $this->integerFromValue((string) $object['destination_id']),
|
||||
'destination_name' => $this->clearString((string) $object['destination_name']),
|
||||
'destination_iban' => $this->clearIban((string) $object['destination_iban']),
|
||||
'destination_number' => $this->clearString((string) $object['destination_number']),
|
||||
'destination_bic' => $this->clearString((string) $object['destination_bic']),
|
||||
|
||||
// budget info
|
||||
'budget_id' => $this->integerFromValue((string) $object['budget_id']),
|
||||
'budget_name' => $this->clearString((string) $object['budget_name']),
|
||||
|
||||
// category info
|
||||
'category_id' => $this->integerFromValue((string) $object['category_id']),
|
||||
'category_name' => $this->clearString((string) $object['category_name']),
|
||||
|
||||
// journal bill reference. Optional. Will only work for withdrawals
|
||||
'bill_id' => $this->integerFromValue((string) $object['bill_id']),
|
||||
'bill_name' => $this->clearString((string) $object['bill_name']),
|
||||
|
||||
// piggy bank reference. Optional. Will only work for transfers
|
||||
'piggy_bank_id' => $this->integerFromValue((string) $object['piggy_bank_id']),
|
||||
'piggy_bank_name' => $this->clearString((string) $object['piggy_bank_name']),
|
||||
|
||||
// some other interesting properties
|
||||
'reconciled' => $this->convertBoolean((string) $object['reconciled']),
|
||||
'notes' => $this->clearStringKeepNewlines((string) $object['notes']),
|
||||
'tags' => $this->arrayFromValue($object['tags']),
|
||||
|
||||
// all custom fields:
|
||||
'internal_reference' => $this->clearString((string) $object['internal_reference']),
|
||||
'external_id' => $this->clearString((string) $object['external_id']),
|
||||
'original_source' => sprintf('ff3-v%s', config('firefly.version')),
|
||||
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
|
||||
'bunq_payment_id' => $this->clearString((string) $object['bunq_payment_id']),
|
||||
'external_url' => $this->clearString((string) $object['external_url']),
|
||||
|
||||
'sepa_cc' => $this->clearString((string) $object['sepa_cc']),
|
||||
'sepa_ct_op' => $this->clearString((string) $object['sepa_ct_op']),
|
||||
'sepa_ct_id' => $this->clearString((string) $object['sepa_ct_id']),
|
||||
'sepa_db' => $this->clearString((string) $object['sepa_db']),
|
||||
'sepa_country' => $this->clearString((string) $object['sepa_country']),
|
||||
'sepa_ep' => $this->clearString((string) $object['sepa_ep']),
|
||||
'sepa_ci' => $this->clearString((string) $object['sepa_ci']),
|
||||
'sepa_batch_id' => $this->clearString((string) $object['sepa_batch_id']),
|
||||
// custom date fields. Must be Carbon objects. Presence is optional.
|
||||
'interest_date' => $this->dateFromValue($object['interest_date']),
|
||||
'book_date' => $this->dateFromValue($object['book_date']),
|
||||
'process_date' => $this->dateFromValue($object['process_date']),
|
||||
'due_date' => $this->dateFromValue($object['due_date']),
|
||||
'payment_date' => $this->dateFromValue($object['payment_date']),
|
||||
'invoice_date' => $this->dateFromValue($object['invoice_date']),
|
||||
];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,158 +133,6 @@ class UpdateRequest extends FormRequest
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$return = [];
|
||||
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
|
||||
if (!is_countable($transactions)) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
/** @var null|array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
if (!is_array($transaction)) {
|
||||
throw new FireflyException('Invalid data submitted: transaction is not array.');
|
||||
}
|
||||
// default response is to update nothing in the transaction:
|
||||
$current = [];
|
||||
$current = $this->getIntegerData($current, $transaction);
|
||||
$current = $this->getStringData($current, $transaction);
|
||||
$current = $this->getNlStringData($current, $transaction);
|
||||
$current = $this->getDateData($current, $transaction);
|
||||
$current = $this->getBooleanData($current, $transaction);
|
||||
$current = $this->getArrayData($current, $transaction);
|
||||
$current = $this->getFloatData($current, $transaction);
|
||||
$return[] = $current;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each field, add it to the array if a reference is present in the request:
|
||||
*
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getIntegerData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->integerFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->integerFromValue((string) $transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getStringData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->stringFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->clearString((string) $transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getNlStringData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->textareaFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->clearStringKeepNewlines((string) $transaction[$fieldName]); // keep newlines
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getDateData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->dateFields as $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]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getBooleanData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->booleanFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->convertBoolean((string) $transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getArrayData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->arrayFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->arrayFromValue($transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getFloatData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->floatFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$value = $transaction[$fieldName];
|
||||
if (is_float($value)) {
|
||||
$current[$fieldName] = sprintf('%.12f', $value);
|
||||
}
|
||||
if (!is_float($value)) {
|
||||
$current[$fieldName] = (string) $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* The rules that the incoming request must be matched against.
|
||||
*/
|
||||
@@ -406,4 +254,156 @@ class UpdateRequest extends FormRequest
|
||||
Log::channel('audit')->error(sprintf('Validation errors in %s', self::class), $validator->errors()->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getArrayData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->arrayFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->arrayFromValue($transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getBooleanData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->booleanFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->convertBoolean((string) $transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getDateData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->dateFields as $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]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getFloatData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->floatFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$value = $transaction[$fieldName];
|
||||
if (is_float($value)) {
|
||||
$current[$fieldName] = sprintf('%.12f', $value);
|
||||
}
|
||||
if (!is_float($value)) {
|
||||
$current[$fieldName] = (string) $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* For each field, add it to the array if a reference is present in the request:
|
||||
*
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getIntegerData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->integerFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->integerFromValue((string) $transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getNlStringData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->textareaFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->clearStringKeepNewlines((string) $transaction[$fieldName]); // keep newlines
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $current
|
||||
* @param array<string, mixed> $transaction
|
||||
*/
|
||||
private function getStringData(array $current, array $transaction): array
|
||||
{
|
||||
foreach ($this->stringFields as $fieldName) {
|
||||
if (array_key_exists($fieldName, $transaction)) {
|
||||
$current[$fieldName] = $this->clearString((string) $transaction[$fieldName]);
|
||||
}
|
||||
}
|
||||
|
||||
return $current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transaction data.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getTransactionData(): array
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$return = [];
|
||||
|
||||
/** @var null|array $transactions */
|
||||
$transactions = $this->get('transactions');
|
||||
|
||||
if (!is_countable($transactions)) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
/** @var null|array $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
if (!is_array($transaction)) {
|
||||
throw new FireflyException('Invalid data submitted: transaction is not array.');
|
||||
}
|
||||
// default response is to update nothing in the transaction:
|
||||
$current = [];
|
||||
$current = $this->getIntegerData($current, $transaction);
|
||||
$current = $this->getStringData($current, $transaction);
|
||||
$current = $this->getNlStringData($current, $transaction);
|
||||
$current = $this->getDateData($current, $transaction);
|
||||
$current = $this->getBooleanData($current, $transaction);
|
||||
$current = $this->getArrayData($current, $transaction);
|
||||
$current = $this->getFloatData($current, $transaction);
|
||||
$return[] = $current;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,14 +62,6 @@ class ConvertsDatesToUTC extends Command
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function ConvertModeltoUTC(string $model, array $fields): void
|
||||
{
|
||||
/** @var string $field */
|
||||
foreach ($fields as $field) {
|
||||
$this->convertFieldtoUTC($model, $field);
|
||||
}
|
||||
}
|
||||
|
||||
private function convertFieldtoUTC(string $model, string $field): void
|
||||
{
|
||||
$this->info(sprintf('Converting %s.%s to UTC', $model, $field));
|
||||
@@ -98,4 +90,12 @@ class ConvertsDatesToUTC extends Command
|
||||
$item->save();
|
||||
});
|
||||
}
|
||||
|
||||
private function ConvertModeltoUTC(string $model, array $fields): void
|
||||
{
|
||||
/** @var string $field */
|
||||
foreach ($fields as $field) {
|
||||
$this->convertFieldtoUTC($model, $field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,56 +123,17 @@ class CorrectsAccountTypes extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function stupidLaravel(): void
|
||||
private function canCreateDestination(array $validDestinations): bool
|
||||
{
|
||||
$this->count = 0;
|
||||
return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, true);
|
||||
}
|
||||
|
||||
private function inspectJournal(TransactionJournal $journal): void
|
||||
/**
|
||||
* Can only create revenue accounts out of the blue.
|
||||
*/
|
||||
private function canCreateSource(array $validSources): bool
|
||||
{
|
||||
Log::debug(sprintf('Now inspecting journal #%d', $journal->id));
|
||||
$transactions = $journal->transactions()->count();
|
||||
if (2 !== $transactions) {
|
||||
Log::debug(sprintf('Journal has %d transactions, so can\'t fix.', $transactions));
|
||||
$this->friendlyError(sprintf('Cannot inspect transaction journal #%d because it has %d transaction(s) instead of 2.', $journal->id, $transactions));
|
||||
|
||||
return;
|
||||
}
|
||||
$type = $journal->transactionType->type;
|
||||
$sourceTransaction = $this->getSourceTransaction($journal);
|
||||
$destTransaction = $this->getDestinationTransaction($journal);
|
||||
$sourceAccount = $sourceTransaction->account;
|
||||
$sourceAccountType = $sourceAccount->accountType->type;
|
||||
$destAccount = $destTransaction->account;
|
||||
$destAccountType = $destAccount->accountType->type;
|
||||
|
||||
if (!array_key_exists($type, $this->expected)) {
|
||||
Log::info(sprintf('No source/destination info for transaction type %s.', $type));
|
||||
$this->friendlyError(sprintf('No source/destination info for transaction type %s.', $type));
|
||||
|
||||
return;
|
||||
}
|
||||
if (!array_key_exists($sourceAccountType, $this->expected[$type])) {
|
||||
Log::debug(sprintf('[a] Going to fix journal #%d', $journal->id));
|
||||
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
|
||||
|
||||
return;
|
||||
}
|
||||
$expectedTypes = $this->expected[$type][$sourceAccountType];
|
||||
if (!in_array($destAccountType, $expectedTypes, true)) {
|
||||
Log::debug(sprintf('[b] Going to fix journal #%d', $journal->id));
|
||||
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
private function getSourceTransaction(TransactionJournal $journal): Transaction
|
||||
{
|
||||
return $journal->transactions->firstWhere('amount', '<', 0);
|
||||
}
|
||||
|
||||
private function getDestinationTransaction(TransactionJournal $journal): Transaction
|
||||
{
|
||||
return $journal->transactions->firstWhere('amount', '>', 0);
|
||||
return in_array(AccountTypeEnum::REVENUE->value, $validSources, true);
|
||||
}
|
||||
|
||||
private function fixJournal(TransactionJournal $journal, string $transactionType, Transaction $source, Transaction $dest): void
|
||||
@@ -266,12 +227,113 @@ class CorrectsAccountTypes extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function shouldBeTransfer(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
private function getDestinationTransaction(TransactionJournal $journal): Transaction
|
||||
{
|
||||
return
|
||||
TransactionTypeEnum::TRANSFER->value === $transactionType
|
||||
&& AccountTypeEnum::ASSET->value === $sourceType
|
||||
&& $this->isLiability($destinationType);
|
||||
return $journal->transactions->firstWhere('amount', '>', 0);
|
||||
}
|
||||
|
||||
private function getSourceTransaction(TransactionJournal $journal): Transaction
|
||||
{
|
||||
return $journal->transactions->firstWhere('amount', '<', 0);
|
||||
}
|
||||
|
||||
private function giveNewDestinationAccount(TransactionJournal $journal, Account $newDestination): void
|
||||
{
|
||||
$destTransaction = $this->getDestinationTransaction($journal);
|
||||
$oldDest = $destTransaction->account;
|
||||
$destTransaction->account_id = $newDestination->id;
|
||||
$destTransaction->save();
|
||||
$message = sprintf(
|
||||
'Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").',
|
||||
$journal->id,
|
||||
$oldDest->id,
|
||||
$oldDest->name,
|
||||
$newDestination->id,
|
||||
$newDestination->name
|
||||
);
|
||||
$this->friendlyInfo($message);
|
||||
$journal->refresh();
|
||||
Log::debug($message);
|
||||
}
|
||||
|
||||
private function giveNewExpense(TransactionJournal $journal, Transaction $destination): void
|
||||
{
|
||||
Log::debug(sprintf('An account of type "%s" could be a valid destination.', AccountTypeEnum::EXPENSE->value));
|
||||
$this->factory->setUser($journal->user);
|
||||
$name = $destination->account->name;
|
||||
$newDestination = $this->factory->findOrCreate($name, AccountTypeEnum::EXPENSE->value);
|
||||
$destination->account()->associate($newDestination);
|
||||
$destination->save();
|
||||
$this->friendlyPositive(sprintf(
|
||||
'Firefly III gave transaction #%d a new destination %s: #%d ("%s").',
|
||||
$journal->transaction_group_id,
|
||||
AccountTypeEnum::EXPENSE->value,
|
||||
$newDestination->id,
|
||||
$newDestination->name
|
||||
));
|
||||
Log::debug(sprintf('Associated account #%d with transaction #%d', $newDestination->id, $destination->id));
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function giveNewRevenue(TransactionJournal $journal, Transaction $source): void
|
||||
{
|
||||
Log::debug(sprintf('An account of type "%s" could be a valid source.', AccountTypeEnum::REVENUE->value));
|
||||
$this->factory->setUser($journal->user);
|
||||
$name = $source->account->name;
|
||||
$newSource = $this->factory->findOrCreate($name, AccountTypeEnum::REVENUE->value);
|
||||
$source->account()->associate($newSource);
|
||||
$source->save();
|
||||
$this->friendlyPositive(sprintf(
|
||||
'Firefly III gave transaction #%d a new source %s: #%d ("%s").',
|
||||
$journal->transaction_group_id,
|
||||
AccountTypeEnum::REVENUE->value,
|
||||
$newSource->id,
|
||||
$newSource->name
|
||||
));
|
||||
Log::debug(sprintf('Associated account #%d with transaction #%d', $newSource->id, $source->id));
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function hasValidAccountType(array $validTypes, string $accountType): bool
|
||||
{
|
||||
return in_array($accountType, $validTypes, true);
|
||||
}
|
||||
|
||||
private function inspectJournal(TransactionJournal $journal): void
|
||||
{
|
||||
Log::debug(sprintf('Now inspecting journal #%d', $journal->id));
|
||||
$transactions = $journal->transactions()->count();
|
||||
if (2 !== $transactions) {
|
||||
Log::debug(sprintf('Journal has %d transactions, so can\'t fix.', $transactions));
|
||||
$this->friendlyError(sprintf('Cannot inspect transaction journal #%d because it has %d transaction(s) instead of 2.', $journal->id, $transactions));
|
||||
|
||||
return;
|
||||
}
|
||||
$type = $journal->transactionType->type;
|
||||
$sourceTransaction = $this->getSourceTransaction($journal);
|
||||
$destTransaction = $this->getDestinationTransaction($journal);
|
||||
$sourceAccount = $sourceTransaction->account;
|
||||
$sourceAccountType = $sourceAccount->accountType->type;
|
||||
$destAccount = $destTransaction->account;
|
||||
$destAccountType = $destAccount->accountType->type;
|
||||
|
||||
if (!array_key_exists($type, $this->expected)) {
|
||||
Log::info(sprintf('No source/destination info for transaction type %s.', $type));
|
||||
$this->friendlyError(sprintf('No source/destination info for transaction type %s.', $type));
|
||||
|
||||
return;
|
||||
}
|
||||
if (!array_key_exists($sourceAccountType, $this->expected[$type])) {
|
||||
Log::debug(sprintf('[a] Going to fix journal #%d', $journal->id));
|
||||
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
|
||||
|
||||
return;
|
||||
}
|
||||
$expectedTypes = $this->expected[$type][$sourceAccountType];
|
||||
if (!in_array($destAccountType, $expectedTypes, true)) {
|
||||
Log::debug(sprintf('[b] Going to fix journal #%d', $journal->id));
|
||||
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
private function isLiability(string $destinationType): bool
|
||||
@@ -282,27 +344,6 @@ class CorrectsAccountTypes extends Command
|
||||
|| AccountTypeEnum::MORTGAGE->value === $destinationType;
|
||||
}
|
||||
|
||||
private function makeTransfer(TransactionJournal $journal): void
|
||||
{
|
||||
// from an asset to a liability should be a withdrawal:
|
||||
$withdrawal = TransactionType::whereType(TransactionTypeEnum::WITHDRAWAL->value)->first();
|
||||
$journal->transactionType()->associate($withdrawal);
|
||||
$journal->save();
|
||||
$message = sprintf('Converted transaction #%d from a transfer to a withdrawal.', $journal->id);
|
||||
$this->friendlyInfo($message);
|
||||
Log::debug($message);
|
||||
// check it again:
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function shouldBeDeposit(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return
|
||||
TransactionTypeEnum::TRANSFER->value === $transactionType
|
||||
&& $this->isLiability($sourceType)
|
||||
&& AccountTypeEnum::ASSET->value === $destinationType;
|
||||
}
|
||||
|
||||
private function makeDeposit(TransactionJournal $journal): void
|
||||
{
|
||||
// from a liability to an asset should be a deposit.
|
||||
@@ -316,14 +357,6 @@ class CorrectsAccountTypes extends Command
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function shouldGoToExpenseAccount(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return
|
||||
TransactionTypeEnum::WITHDRAWAL->value === $transactionType
|
||||
&& AccountTypeEnum::ASSET->value === $sourceType
|
||||
&& AccountTypeEnum::REVENUE->value === $destinationType;
|
||||
}
|
||||
|
||||
private function makeExpenseDestination(TransactionJournal $journal, Transaction $destination): void
|
||||
{
|
||||
// withdrawals with a revenue account as destination instead of an expense account.
|
||||
@@ -345,14 +378,6 @@ class CorrectsAccountTypes extends Command
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function shouldComeFromRevenueAccount(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return
|
||||
TransactionTypeEnum::DEPOSIT->value === $transactionType
|
||||
&& AccountTypeEnum::EXPENSE->value === $sourceType
|
||||
&& AccountTypeEnum::ASSET->value === $destinationType;
|
||||
}
|
||||
|
||||
private function makeRevenueSource(TransactionJournal $journal, Transaction $source): void
|
||||
{
|
||||
// deposits with an expense account as source instead of a revenue account.
|
||||
@@ -375,78 +400,53 @@ class CorrectsAccountTypes extends Command
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can only create revenue accounts out of the blue.
|
||||
*/
|
||||
private function canCreateSource(array $validSources): bool
|
||||
private function makeTransfer(TransactionJournal $journal): void
|
||||
{
|
||||
return in_array(AccountTypeEnum::REVENUE->value, $validSources, true);
|
||||
}
|
||||
|
||||
private function hasValidAccountType(array $validTypes, string $accountType): bool
|
||||
{
|
||||
return in_array($accountType, $validTypes, true);
|
||||
}
|
||||
|
||||
private function giveNewRevenue(TransactionJournal $journal, Transaction $source): void
|
||||
{
|
||||
Log::debug(sprintf('An account of type "%s" could be a valid source.', AccountTypeEnum::REVENUE->value));
|
||||
$this->factory->setUser($journal->user);
|
||||
$name = $source->account->name;
|
||||
$newSource = $this->factory->findOrCreate($name, AccountTypeEnum::REVENUE->value);
|
||||
$source->account()->associate($newSource);
|
||||
$source->save();
|
||||
$this->friendlyPositive(sprintf(
|
||||
'Firefly III gave transaction #%d a new source %s: #%d ("%s").',
|
||||
$journal->transaction_group_id,
|
||||
AccountTypeEnum::REVENUE->value,
|
||||
$newSource->id,
|
||||
$newSource->name
|
||||
));
|
||||
Log::debug(sprintf('Associated account #%d with transaction #%d', $newSource->id, $source->id));
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function canCreateDestination(array $validDestinations): bool
|
||||
{
|
||||
return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, true);
|
||||
}
|
||||
|
||||
private function giveNewExpense(TransactionJournal $journal, Transaction $destination): void
|
||||
{
|
||||
Log::debug(sprintf('An account of type "%s" could be a valid destination.', AccountTypeEnum::EXPENSE->value));
|
||||
$this->factory->setUser($journal->user);
|
||||
$name = $destination->account->name;
|
||||
$newDestination = $this->factory->findOrCreate($name, AccountTypeEnum::EXPENSE->value);
|
||||
$destination->account()->associate($newDestination);
|
||||
$destination->save();
|
||||
$this->friendlyPositive(sprintf(
|
||||
'Firefly III gave transaction #%d a new destination %s: #%d ("%s").',
|
||||
$journal->transaction_group_id,
|
||||
AccountTypeEnum::EXPENSE->value,
|
||||
$newDestination->id,
|
||||
$newDestination->name
|
||||
));
|
||||
Log::debug(sprintf('Associated account #%d with transaction #%d', $newDestination->id, $destination->id));
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function giveNewDestinationAccount(TransactionJournal $journal, Account $newDestination): void
|
||||
{
|
||||
$destTransaction = $this->getDestinationTransaction($journal);
|
||||
$oldDest = $destTransaction->account;
|
||||
$destTransaction->account_id = $newDestination->id;
|
||||
$destTransaction->save();
|
||||
$message = sprintf(
|
||||
'Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").',
|
||||
$journal->id,
|
||||
$oldDest->id,
|
||||
$oldDest->name,
|
||||
$newDestination->id,
|
||||
$newDestination->name
|
||||
);
|
||||
// from an asset to a liability should be a withdrawal:
|
||||
$withdrawal = TransactionType::whereType(TransactionTypeEnum::WITHDRAWAL->value)->first();
|
||||
$journal->transactionType()->associate($withdrawal);
|
||||
$journal->save();
|
||||
$message = sprintf('Converted transaction #%d from a transfer to a withdrawal.', $journal->id);
|
||||
$this->friendlyInfo($message);
|
||||
$journal->refresh();
|
||||
Log::debug($message);
|
||||
// check it again:
|
||||
$this->inspectJournal($journal);
|
||||
}
|
||||
|
||||
private function shouldBeDeposit(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return
|
||||
TransactionTypeEnum::TRANSFER->value === $transactionType
|
||||
&& $this->isLiability($sourceType)
|
||||
&& AccountTypeEnum::ASSET->value === $destinationType;
|
||||
}
|
||||
|
||||
private function shouldBeTransfer(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return
|
||||
TransactionTypeEnum::TRANSFER->value === $transactionType
|
||||
&& AccountTypeEnum::ASSET->value === $sourceType
|
||||
&& $this->isLiability($destinationType);
|
||||
}
|
||||
|
||||
private function shouldComeFromRevenueAccount(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return
|
||||
TransactionTypeEnum::DEPOSIT->value === $transactionType
|
||||
&& AccountTypeEnum::EXPENSE->value === $sourceType
|
||||
&& AccountTypeEnum::ASSET->value === $destinationType;
|
||||
}
|
||||
|
||||
private function shouldGoToExpenseAccount(string $transactionType, string $sourceType, string $destinationType): bool
|
||||
{
|
||||
return
|
||||
TransactionTypeEnum::WITHDRAWAL->value === $transactionType
|
||||
&& AccountTypeEnum::ASSET->value === $sourceType
|
||||
&& AccountTypeEnum::REVENUE->value === $destinationType;
|
||||
}
|
||||
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Services\Internal\Destroy\GenericDestroyService;
|
||||
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use Illuminate\Console\Command;
|
||||
@@ -51,9 +53,13 @@ class CorrectsAmounts extends Command
|
||||
|
||||
protected $description = 'This command makes sure positive and negative amounts are recorded correctly.';
|
||||
protected $signature = 'correction:amounts';
|
||||
private JournalDestroyService $service;
|
||||
private GenericDestroyService $genericService;
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$this->service = new JournalDestroyService();
|
||||
$this->genericService = new GenericDestroyService();
|
||||
// transfers must not have foreign currency info if both accounts have the same currency.
|
||||
$this->correctTransfers();
|
||||
// auto budgets must be positive
|
||||
@@ -177,8 +183,7 @@ class CorrectsAmounts extends Command
|
||||
|
||||
private function deleteJournal(TransactionJournal $journal): void
|
||||
{
|
||||
$journal->transactionGroup?->delete();
|
||||
$journal->delete();
|
||||
$this->service->destroy($journal);
|
||||
}
|
||||
|
||||
private function fixAutoBudgets(): void
|
||||
@@ -248,6 +253,33 @@ class CorrectsAmounts extends Command
|
||||
$this->friendlyInfo(sprintf('Corrected %d recurring transaction amount(s).', $count));
|
||||
}
|
||||
|
||||
private function fixRuleTrigger(RuleTrigger $item): bool
|
||||
{
|
||||
try {
|
||||
$check = bccomp((string) $item->trigger_value, '0');
|
||||
} catch (ValueError) {
|
||||
$this->friendlyError(sprintf(
|
||||
'Rule #%d contained invalid %s-trigger "%s". The trigger has been removed, and the rule is disabled.',
|
||||
$item->rule_id,
|
||||
$item->trigger_type,
|
||||
$item->trigger_value
|
||||
));
|
||||
$item->rule->active = false;
|
||||
$item->rule->save();
|
||||
$this->genericService->deleteRuleTrigger($item);
|
||||
|
||||
return false;
|
||||
}
|
||||
if (-1 === $check) {
|
||||
$item->trigger_value = Steam::positive($item->trigger_value);
|
||||
$item->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Foreach loop is unavoidable here.
|
||||
*/
|
||||
@@ -269,33 +301,6 @@ class CorrectsAmounts extends Command
|
||||
$this->friendlyInfo(sprintf('Corrected %d rule trigger amount(s).', $fixed));
|
||||
}
|
||||
|
||||
private function fixRuleTrigger(RuleTrigger $item): bool
|
||||
{
|
||||
try {
|
||||
$check = bccomp((string) $item->trigger_value, '0');
|
||||
} catch (ValueError) {
|
||||
$this->friendlyError(sprintf(
|
||||
'Rule #%d contained invalid %s-trigger "%s". The trigger has been removed, and the rule is disabled.',
|
||||
$item->rule_id,
|
||||
$item->trigger_type,
|
||||
$item->trigger_value
|
||||
));
|
||||
$item->rule->active = false;
|
||||
$item->rule->save();
|
||||
$item->forceDelete();
|
||||
|
||||
return false;
|
||||
}
|
||||
if (-1 === $check) {
|
||||
$item->trigger_value = Steam::positive($item->trigger_value);
|
||||
$item->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function validateJournal(TransactionJournal $journal): bool
|
||||
{
|
||||
$countSource = $journal->transactions()->where('amount', '<', 0)->count();
|
||||
|
||||
@@ -25,8 +25,10 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Events\UpdatedTransactionGroup;
|
||||
use FireflyIII\Handlers\Events\UpdatedGroupEventHandler;
|
||||
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
|
||||
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventObjects;
|
||||
use FireflyIII\Events\Model\TransactionGroup\UpdatedSingleTransactionGroup;
|
||||
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Console\Command;
|
||||
@@ -44,8 +46,8 @@ class CorrectsGroupAccounts extends Command
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$groups = [];
|
||||
$res = TransactionJournal::groupBy('transaction_group_id')->get(['transaction_group_id', DB::raw('COUNT(transaction_group_id) as the_count')]);
|
||||
$groups = [];
|
||||
$res = TransactionJournal::groupBy('transaction_group_id')->get(['transaction_group_id', DB::raw('COUNT(transaction_group_id) as the_count')]);
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($res as $journal) {
|
||||
@@ -53,14 +55,17 @@ class CorrectsGroupAccounts extends Command
|
||||
$groups[] = (int) $journal->transaction_group_id;
|
||||
}
|
||||
}
|
||||
$handler = new UpdatedGroupEventHandler();
|
||||
$flags = new TransactionGroupEventFlags();
|
||||
$flags->applyRules = true;
|
||||
$flags->fireWebhooks = true;
|
||||
$flags->recalculateCredit = true;
|
||||
$objects = new TransactionGroupEventObjects();
|
||||
foreach ($groups as $groupId) {
|
||||
$group = TransactionGroup::find($groupId);
|
||||
// TODO in theory the "unifyAccounts" method could lead to the need for run recalculations.
|
||||
// FIXME needs to be a collection.
|
||||
$event = new UpdatedTransactionGroup($group, true, true, false);
|
||||
$handler->unifyAccounts($event);
|
||||
$objects->appendFromTransactionGroup($group);
|
||||
}
|
||||
event(new UpdatedSingleTransactionGroup($flags, $objects));
|
||||
event(new WebhookMessagesRequestSending());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -51,33 +51,6 @@ class CorrectsIbans extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function filterIbans(Collection $accounts): void
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$iban = (string) $account->iban;
|
||||
$newIban = Steam::filterSpaces($iban);
|
||||
if ('' !== $iban && $iban !== $newIban) {
|
||||
$account->iban = $newIban;
|
||||
$account->save();
|
||||
$this->friendlyInfo(sprintf('Removed spaces from IBAN of account #%d', $account->id));
|
||||
++$this->count;
|
||||
}
|
||||
// same for account number:
|
||||
$accountNumber = $account->accountMeta->where('name', 'account_number')->first();
|
||||
if (null !== $accountNumber) {
|
||||
$number = (string) $accountNumber->value;
|
||||
$newNumber = Steam::filterSpaces($number);
|
||||
if ('' !== $number && $number !== $newNumber) {
|
||||
$accountNumber->value = $newNumber;
|
||||
$accountNumber->save();
|
||||
$this->friendlyInfo(sprintf('Removed spaces from account number of account #%d', $account->id));
|
||||
++$this->count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function countAndCorrectIbans(Collection $accounts): void
|
||||
{
|
||||
$set = [];
|
||||
@@ -119,4 +92,31 @@ class CorrectsIbans extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function filterIbans(Collection $accounts): void
|
||||
{
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$iban = (string) $account->iban;
|
||||
$newIban = Steam::filterSpaces($iban);
|
||||
if ('' !== $iban && $iban !== $newIban) {
|
||||
$account->iban = $newIban;
|
||||
$account->save();
|
||||
$this->friendlyInfo(sprintf('Removed spaces from IBAN of account #%d', $account->id));
|
||||
++$this->count;
|
||||
}
|
||||
// same for account number:
|
||||
$accountNumber = $account->accountMeta->where('name', 'account_number')->first();
|
||||
if (null !== $accountNumber) {
|
||||
$number = (string) $accountNumber->value;
|
||||
$newNumber = Steam::filterSpaces($number);
|
||||
if ('' !== $number && $number !== $newNumber) {
|
||||
$accountNumber->value = $newNumber;
|
||||
$accountNumber->save();
|
||||
$this->friendlyInfo(sprintf('Removed spaces from account number of account #%d', $account->id));
|
||||
++$this->count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,11 @@ class CorrectsInvertedBudgetLimits extends Command
|
||||
$budgetLimit->end_date = $start;
|
||||
$budgetLimit->saveQuietly();
|
||||
}
|
||||
|
||||
if ($set->count() > 0) {
|
||||
// FIXME here be a available budget event.
|
||||
}
|
||||
|
||||
if (1 === $set->count()) {
|
||||
$this->friendlyInfo('Corrected one budget limit to have the right start/end dates.');
|
||||
|
||||
|
||||
@@ -65,16 +65,6 @@ class CorrectsOpeningBalanceCurrencies extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getJournals(): Collection
|
||||
{
|
||||
/** @var Collection */
|
||||
return TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->where('transaction_types.type', TransactionTypeEnum::OPENING_BALANCE->value)
|
||||
->get(['transaction_journals.*'])
|
||||
;
|
||||
}
|
||||
|
||||
private function correctJournal(TransactionJournal $journal): int
|
||||
{
|
||||
// get the asset account for this opening balance:
|
||||
@@ -107,6 +97,25 @@ class CorrectsOpeningBalanceCurrencies extends Command
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getCurrency(Account $account): TransactionCurrency
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repos */
|
||||
$repos = app(AccountRepositoryInterface::class);
|
||||
$repos->setUser($account->user);
|
||||
|
||||
return $repos->getAccountCurrency($account) ?? Amount::getPrimaryCurrencyByUserGroup($account->userGroup);
|
||||
}
|
||||
|
||||
private function getJournals(): Collection
|
||||
{
|
||||
/** @var Collection */
|
||||
return TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->where('transaction_types.type', TransactionTypeEnum::OPENING_BALANCE->value)
|
||||
->get(['transaction_journals.*'])
|
||||
;
|
||||
}
|
||||
|
||||
private function setCorrectCurrency(Account $account, TransactionJournal $journal): int
|
||||
{
|
||||
$currency = $this->getCurrency($account);
|
||||
@@ -128,13 +137,4 @@ class CorrectsOpeningBalanceCurrencies extends Command
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function getCurrency(Account $account): TransactionCurrency
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repos */
|
||||
$repos = app(AccountRepositoryInterface::class);
|
||||
$repos->setUser($account->user);
|
||||
|
||||
return $repos->getAccountCurrency($account) ?? Amount::getPrimaryCurrencyByUserGroup($account->userGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,28 +25,9 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Handlers\Observer\TransactionObserver;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AutoBudget;
|
||||
use FireflyIII\Models\AvailableBudget;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Services\Internal\Recalculate\PrimaryAmountRecalculationService;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\Builder as DatabaseBuilder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CorrectsPrimaryCurrencyAmounts extends Command
|
||||
@@ -70,186 +51,11 @@ class CorrectsPrimaryCurrencyAmounts extends Command
|
||||
Log::debug('Will update all primary currency amounts. This may take some time.');
|
||||
$this->friendlyWarning('Recalculating primary currency amounts for all objects. This may take some time!');
|
||||
|
||||
/** @var UserGroupRepositoryInterface $repository */
|
||||
$repository = app(UserGroupRepositoryInterface::class);
|
||||
$calculator = new PrimaryAmountRecalculationService();
|
||||
$calculator->recalculate();
|
||||
|
||||
Preferences::mark();
|
||||
|
||||
/** @var UserGroup $userGroup */
|
||||
foreach ($repository->getAll() as $userGroup) {
|
||||
$this->recalculateForGroup($userGroup);
|
||||
}
|
||||
$this->friendlyInfo('Recalculated all primary currency amounts.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function recalculateForGroup(UserGroup $userGroup): void
|
||||
{
|
||||
Log::debug(sprintf('Now recalculating for user group #%d', $userGroup->id));
|
||||
$this->recalculateAccounts($userGroup);
|
||||
|
||||
// do a check with the group's currency so we can skip some stuff.
|
||||
$currency = Amount::getPrimaryCurrencyByUserGroup($userGroup);
|
||||
|
||||
$this->recalculatePiggyBanks($userGroup, $currency);
|
||||
$this->recalculateBudgets($userGroup, $currency);
|
||||
$this->recalculateAvailableBudgets($userGroup, $currency);
|
||||
$this->recalculateBills($userGroup, $currency);
|
||||
$this->calculateTransactions($userGroup, $currency);
|
||||
}
|
||||
|
||||
private function recalculateAccounts(UserGroup $userGroup): void
|
||||
{
|
||||
$set = $userGroup
|
||||
->accounts()
|
||||
->where(static function (EloquentBuilder $q): void {
|
||||
$q->whereNotNull('virtual_balance');
|
||||
|
||||
// this needs a different piece of code for postgres.
|
||||
if ('pgsql' === config('database.default')) {
|
||||
$q->orWhere(DB::raw('CAST(virtual_balance AS TEXT)'), '!=', '');
|
||||
}
|
||||
if ('pgsql' !== config('database.default')) {
|
||||
$q->orWhere('virtual_balance', '!=', '');
|
||||
}
|
||||
})
|
||||
->get()
|
||||
;
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($set as $account) {
|
||||
$account->touch();
|
||||
}
|
||||
Log::debug(sprintf('Recalculated %d accounts for user group #%d.', $set->count(), $userGroup->id));
|
||||
}
|
||||
|
||||
private function recalculatePiggyBanks(UserGroup $userGroup, TransactionCurrency $currency): void
|
||||
{
|
||||
$converter = new ExchangeRateConverter();
|
||||
$converter->setUserGroup($userGroup);
|
||||
$converter->setIgnoreSettings(true);
|
||||
$repository = app(PiggyBankRepositoryInterface::class);
|
||||
$repository->setUserGroup($userGroup);
|
||||
$set = $repository->getPiggyBanks();
|
||||
$set = $set->filter(static fn (PiggyBank $piggyBank): bool => $currency->id !== $piggyBank->transaction_currency_id);
|
||||
foreach ($set as $piggyBank) {
|
||||
$piggyBank->encrypted = false;
|
||||
$piggyBank->save();
|
||||
|
||||
foreach ($piggyBank->accounts as $account) {
|
||||
$account->pivot->native_current_amount = null;
|
||||
if (0 !== bccomp((string) $account->pivot->current_amount, '0')) {
|
||||
$account->pivot->native_current_amount = $converter->convert(
|
||||
$piggyBank->transactionCurrency,
|
||||
$currency,
|
||||
today(),
|
||||
(string) $account->pivot->current_amount
|
||||
);
|
||||
}
|
||||
$account->pivot->save();
|
||||
}
|
||||
$this->recalculatePiggyBankEvents($piggyBank);
|
||||
}
|
||||
Log::debug(sprintf('Recalculated %d piggy banks for user group #%d.', $set->count(), $userGroup->id));
|
||||
}
|
||||
|
||||
private function recalculatePiggyBankEvents(PiggyBank $piggyBank): void
|
||||
{
|
||||
$set = $piggyBank->piggyBankEvents()->get();
|
||||
$set->each(static function (PiggyBankEvent $event): void { // @phpstan-ignore-line
|
||||
$event->touch();
|
||||
});
|
||||
Log::debug(sprintf('Recalculated %d piggy bank events.', $set->count()));
|
||||
}
|
||||
|
||||
private function recalculateBudgets(UserGroup $userGroup, TransactionCurrency $currency): void
|
||||
{
|
||||
$set = $userGroup->budgets()->get();
|
||||
|
||||
/** @var Budget $budget */
|
||||
foreach ($set as $budget) {
|
||||
$this->recalculateBudgetLimits($budget, $currency);
|
||||
$this->recalculateAutoBudgets($budget, $currency);
|
||||
}
|
||||
Log::debug(sprintf('Recalculated %d budgets.', $set->count()));
|
||||
}
|
||||
|
||||
private function recalculateBudgetLimits(Budget $budget, TransactionCurrency $currency): void
|
||||
{
|
||||
$set = $budget->budgetlimits()->where('transaction_currency_id', '!=', $currency->id)->get();
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($set as $limit) {
|
||||
Log::debug(sprintf('Will now touch BL #%d', $limit->id));
|
||||
$limit->touch();
|
||||
Log::debug(sprintf('Done with touch BL #%d', $limit->id));
|
||||
}
|
||||
Log::debug(sprintf('Recalculated %d budget limits for budget #%d.', $set->count(), $budget->id));
|
||||
}
|
||||
|
||||
private function recalculateAutoBudgets(Budget $budget, TransactionCurrency $currency): void
|
||||
{
|
||||
$set = $budget->autoBudgets()->where('transaction_currency_id', '!=', $currency->id)->get();
|
||||
|
||||
/** @var AutoBudget $autoBudget */
|
||||
foreach ($set as $autoBudget) {
|
||||
$autoBudget->touch();
|
||||
}
|
||||
Log::debug(sprintf('Recalculated %d auto budgets for budget #%d.', $set->count(), $budget->id));
|
||||
}
|
||||
|
||||
private function recalculateAvailableBudgets(UserGroup $userGroup, TransactionCurrency $currency): void
|
||||
{
|
||||
Log::debug('Start with available budgets.');
|
||||
$set = $userGroup->availableBudgets()->where('transaction_currency_id', '!=', $currency->id)->get();
|
||||
|
||||
/** @var AvailableBudget $budget */
|
||||
foreach ($set as $budget) {
|
||||
$budget->touch();
|
||||
}
|
||||
Log::debug(sprintf('Recalculated %d available budgets.', $set->count()));
|
||||
}
|
||||
|
||||
private function recalculateBills(UserGroup $userGroup, TransactionCurrency $currency): void
|
||||
{
|
||||
$set = $userGroup->bills()->where('transaction_currency_id', '!=', $currency->id)->get();
|
||||
|
||||
/** @var Bill $bill */
|
||||
foreach ($set as $bill) {
|
||||
$bill->touch();
|
||||
}
|
||||
Log::debug(sprintf('Recalculated %d bills.', $set->count()));
|
||||
}
|
||||
|
||||
private function calculateTransactions(UserGroup $userGroup, TransactionCurrency $currency): void
|
||||
{
|
||||
// custom query because of the potential size of this update.
|
||||
$set = DB::table('transactions')
|
||||
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->where('transaction_journals.user_group_id', $userGroup->id)
|
||||
->where(static function (DatabaseBuilder $q1) use ($currency): void {
|
||||
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
|
||||
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
|
||||
})->orWhere(static function (DatabaseBuilder $q3) use ($currency): void {
|
||||
$q3->whereNot('transactions.transaction_currency_id', $currency->id)->whereNot('transactions.foreign_currency_id', $currency->id);
|
||||
});
|
||||
})
|
||||
// ->where(static function (DatabaseBuilder $q) use ($currency): void {
|
||||
// $q->whereNot('transactions.transaction_currency_id', $currency->id)
|
||||
// ->whereNot('transactions.foreign_currency_id', $currency->id)
|
||||
// ;
|
||||
// })
|
||||
->get(['transactions.id'])
|
||||
;
|
||||
TransactionObserver::$recalculate = false;
|
||||
foreach ($set as $item) {
|
||||
// here we are.
|
||||
/** @var null|Transaction $transaction */
|
||||
$transaction = Transaction::find($item->id);
|
||||
$transaction?->touch();
|
||||
}
|
||||
TransactionObserver::$recalculate = true;
|
||||
Log::debug(sprintf('Recalculated %d transactions.', $set->count()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,17 +54,6 @@ class CorrectsRecurringTransactions extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->recurringRepos = app(RecurringRepositoryInterface::class);
|
||||
$this->userRepos = app(UserRepositoryInterface::class);
|
||||
}
|
||||
|
||||
private function correctTransactions(): void
|
||||
{
|
||||
$users = $this->userRepos->all();
|
||||
@@ -75,17 +64,6 @@ class CorrectsRecurringTransactions extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function processUser(User $user): void
|
||||
{
|
||||
$this->recurringRepos->setUser($user);
|
||||
$recurrences = $this->recurringRepos->get();
|
||||
|
||||
/** @var Recurrence $recurrence */
|
||||
foreach ($recurrences as $recurrence) {
|
||||
$this->processRecurrence($recurrence);
|
||||
}
|
||||
}
|
||||
|
||||
private function processRecurrence(Recurrence $recurrence): void
|
||||
{
|
||||
/** @var RecurrenceTransaction $transaction */
|
||||
@@ -115,4 +93,26 @@ class CorrectsRecurringTransactions extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processUser(User $user): void
|
||||
{
|
||||
$this->recurringRepos->setUser($user);
|
||||
$recurrences = $this->recurringRepos->get();
|
||||
|
||||
/** @var Recurrence $recurrence */
|
||||
foreach ($recurrences as $recurrence) {
|
||||
$this->processRecurrence($recurrence);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->recurringRepos = app(RecurringRepositoryInterface::class);
|
||||
$this->userRepos = app(UserRepositoryInterface::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Console\Commands\Correction;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Models\AccountBalance;
|
||||
use FireflyIII\Models\AvailableBudget;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
@@ -47,7 +46,6 @@ class CorrectsTimezoneInformation extends Command
|
||||
use ShowsFriendlyMessages;
|
||||
|
||||
public static array $models = [
|
||||
AccountBalance::class => ['date'], // done
|
||||
AvailableBudget::class => ['start_date', 'end_date'], // done
|
||||
Bill::class => ['date', 'end_date', 'extension_date'], // done
|
||||
BudgetLimit::class => ['start_date', 'end_date'], // done
|
||||
|
||||
@@ -67,6 +67,15 @@ class CorrectsTransactionTypes extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function changeJournal(TransactionJournal $journal, string $expectedType): void
|
||||
{
|
||||
$type = TransactionType::whereType($expectedType)->first();
|
||||
if (null !== $type) {
|
||||
$journal->transaction_type_id = $type->id;
|
||||
$journal->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all transaction journals.
|
||||
*/
|
||||
@@ -105,31 +114,6 @@ class CorrectsTransactionTypes extends Command
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getSourceAccount(TransactionJournal $journal): Account
|
||||
{
|
||||
$collection = $journal->transactions->filter(static fn (Transaction $transaction): bool => $transaction->amount < 0);
|
||||
if (0 === $collection->count()) {
|
||||
throw new FireflyException(sprintf('300001: Journal #%d has no source transaction.', $journal->id));
|
||||
}
|
||||
if (1 !== $collection->count()) {
|
||||
throw new FireflyException(sprintf('300002: Journal #%d has multiple source transactions.', $journal->id));
|
||||
}
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $collection->first();
|
||||
|
||||
/** @var null|Account $account */
|
||||
$account = $transaction->account;
|
||||
if (null === $account) {
|
||||
throw new FireflyException(sprintf('300003: Journal #%d, transaction #%d has no source account.', $journal->id, $transaction->id));
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -155,12 +139,28 @@ class CorrectsTransactionTypes extends Command
|
||||
return $account;
|
||||
}
|
||||
|
||||
private function changeJournal(TransactionJournal $journal, string $expectedType): void
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getSourceAccount(TransactionJournal $journal): Account
|
||||
{
|
||||
$type = TransactionType::whereType($expectedType)->first();
|
||||
if (null !== $type) {
|
||||
$journal->transaction_type_id = $type->id;
|
||||
$journal->save();
|
||||
$collection = $journal->transactions->filter(static fn (Transaction $transaction): bool => $transaction->amount < 0);
|
||||
if (0 === $collection->count()) {
|
||||
throw new FireflyException(sprintf('300001: Journal #%d has no source transaction.', $journal->id));
|
||||
}
|
||||
if (1 !== $collection->count()) {
|
||||
throw new FireflyException(sprintf('300002: Journal #%d has multiple source transactions.', $journal->id));
|
||||
}
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $collection->first();
|
||||
|
||||
/** @var null|Account $account */
|
||||
$account = $transaction->account;
|
||||
if (null === $account) {
|
||||
throw new FireflyException(sprintf('300003: Journal #%d, transaction #%d has no source account.', $journal->id, $transaction->id));
|
||||
}
|
||||
|
||||
return $account;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,271 +70,6 @@ class CorrectsUnevenAmount extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function convertOldStyleTransfers(): void
|
||||
{
|
||||
Log::debug('convertOldStyleTransfers()');
|
||||
// select transactions with a foreign amount and a foreign currency. and it's a transfer. and they are different.
|
||||
$transactions = Transaction::distinct()
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
|
||||
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionTypeEnum::TRANSFER->value)
|
||||
->whereNotNull('foreign_currency_id')
|
||||
->whereNotNull('foreign_amount')
|
||||
->get(['transactions.transaction_journal_id'])
|
||||
;
|
||||
$count = 0;
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
/** @var null|TransactionJournal $journal */
|
||||
$journal = TransactionJournal::find($transaction->transaction_journal_id);
|
||||
if (null === $journal) {
|
||||
Log::debug('Found no journal, continue.');
|
||||
|
||||
continue;
|
||||
}
|
||||
// needs to be a transfer.
|
||||
if (TransactionTypeEnum::TRANSFER->value !== $journal->transactionType->type) {
|
||||
Log::debug('Must be a transfer, continue.');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var null|Transaction $destination */
|
||||
$destination = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
|
||||
/** @var null|Transaction $source */
|
||||
$source = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
if (null === $destination || null === $source) {
|
||||
Log::debug('Source or destination transaction is NULL, continue.');
|
||||
|
||||
// will be picked up later.
|
||||
continue;
|
||||
}
|
||||
if ($source->transaction_currency_id === $destination->transaction_currency_id) {
|
||||
Log::debug('Ready to swap data between transactions.');
|
||||
$destination->foreign_currency_id = $source->transaction_currency_id;
|
||||
$destination->foreign_amount = Steam::positive($source->amount);
|
||||
$destination->transaction_currency_id = $source->foreign_currency_id;
|
||||
$destination->amount = Steam::positive($source->foreign_amount);
|
||||
$destination->balance_dirty = true;
|
||||
$source->balance_dirty = true;
|
||||
$destination->save();
|
||||
$source->save();
|
||||
$this->friendlyWarning(sprintf('Corrected foreign amounts of transfer #%d.', $journal->id));
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
if (0 === $count) {
|
||||
return;
|
||||
}
|
||||
$this->friendlyPositive(sprintf('Fixed %d transfer(s) with unbalanced amounts.', $count));
|
||||
}
|
||||
|
||||
private function fixUnevenAmounts(): void
|
||||
{
|
||||
Log::debug('fixUnevenAmounts()');
|
||||
$journals = DB::table('transactions')
|
||||
->groupBy('transaction_journal_id')
|
||||
->whereNull('deleted_at')
|
||||
->get(['transaction_journal_id', DB::raw('SUM(amount) AS the_sum')])
|
||||
;
|
||||
|
||||
/** @var stdClass $entry */
|
||||
foreach ($journals as $entry) {
|
||||
$sum = (string) $entry->the_sum;
|
||||
$sum = Steam::floatalize($sum);
|
||||
if (
|
||||
!is_numeric($sum)
|
||||
|| '' === $sum // @phpstan-ignore-line
|
||||
|| str_contains($sum, 'e')
|
||||
|| str_contains($sum, ',')
|
||||
) {
|
||||
$message = sprintf('Journal #%d has an invalid sum ("%s"). No sure what to do.', $entry->transaction_journal_id, $entry->the_sum);
|
||||
$this->friendlyWarning($message);
|
||||
Log::warning($message);
|
||||
++$this->count;
|
||||
|
||||
continue;
|
||||
}
|
||||
$res = -1;
|
||||
|
||||
try {
|
||||
$res = bccomp($sum, '0');
|
||||
} catch (ValueError $e) {
|
||||
$this->friendlyError(sprintf('Could not bccomp("%s", "0").', $sum));
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
if (0 !== $res) {
|
||||
$this->fixJournal((int) $entry->transaction_journal_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function fixJournal(int $param): void
|
||||
{
|
||||
// one of the transactions is bad.
|
||||
$journal = TransactionJournal::find($param);
|
||||
if (null === $journal) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var null|Transaction $source */
|
||||
$source = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
|
||||
if (null === $source) {
|
||||
$this->friendlyError(sprintf(
|
||||
'Journal #%d ("%s") has no source transaction. It will be deleted to maintain database consistency.',
|
||||
$journal->id ?? 0,
|
||||
$journal->description ?? ''
|
||||
));
|
||||
Transaction::where('transaction_journal_id', $journal->id ?? 0)->forceDelete();
|
||||
TransactionJournal::where('id', $journal->id ?? 0)->forceDelete();
|
||||
++$this->count;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$amount = bcmul('-1', (string) $source->amount);
|
||||
|
||||
// fix amount of destination:
|
||||
/** @var null|Transaction $destination */
|
||||
$destination = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
|
||||
if (null === $destination) {
|
||||
$this->friendlyError(sprintf(
|
||||
'Journal #%d ("%s") has no destination transaction. It will be deleted to maintain database consistency.',
|
||||
$journal->id ?? 0,
|
||||
$journal->description ?? ''
|
||||
));
|
||||
|
||||
Transaction::where('transaction_journal_id', $journal->id ?? 0)->forceDelete();
|
||||
TransactionJournal::where('id', $journal->id ?? 0)->forceDelete();
|
||||
++$this->count;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// may still be able to salvage this journal if it is a transfer with foreign currency info
|
||||
if ($this->isForeignCurrencyTransfer($journal) || $this->isBetweenAssetAndLiability($journal)) {
|
||||
Log::debug(sprintf('Can skip foreign currency transfer / asset+liability transaction #%d.', $journal->id));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$message = sprintf('Sum of journal #%d is not zero, journal is broken and now fixed.', $journal->id);
|
||||
|
||||
$this->friendlyWarning($message);
|
||||
Log::warning($message);
|
||||
|
||||
$destination->amount = $amount;
|
||||
$destination->save();
|
||||
|
||||
$message = sprintf('Corrected amount in transaction journal #%d', $param);
|
||||
$this->friendlyInfo($message);
|
||||
++$this->count;
|
||||
}
|
||||
|
||||
private function isForeignCurrencyTransfer(TransactionJournal $journal): bool
|
||||
{
|
||||
if (TransactionTypeEnum::TRANSFER->value !== $journal->transactionType->type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Transaction $destination */
|
||||
$destination = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
|
||||
/** @var Transaction $source */
|
||||
$source = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
|
||||
// safety catch on NULL should not be necessary, we just had that catch.
|
||||
// source amount = dest foreign amount
|
||||
// source currency = dest foreign currency
|
||||
// dest amount = source foreign currency
|
||||
// dest currency = source foreign currency
|
||||
// Log::debug(sprintf('[a] %s', bccomp(\FireflyIII\Support\Facades\Steam::positive($source->amount), \FireflyIII\Support\Facades\Steam::positive($destination->foreign_amount))));
|
||||
// Log::debug(sprintf('[b] %s', bccomp(\FireflyIII\Support\Facades\Steam::positive($destination->amount), \FireflyIII\Support\Facades\Steam::positive($source->foreign_amount))));
|
||||
// Log::debug(sprintf('[c] %s', var_export($source->transaction_currency_id === $destination->foreign_currency_id,true)));
|
||||
// Log::debug(sprintf('[d] %s', var_export((int) $destination->transaction_currency_id ===(int) $source->foreign_currency_id, true)));
|
||||
return
|
||||
0 === bccomp(Steam::positive($source->amount), Steam::positive($destination->foreign_amount))
|
||||
&& $source->transaction_currency_id === $destination->foreign_currency_id
|
||||
&& 0 === bccomp(Steam::positive($destination->amount), Steam::positive($source->foreign_amount))
|
||||
&& (int) $destination->transaction_currency_id === (int) $source->foreign_currency_id;
|
||||
}
|
||||
|
||||
private function matchCurrencies(): void
|
||||
{
|
||||
$journals = TransactionJournal::leftJoin('transactions', 'transaction_journals.id', 'transactions.transaction_journal_id')->where(
|
||||
'transactions.transaction_currency_id',
|
||||
'!=',
|
||||
DB::raw('transaction_journals.transaction_currency_id')
|
||||
)->get(['transaction_journals.*']);
|
||||
|
||||
$count = 0;
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if (!$this->isForeignCurrencyTransfer($journal) && !$this->isBetweenAssetAndLiability($journal)) {
|
||||
Transaction::where('transaction_journal_id', $journal->id)->update(['transaction_currency_id' => $journal->transaction_currency_id]);
|
||||
++$count;
|
||||
|
||||
continue;
|
||||
}
|
||||
Log::debug(sprintf('Can skip foreign currency transfer or transaction between asset and liability #%d.', $journal->id));
|
||||
}
|
||||
if (0 === $count) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->friendlyPositive(sprintf('Fixed %d journal(s) with mismatched currencies.', $journals->count()));
|
||||
}
|
||||
|
||||
private function isBetweenAssetAndLiability(TransactionJournal $journal): bool
|
||||
{
|
||||
/** @var null|Transaction $sourceTransaction */
|
||||
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
|
||||
/** @var null|Transaction $destinationTransaction */
|
||||
$destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
if (null === $sourceTransaction || null === $destinationTransaction) {
|
||||
Log::warning('Either transaction is false, stop.');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (null === $sourceTransaction->foreign_amount || null === $destinationTransaction->foreign_amount) {
|
||||
Log::warning('Either foreign amount is false, stop.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$source = $sourceTransaction->account;
|
||||
$destination = $destinationTransaction->account;
|
||||
|
||||
if (null === $source || null === $destination) {
|
||||
Log::warning('Either is false, stop.');
|
||||
|
||||
return false;
|
||||
}
|
||||
$sourceTypes = [AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
|
||||
|
||||
// source is liability, destination is asset
|
||||
if (in_array($source->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $destination->accountType->type) {
|
||||
Log::debug('Source is a liability account, destination is an asset account, return TRUE.');
|
||||
|
||||
return true;
|
||||
}
|
||||
// source is asset, destination is liability
|
||||
if (in_array($destination->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $source->accountType->type) {
|
||||
Log::debug('Destination is a liability account, source is an asset account, return TRUE.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function convertOldStyleTransactions(): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
@@ -448,4 +183,269 @@ class CorrectsUnevenAmount extends Command
|
||||
|
||||
$this->friendlyPositive(sprintf('Fixed %d journal(s) with unbalanced amounts.', $count));
|
||||
}
|
||||
|
||||
private function convertOldStyleTransfers(): void
|
||||
{
|
||||
Log::debug('convertOldStyleTransfers()');
|
||||
// select transactions with a foreign amount and a foreign currency. and it's a transfer. and they are different.
|
||||
$transactions = Transaction::distinct()
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
|
||||
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionTypeEnum::TRANSFER->value)
|
||||
->whereNotNull('foreign_currency_id')
|
||||
->whereNotNull('foreign_amount')
|
||||
->get(['transactions.transaction_journal_id'])
|
||||
;
|
||||
$count = 0;
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
/** @var null|TransactionJournal $journal */
|
||||
$journal = TransactionJournal::find($transaction->transaction_journal_id);
|
||||
if (null === $journal) {
|
||||
Log::debug('Found no journal, continue.');
|
||||
|
||||
continue;
|
||||
}
|
||||
// needs to be a transfer.
|
||||
if (TransactionTypeEnum::TRANSFER->value !== $journal->transactionType->type) {
|
||||
Log::debug('Must be a transfer, continue.');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var null|Transaction $destination */
|
||||
$destination = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
|
||||
/** @var null|Transaction $source */
|
||||
$source = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
if (null === $destination || null === $source) {
|
||||
Log::debug('Source or destination transaction is NULL, continue.');
|
||||
|
||||
// will be picked up later.
|
||||
continue;
|
||||
}
|
||||
if ($source->transaction_currency_id === $destination->transaction_currency_id) {
|
||||
Log::debug('Ready to swap data between transactions.');
|
||||
$destination->foreign_currency_id = $source->transaction_currency_id;
|
||||
$destination->foreign_amount = Steam::positive($source->amount);
|
||||
$destination->transaction_currency_id = $source->foreign_currency_id;
|
||||
$destination->amount = Steam::positive($source->foreign_amount);
|
||||
$destination->balance_dirty = true;
|
||||
$source->balance_dirty = true;
|
||||
$destination->save();
|
||||
$source->save();
|
||||
$this->friendlyWarning(sprintf('Corrected foreign amounts of transfer #%d.', $journal->id));
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
if (0 === $count) {
|
||||
return;
|
||||
}
|
||||
$this->friendlyPositive(sprintf('Fixed %d transfer(s) with unbalanced amounts.', $count));
|
||||
}
|
||||
|
||||
private function fixJournal(int $param): void
|
||||
{
|
||||
// one of the transactions is bad.
|
||||
$journal = TransactionJournal::find($param);
|
||||
if (null === $journal) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var null|Transaction $source */
|
||||
$source = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
|
||||
if (null === $source) {
|
||||
$this->friendlyError(sprintf(
|
||||
'Journal #%d ("%s") has no source transaction. It will be deleted to maintain database consistency.',
|
||||
$journal->id ?? 0,
|
||||
$journal->description ?? ''
|
||||
));
|
||||
Transaction::where('transaction_journal_id', $journal->id ?? 0)->forceDelete();
|
||||
TransactionJournal::where('id', $journal->id ?? 0)->forceDelete();
|
||||
++$this->count;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$amount = bcmul('-1', (string) $source->amount);
|
||||
|
||||
// fix amount of destination:
|
||||
/** @var null|Transaction $destination */
|
||||
$destination = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
|
||||
if (null === $destination) {
|
||||
$this->friendlyError(sprintf(
|
||||
'Journal #%d ("%s") has no destination transaction. It will be deleted to maintain database consistency.',
|
||||
$journal->id ?? 0,
|
||||
$journal->description ?? ''
|
||||
));
|
||||
|
||||
Transaction::where('transaction_journal_id', $journal->id ?? 0)->forceDelete();
|
||||
TransactionJournal::where('id', $journal->id ?? 0)->forceDelete();
|
||||
++$this->count;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// may still be able to salvage this journal if it is a transfer with foreign currency info
|
||||
if ($this->isForeignCurrencyTransfer($journal) || $this->isBetweenAssetAndLiability($journal)) {
|
||||
Log::debug(sprintf('Can skip foreign currency transfer / asset+liability transaction #%d.', $journal->id));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$message = sprintf('Sum of journal #%d is not zero, journal is broken and now fixed.', $journal->id);
|
||||
|
||||
$this->friendlyWarning($message);
|
||||
Log::warning($message);
|
||||
|
||||
$destination->amount = $amount;
|
||||
$destination->save();
|
||||
|
||||
$message = sprintf('Corrected amount in transaction journal #%d', $param);
|
||||
$this->friendlyInfo($message);
|
||||
++$this->count;
|
||||
}
|
||||
|
||||
private function fixUnevenAmounts(): void
|
||||
{
|
||||
Log::debug('fixUnevenAmounts()');
|
||||
$journals = DB::table('transactions')
|
||||
->groupBy('transaction_journal_id')
|
||||
->whereNull('deleted_at')
|
||||
->get(['transaction_journal_id', DB::raw('SUM(amount) AS the_sum')])
|
||||
;
|
||||
|
||||
/** @var stdClass $entry */
|
||||
foreach ($journals as $entry) {
|
||||
$sum = (string) $entry->the_sum;
|
||||
$sum = Steam::floatalize($sum);
|
||||
if (
|
||||
!is_numeric($sum)
|
||||
|| '' === $sum // @phpstan-ignore-line
|
||||
|| str_contains($sum, 'e')
|
||||
|| str_contains($sum, ',')
|
||||
) {
|
||||
$message = sprintf('Journal #%d has an invalid sum ("%s"). No sure what to do.', $entry->transaction_journal_id, $entry->the_sum);
|
||||
$this->friendlyWarning($message);
|
||||
Log::warning($message);
|
||||
++$this->count;
|
||||
|
||||
continue;
|
||||
}
|
||||
$res = -1;
|
||||
|
||||
try {
|
||||
$res = bccomp($sum, '0');
|
||||
} catch (ValueError $e) {
|
||||
$this->friendlyError(sprintf('Could not bccomp("%s", "0").', $sum));
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
if (0 !== $res) {
|
||||
$this->fixJournal((int) $entry->transaction_journal_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function isBetweenAssetAndLiability(TransactionJournal $journal): bool
|
||||
{
|
||||
/** @var null|Transaction $sourceTransaction */
|
||||
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
|
||||
/** @var null|Transaction $destinationTransaction */
|
||||
$destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
if (null === $sourceTransaction || null === $destinationTransaction) {
|
||||
Log::warning('Either transaction is false, stop.');
|
||||
|
||||
return false;
|
||||
}
|
||||
if (null === $sourceTransaction->foreign_amount || null === $destinationTransaction->foreign_amount) {
|
||||
Log::warning('Either foreign amount is false, stop.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$source = $sourceTransaction->account;
|
||||
$destination = $destinationTransaction->account;
|
||||
|
||||
if (null === $source || null === $destination) {
|
||||
Log::warning('Either is false, stop.');
|
||||
|
||||
return false;
|
||||
}
|
||||
$sourceTypes = [AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
|
||||
|
||||
// source is liability, destination is asset
|
||||
if (in_array($source->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $destination->accountType->type) {
|
||||
Log::debug('Source is a liability account, destination is an asset account, return TRUE.');
|
||||
|
||||
return true;
|
||||
}
|
||||
// source is asset, destination is liability
|
||||
if (in_array($destination->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $source->accountType->type) {
|
||||
Log::debug('Destination is a liability account, source is an asset account, return TRUE.');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function isForeignCurrencyTransfer(TransactionJournal $journal): bool
|
||||
{
|
||||
if (TransactionTypeEnum::TRANSFER->value !== $journal->transactionType->type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Transaction $destination */
|
||||
$destination = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
|
||||
/** @var Transaction $source */
|
||||
$source = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
|
||||
// safety catch on NULL should not be necessary, we just had that catch.
|
||||
// source amount = dest foreign amount
|
||||
// source currency = dest foreign currency
|
||||
// dest amount = source foreign currency
|
||||
// dest currency = source foreign currency
|
||||
// Log::debug(sprintf('[a] %s', bccomp(\FireflyIII\Support\Facades\Steam::positive($source->amount), \FireflyIII\Support\Facades\Steam::positive($destination->foreign_amount))));
|
||||
// Log::debug(sprintf('[b] %s', bccomp(\FireflyIII\Support\Facades\Steam::positive($destination->amount), \FireflyIII\Support\Facades\Steam::positive($source->foreign_amount))));
|
||||
// Log::debug(sprintf('[c] %s', var_export($source->transaction_currency_id === $destination->foreign_currency_id,true)));
|
||||
// Log::debug(sprintf('[d] %s', var_export((int) $destination->transaction_currency_id ===(int) $source->foreign_currency_id, true)));
|
||||
return
|
||||
0 === bccomp(Steam::positive($source->amount), Steam::positive($destination->foreign_amount))
|
||||
&& $source->transaction_currency_id === $destination->foreign_currency_id
|
||||
&& 0 === bccomp(Steam::positive($destination->amount), Steam::positive($source->foreign_amount))
|
||||
&& (int) $destination->transaction_currency_id === (int) $source->foreign_currency_id;
|
||||
}
|
||||
|
||||
private function matchCurrencies(): void
|
||||
{
|
||||
$journals = TransactionJournal::leftJoin('transactions', 'transaction_journals.id', 'transactions.transaction_journal_id')->where(
|
||||
'transactions.transaction_currency_id',
|
||||
'!=',
|
||||
DB::raw('transaction_journals.transaction_currency_id')
|
||||
)->get(['transaction_journals.*']);
|
||||
|
||||
$count = 0;
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if (!$this->isForeignCurrencyTransfer($journal) && !$this->isBetweenAssetAndLiability($journal)) {
|
||||
Transaction::where('transaction_journal_id', $journal->id)->update(['transaction_currency_id' => $journal->transaction_currency_id]);
|
||||
++$count;
|
||||
|
||||
continue;
|
||||
}
|
||||
Log::debug(sprintf('Can skip foreign currency transfer or transaction between asset and liability #%d.', $journal->id));
|
||||
}
|
||||
if (0 === $count) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->friendlyPositive(sprintf('Fixed %d journal(s) with mismatched currencies.', $journals->count()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,31 +42,6 @@ class CreatesGroupMemberships extends Command
|
||||
protected $description = 'Update group memberships';
|
||||
protected $signature = 'correction:create-group-memberships';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->createGroupMemberships();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function createGroupMemberships(): void
|
||||
{
|
||||
$users = User::get();
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
self::createGroupMembership($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO move to helper.
|
||||
*
|
||||
@@ -98,4 +73,29 @@ class CreatesGroupMemberships extends Command
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$this->createGroupMemberships();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function createGroupMemberships(): void
|
||||
{
|
||||
$users = User::get();
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
self::createGroupMembership($user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,30 @@ class RemovesEmptyJournals extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function deleteEmptyJournals(): void
|
||||
{
|
||||
$count = 0;
|
||||
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->groupBy('transaction_journals.id')
|
||||
->whereNull('transactions.transaction_journal_id')
|
||||
->get(['transaction_journals.id'])
|
||||
;
|
||||
|
||||
foreach ($set as $entry) {
|
||||
try {
|
||||
/** @var null|TransactionJournal $journal */
|
||||
$journal = TransactionJournal::find($entry->id);
|
||||
$journal?->delete();
|
||||
} catch (QueryException $e) {
|
||||
Log::info(sprintf('Could not delete entry: %s', $e->getMessage()));
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
|
||||
$this->friendlyInfo(sprintf('Deleted empty transaction journal #%d', $entry->id));
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete transactions and their journals if they have an uneven number of transactions.
|
||||
*/
|
||||
@@ -85,28 +109,4 @@ class RemovesEmptyJournals extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteEmptyJournals(): void
|
||||
{
|
||||
$count = 0;
|
||||
$set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->groupBy('transaction_journals.id')
|
||||
->whereNull('transactions.transaction_journal_id')
|
||||
->get(['transaction_journals.id'])
|
||||
;
|
||||
|
||||
foreach ($set as $entry) {
|
||||
try {
|
||||
/** @var null|TransactionJournal $journal */
|
||||
$journal = TransactionJournal::find($entry->id);
|
||||
$journal?->delete();
|
||||
} catch (QueryException $e) {
|
||||
Log::info(sprintf('Could not delete entry: %s', $e->getMessage()));
|
||||
Log::error($e->getTraceAsString());
|
||||
}
|
||||
|
||||
$this->friendlyInfo(sprintf('Deleted empty transaction journal #%d', $entry->id));
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,11 +95,19 @@ class RemovesLinksToDeletedObjects extends Command
|
||||
$this->friendlyNeutral('Validated links to deleted objects.');
|
||||
}
|
||||
|
||||
private function cleanupTags(array $tags): void
|
||||
private function cleanupBudgets(array $budgets): void
|
||||
{
|
||||
$count = DB::table('tag_transaction_journal')->whereIn('tag_id', $tags)->delete();
|
||||
$count = DB::table('budget_transaction_journal')->whereIn('budget_id', $budgets)->delete();
|
||||
if ($count > 0) {
|
||||
$this->friendlyInfo(sprintf('Removed %d old relationship(s) categories transactions and tags.', $count));
|
||||
$this->friendlyInfo(sprintf('Removed %d old relationship(s) between budgets and transactions.', $count));
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupCategories(array $categories): void
|
||||
{
|
||||
$count = DB::table('category_transaction_journal')->whereIn('category_id', $categories)->delete();
|
||||
if ($count > 0) {
|
||||
$this->friendlyInfo(sprintf('Removed %d old relationship(s) categories categories and transactions.', $count));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,19 +135,11 @@ class RemovesLinksToDeletedObjects extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupBudgets(array $budgets): void
|
||||
private function cleanupTags(array $tags): void
|
||||
{
|
||||
$count = DB::table('budget_transaction_journal')->whereIn('budget_id', $budgets)->delete();
|
||||
$count = DB::table('tag_transaction_journal')->whereIn('tag_id', $tags)->delete();
|
||||
if ($count > 0) {
|
||||
$this->friendlyInfo(sprintf('Removed %d old relationship(s) between budgets and transactions.', $count));
|
||||
}
|
||||
}
|
||||
|
||||
private function cleanupCategories(array $categories): void
|
||||
{
|
||||
$count = DB::table('category_transaction_journal')->whereIn('category_id', $categories)->delete();
|
||||
if ($count > 0) {
|
||||
$this->friendlyInfo(sprintf('Removed %d old relationship(s) categories categories and transactions.', $count));
|
||||
$this->friendlyInfo(sprintf('Removed %d old relationship(s) categories transactions and tags.', $count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,27 @@ class RemovesOrphanedTransactions extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function deleteFromOrphanedAccounts(): void
|
||||
{
|
||||
$set = Transaction::leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')->whereNotNull('accounts.deleted_at')->get(['transactions.*']);
|
||||
$count = 0;
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($set as $transaction) {
|
||||
// delete journals
|
||||
/** @var null|TransactionJournal $journal */
|
||||
$journal = TransactionJournal::find($transaction->transaction_journal_id);
|
||||
$journal?->delete();
|
||||
Transaction::where('transaction_journal_id', $transaction->transaction_journal_id)->delete();
|
||||
$this->friendlyWarning(sprintf(
|
||||
'Deleted transaction journal #%d because account #%d was already deleted.',
|
||||
$transaction->transaction_journal_id,
|
||||
$transaction->account_id
|
||||
));
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteOrphanedJournals(): void
|
||||
{
|
||||
$set = TransactionJournal::leftJoin('transaction_groups', 'transaction_journals.transaction_group_id', 'transaction_groups.id')
|
||||
@@ -111,25 +132,4 @@ class RemovesOrphanedTransactions extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function deleteFromOrphanedAccounts(): void
|
||||
{
|
||||
$set = Transaction::leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')->whereNotNull('accounts.deleted_at')->get(['transactions.*']);
|
||||
$count = 0;
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($set as $transaction) {
|
||||
// delete journals
|
||||
/** @var null|TransactionJournal $journal */
|
||||
$journal = TransactionJournal::find($transaction->transaction_journal_id);
|
||||
$journal?->delete();
|
||||
Transaction::where('transaction_journal_id', $transaction->transaction_journal_id)->delete();
|
||||
$this->friendlyWarning(sprintf(
|
||||
'Deleted transaction journal #%d because account #%d was already deleted.',
|
||||
$transaction->transaction_journal_id,
|
||||
$transaction->account_id
|
||||
));
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,26 @@ class RestoresOAuthKeys extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function generateKeys(): void
|
||||
{
|
||||
OAuthKeys::generateKeys();
|
||||
}
|
||||
|
||||
private function keysInDatabase(): bool
|
||||
{
|
||||
return OAuthKeys::keysInDatabase();
|
||||
}
|
||||
|
||||
private function keysOnDrive(): bool
|
||||
{
|
||||
return OAuthKeys::hasKeyFiles();
|
||||
}
|
||||
|
||||
private function restoreKeysFromDB(): bool
|
||||
{
|
||||
return OAuthKeys::restoreKeysFromDB();
|
||||
}
|
||||
|
||||
private function restoreOAuthKeys(): void
|
||||
{
|
||||
if (!$this->keysInDatabase() && !$this->keysOnDrive()) {
|
||||
@@ -76,28 +96,8 @@ class RestoresOAuthKeys extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function keysInDatabase(): bool
|
||||
{
|
||||
return OAuthKeys::keysInDatabase();
|
||||
}
|
||||
|
||||
private function keysOnDrive(): bool
|
||||
{
|
||||
return OAuthKeys::hasKeyFiles();
|
||||
}
|
||||
|
||||
private function generateKeys(): void
|
||||
{
|
||||
OAuthKeys::generateKeys();
|
||||
}
|
||||
|
||||
private function storeKeysInDB(): void
|
||||
{
|
||||
OAuthKeys::storeKeysInDB();
|
||||
}
|
||||
|
||||
private function restoreKeysFromDB(): bool
|
||||
{
|
||||
return OAuthKeys::restoreKeysFromDB();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,14 @@ class TriggersCreditCalculation extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function processAccount(Account $account): void
|
||||
{
|
||||
/** @var CreditRecalculateService $object */
|
||||
$object = app(CreditRecalculateService::class);
|
||||
$object->setAccount($account);
|
||||
$object->recalculate();
|
||||
}
|
||||
|
||||
private function processAccounts(): void
|
||||
{
|
||||
$accounts = Account::leftJoin('account_types', 'accounts.account_type_id', 'account_types.id')->whereIn(
|
||||
@@ -53,12 +61,4 @@ class TriggersCreditCalculation extends Command
|
||||
$this->processAccount($account);
|
||||
}
|
||||
}
|
||||
|
||||
private function processAccount(Account $account): void
|
||||
{
|
||||
/** @var CreditRecalculateService $object */
|
||||
$object = app(CreditRecalculateService::class);
|
||||
$object->setAccount($account);
|
||||
$object->recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,45 +134,55 @@ class ExportsData extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
* @throws FireflyException
|
||||
* @throws FilesystemException
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
private function exportData(array $options, array $data): void
|
||||
{
|
||||
$this->journalRepository = app(JournalRepositoryInterface::class);
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$date = Carbon::now()->format('Y_m_d');
|
||||
foreach ($data as $key => $content) {
|
||||
$file = sprintf('%s%s_%s.csv', $options['directory'], $date, $key);
|
||||
if (false === $options['force'] && file_exists($file)) {
|
||||
throw new FireflyException(sprintf('File "%s" exists already. Use --force to overwrite.', $file));
|
||||
}
|
||||
if (true === $options['force'] && file_exists($file)) {
|
||||
$this->friendlyWarning(sprintf('File "%s" exists already but will be replaced.', $file));
|
||||
}
|
||||
// continue to write to file.
|
||||
file_put_contents($file, $content);
|
||||
$this->friendlyPositive(sprintf('Wrote %s-export to file "%s".', $key, $file));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function parseOptions(): array
|
||||
private function getAccountsParameter(): Collection
|
||||
{
|
||||
$start = $this->getDateParameter('start');
|
||||
$end = $this->getDateParameter('end');
|
||||
$accounts = $this->getAccountsParameter();
|
||||
$export = $this->getExportDirectory();
|
||||
$final = new Collection();
|
||||
$accounts = new Collection();
|
||||
$accountList = (string) $this->option('accounts');
|
||||
$types = [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
|
||||
if ('' !== $accountList) {
|
||||
$accountIds = explode(',', $accountList);
|
||||
$accounts = $this->accountRepository->getAccountsById($accountIds);
|
||||
}
|
||||
if ('' === $accountList) {
|
||||
$accounts = $this->accountRepository->getAccountsByType($types);
|
||||
}
|
||||
|
||||
return [
|
||||
'export' => [
|
||||
'transactions' => $this->option('export-transactions'),
|
||||
'accounts' => $this->option('export-accounts'),
|
||||
'budgets' => $this->option('export-budgets'),
|
||||
'categories' => $this->option('export-categories'),
|
||||
'tags' => $this->option('export-tags'),
|
||||
'recurring' => $this->option('export-recurring'),
|
||||
'rules' => $this->option('export-rules'),
|
||||
'bills' => $this->option('export-subscriptions'),
|
||||
'piggies' => $this->option('export-piggies'),
|
||||
],
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'accounts' => $accounts,
|
||||
'directory' => $export,
|
||||
'force' => $this->option('force'),
|
||||
];
|
||||
// filter accounts,
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
if (in_array($account->accountType->type, $types, true)) {
|
||||
$final->push($account);
|
||||
}
|
||||
}
|
||||
if (0 === $final->count()) {
|
||||
throw new FireflyException('300007: Ended up with zero valid accounts to export from.');
|
||||
}
|
||||
|
||||
return $final;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,37 +238,6 @@ class ExportsData extends Command
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function getAccountsParameter(): Collection
|
||||
{
|
||||
$final = new Collection();
|
||||
$accounts = new Collection();
|
||||
$accountList = (string) $this->option('accounts');
|
||||
$types = [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
|
||||
if ('' !== $accountList) {
|
||||
$accountIds = explode(',', $accountList);
|
||||
$accounts = $this->accountRepository->getAccountsById($accountIds);
|
||||
}
|
||||
if ('' === $accountList) {
|
||||
$accounts = $this->accountRepository->getAccountsByType($types);
|
||||
}
|
||||
|
||||
// filter accounts,
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
if (in_array($account->accountType->type, $types, true)) {
|
||||
$final->push($account);
|
||||
}
|
||||
}
|
||||
if (0 === $final->count()) {
|
||||
throw new FireflyException('300007: Ended up with zero valid accounts to export from.');
|
||||
}
|
||||
|
||||
return $final;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -277,22 +256,43 @@ class ExportsData extends Command
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
* @throws FilesystemException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function exportData(array $options, array $data): void
|
||||
private function parseOptions(): array
|
||||
{
|
||||
$date = Carbon::now()->format('Y_m_d');
|
||||
foreach ($data as $key => $content) {
|
||||
$file = sprintf('%s%s_%s.csv', $options['directory'], $date, $key);
|
||||
if (false === $options['force'] && file_exists($file)) {
|
||||
throw new FireflyException(sprintf('File "%s" exists already. Use --force to overwrite.', $file));
|
||||
}
|
||||
if (true === $options['force'] && file_exists($file)) {
|
||||
$this->friendlyWarning(sprintf('File "%s" exists already but will be replaced.', $file));
|
||||
}
|
||||
// continue to write to file.
|
||||
file_put_contents($file, $content);
|
||||
$this->friendlyPositive(sprintf('Wrote %s-export to file "%s".', $key, $file));
|
||||
}
|
||||
$start = $this->getDateParameter('start');
|
||||
$end = $this->getDateParameter('end');
|
||||
$accounts = $this->getAccountsParameter();
|
||||
$export = $this->getExportDirectory();
|
||||
|
||||
return [
|
||||
'export' => [
|
||||
'transactions' => $this->option('export-transactions'),
|
||||
'accounts' => $this->option('export-accounts'),
|
||||
'budgets' => $this->option('export-budgets'),
|
||||
'categories' => $this->option('export-categories'),
|
||||
'tags' => $this->option('export-tags'),
|
||||
'recurring' => $this->option('export-recurring'),
|
||||
'rules' => $this->option('export-rules'),
|
||||
'bills' => $this->option('export-subscriptions'),
|
||||
'piggies' => $this->option('export-piggies'),
|
||||
],
|
||||
'start' => $start,
|
||||
'end' => $end,
|
||||
'accounts' => $accounts,
|
||||
'directory' => $export,
|
||||
'force' => $this->option('force'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->journalRepository = app(JournalRepositoryInterface::class);
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,45 @@ class ReportsEmptyObjects extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on accounts with no transactions.
|
||||
*/
|
||||
private function reportAccounts(): void
|
||||
{
|
||||
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('users', 'accounts.user_id', '=', 'users.id')
|
||||
->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
|
||||
->whereNull('transactions.account_id')
|
||||
->get(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
|
||||
;
|
||||
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$line = 'User #%d (%s) has account #%d ("%s") which has no transactions.';
|
||||
$line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $entry->name);
|
||||
$this->friendlyWarning($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on budgets with no budget limits (which makes them pointless).
|
||||
*/
|
||||
private function reportBudgetLimits(): void
|
||||
{
|
||||
$set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
|
||||
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
|
||||
->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email'])
|
||||
->whereNull('budget_limits.id')
|
||||
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email'])
|
||||
;
|
||||
|
||||
/** @var Budget $entry */
|
||||
foreach ($set as $entry) {
|
||||
$line = sprintf('User #%d (%s) has budget #%d ("%s") which has no budget limits.', $entry->user_id, $entry->email, $entry->id, $entry->name);
|
||||
$this->friendlyWarning($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report on budgets with no transactions or journals.
|
||||
*/
|
||||
@@ -110,43 +149,4 @@ class ReportsEmptyObjects extends Command
|
||||
$this->friendlyWarning($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on accounts with no transactions.
|
||||
*/
|
||||
private function reportAccounts(): void
|
||||
{
|
||||
$set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('users', 'accounts.user_id', '=', 'users.id')
|
||||
->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
|
||||
->whereNull('transactions.account_id')
|
||||
->get(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'])
|
||||
;
|
||||
|
||||
/** @var stdClass $entry */
|
||||
foreach ($set as $entry) {
|
||||
$line = 'User #%d (%s) has account #%d ("%s") which has no transactions.';
|
||||
$line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $entry->name);
|
||||
$this->friendlyWarning($line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports on budgets with no budget limits (which makes them pointless).
|
||||
*/
|
||||
private function reportBudgetLimits(): void
|
||||
{
|
||||
$set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id')
|
||||
->leftJoin('users', 'budgets.user_id', '=', 'users.id')
|
||||
->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email'])
|
||||
->whereNull('budget_limits.id')
|
||||
->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email'])
|
||||
;
|
||||
|
||||
/** @var Budget $entry */
|
||||
foreach ($set as $entry) {
|
||||
$line = sprintf('User #%d (%s) has budget #%d ("%s") which has no budget limits.', $entry->user_id, $entry->email, $entry->id, $entry->name);
|
||||
$this->friendlyWarning($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,20 @@ class ValidatesEnvironmentVariables extends Command
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function validateGuard(): bool
|
||||
{
|
||||
$guard = config('auth.defaults.guard');
|
||||
if ('web' !== $guard && 'remote_user_guard' !== $guard) {
|
||||
$this->friendlyError(sprintf('AUTHENTICATION_GUARD "%s" is not a valid guard for Firefly III.', $guard));
|
||||
$this->friendlyError('Please check your .env file and make sure you use a valid setting.');
|
||||
$this->friendlyError('Valid guards are: web, remote_user_guard');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function validateLanguage(): bool
|
||||
{
|
||||
$language = config('firefly.default_language');
|
||||
@@ -80,20 +94,6 @@ class ValidatesEnvironmentVariables extends Command
|
||||
return true;
|
||||
}
|
||||
|
||||
private function validateGuard(): bool
|
||||
{
|
||||
$guard = config('auth.defaults.guard');
|
||||
if ('web' !== $guard && 'remote_user_guard' !== $guard) {
|
||||
$this->friendlyError(sprintf('AUTHENTICATION_GUARD "%s" is not a valid guard for Firefly III.', $guard));
|
||||
$this->friendlyError('Please check your .env file and make sure you use a valid setting.');
|
||||
$this->friendlyError('Valid guards are: web, remote_user_guard');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function validateStaticToken(): bool
|
||||
{
|
||||
$token = (string) config('firefly.static_cron_token');
|
||||
|
||||
@@ -39,16 +39,16 @@ trait ShowsFriendlyMessages
|
||||
$this->friendlyNeutral($message);
|
||||
}
|
||||
|
||||
public function friendlyNeutral(string $message): void
|
||||
{
|
||||
$this->line(sprintf(' [i] %s', trim($message)));
|
||||
}
|
||||
|
||||
public function friendlyLine(string $message): void
|
||||
{
|
||||
$this->line(sprintf(' %s', trim($message)));
|
||||
}
|
||||
|
||||
public function friendlyNeutral(string $message): void
|
||||
{
|
||||
$this->line(sprintf(' [i] %s', trim($message)));
|
||||
}
|
||||
|
||||
public function friendlyPositive(string $message): void
|
||||
{
|
||||
$this->info(sprintf(' [✓] %s', trim($message)));
|
||||
|
||||
@@ -112,19 +112,53 @@ class ForcesDecimalSize extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function determineDatabaseType(): void
|
||||
/**
|
||||
* This method loops over all accounts and validates the amounts.
|
||||
*/
|
||||
private function correctAccountAmounts(TransactionCurrency $currency, array $fields): void
|
||||
{
|
||||
// switch stuff based on database connection:
|
||||
$this->operator = 'REGEXP';
|
||||
$this->regularExpression = '\'\\\.[\\\d]{%d}[1-9]+\'';
|
||||
$this->cast = 'CHAR';
|
||||
if ('pgsql' === config('database.default')) {
|
||||
$this->operator = 'SIMILAR TO';
|
||||
$this->regularExpression = '\'%%\.[\d]{%d}[1-9]+%%\'';
|
||||
$this->cast = 'TEXT';
|
||||
$operator = $this->operator;
|
||||
$cast = $this->cast;
|
||||
$regularExpression = $this->regularExpression;
|
||||
|
||||
/** @var Builder $query */
|
||||
$query = Account::leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
|
||||
->where('account_meta.name', 'currency_id')
|
||||
->where('account_meta.data', json_encode((string) $currency->id))
|
||||
;
|
||||
$query->where(static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression): void {
|
||||
foreach ($fields as $field) {
|
||||
$q->orWhere(
|
||||
DB::raw(sprintf('CAST(accounts.%s AS %s)', $field, $cast)),
|
||||
$operator,
|
||||
DB::raw(sprintf($regularExpression, $currency->decimal_places))
|
||||
);
|
||||
}
|
||||
});
|
||||
$result = $query->get(['accounts.*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->friendlyPositive(sprintf('All accounts in %s are OK', $currency->code));
|
||||
|
||||
return;
|
||||
}
|
||||
if ('sqlite' === config('database.default')) {
|
||||
$this->regularExpression = '"\.[\d]{%d}[1-9]+"';
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($result as $account) {
|
||||
/** @var string $field */
|
||||
foreach ($fields as $field) {
|
||||
$value = $account->{$field};
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = 10 ** $currency->decimal_places;
|
||||
$correct = bcdiv((string) round($value * $pow), (string) $pow, 12);
|
||||
$this->friendlyInfo(sprintf('Account #%d has %s with value "%s", this has been corrected to "%s".', $account->id, $field, $value, $correct));
|
||||
|
||||
/** @var null|Account $updateAccount */
|
||||
$updateAccount = Account::find($account->id);
|
||||
$updateAccount?->update([$field => $correct]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,56 +264,6 @@ class ForcesDecimalSize extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method loops over all accounts and validates the amounts.
|
||||
*/
|
||||
private function correctAccountAmounts(TransactionCurrency $currency, array $fields): void
|
||||
{
|
||||
$operator = $this->operator;
|
||||
$cast = $this->cast;
|
||||
$regularExpression = $this->regularExpression;
|
||||
|
||||
/** @var Builder $query */
|
||||
$query = Account::leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
|
||||
->where('account_meta.name', 'currency_id')
|
||||
->where('account_meta.data', json_encode((string) $currency->id))
|
||||
;
|
||||
$query->where(static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression): void {
|
||||
foreach ($fields as $field) {
|
||||
$q->orWhere(
|
||||
DB::raw(sprintf('CAST(accounts.%s AS %s)', $field, $cast)),
|
||||
$operator,
|
||||
DB::raw(sprintf($regularExpression, $currency->decimal_places))
|
||||
);
|
||||
}
|
||||
});
|
||||
$result = $query->get(['accounts.*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->friendlyPositive(sprintf('All accounts in %s are OK', $currency->code));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($result as $account) {
|
||||
/** @var string $field */
|
||||
foreach ($fields as $field) {
|
||||
$value = $account->{$field};
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = 10 ** $currency->decimal_places;
|
||||
$correct = bcdiv((string) round($value * $pow), (string) $pow, 12);
|
||||
$this->friendlyInfo(sprintf('Account #%d has %s with value "%s", this has been corrected to "%s".', $account->id, $field, $value, $correct));
|
||||
|
||||
/** @var null|Account $updateAccount */
|
||||
$updateAccount = Account::find($account->id);
|
||||
$updateAccount?->update([$field => $correct]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fixes all auto budgets in currency $currency.
|
||||
*/
|
||||
@@ -328,6 +312,58 @@ class ForcesDecimalSize extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fixes all piggy banks in currency $currency.
|
||||
*/
|
||||
private function correctPiggyAmounts(TransactionCurrency $currency, array $fields): void
|
||||
{
|
||||
$operator = $this->operator;
|
||||
$cast = $this->cast;
|
||||
$regularExpression = $this->regularExpression;
|
||||
|
||||
/** @var Builder $query */
|
||||
$query = PiggyBank::leftJoin('accounts', 'piggy_banks.account_id', '=', 'accounts.id')
|
||||
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
|
||||
->where('account_meta.name', 'currency_id')
|
||||
->where('account_meta.data', json_encode((string) $currency->id))
|
||||
->where(static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression): void {
|
||||
foreach ($fields as $field) {
|
||||
$q->orWhere(
|
||||
DB::raw(sprintf('CAST(piggy_banks.%s AS %s)', $field, $cast)),
|
||||
$operator,
|
||||
DB::raw(sprintf($regularExpression, $currency->decimal_places))
|
||||
);
|
||||
}
|
||||
})
|
||||
;
|
||||
|
||||
$result = $query->get(['piggy_banks.*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->friendlyPositive(sprintf('All piggy banks in %s are OK', $currency->code));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var PiggyBank $item */
|
||||
foreach ($result as $item) {
|
||||
/** @var string $field */
|
||||
foreach ($fields as $field) {
|
||||
$value = $item->{$field};
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = 10 ** $currency->decimal_places;
|
||||
$correct = bcdiv((string) round($value * $pow), (string) $pow, 12);
|
||||
$this->friendlyWarning(sprintf('Piggy bank #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct));
|
||||
|
||||
/** @var null|PiggyBank $piggyBank */
|
||||
$piggyBank = PiggyBank::find($item->id);
|
||||
$piggyBank?->update([$field => $correct]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fixes all piggy bank events in currency $currency.
|
||||
*/
|
||||
@@ -447,58 +483,6 @@ class ForcesDecimalSize extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fixes all piggy banks in currency $currency.
|
||||
*/
|
||||
private function correctPiggyAmounts(TransactionCurrency $currency, array $fields): void
|
||||
{
|
||||
$operator = $this->operator;
|
||||
$cast = $this->cast;
|
||||
$regularExpression = $this->regularExpression;
|
||||
|
||||
/** @var Builder $query */
|
||||
$query = PiggyBank::leftJoin('accounts', 'piggy_banks.account_id', '=', 'accounts.id')
|
||||
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
|
||||
->where('account_meta.name', 'currency_id')
|
||||
->where('account_meta.data', json_encode((string) $currency->id))
|
||||
->where(static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression): void {
|
||||
foreach ($fields as $field) {
|
||||
$q->orWhere(
|
||||
DB::raw(sprintf('CAST(piggy_banks.%s AS %s)', $field, $cast)),
|
||||
$operator,
|
||||
DB::raw(sprintf($regularExpression, $currency->decimal_places))
|
||||
);
|
||||
}
|
||||
})
|
||||
;
|
||||
|
||||
$result = $query->get(['piggy_banks.*']);
|
||||
if (0 === $result->count()) {
|
||||
$this->friendlyPositive(sprintf('All piggy banks in %s are OK', $currency->code));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var PiggyBank $item */
|
||||
foreach ($result as $item) {
|
||||
/** @var string $field */
|
||||
foreach ($fields as $field) {
|
||||
$value = $item->{$field};
|
||||
if (null === $value) {
|
||||
continue;
|
||||
}
|
||||
// fix $field by rounding it down correctly.
|
||||
$pow = 10 ** $currency->decimal_places;
|
||||
$correct = bcdiv((string) round($value * $pow), (string) $pow, 12);
|
||||
$this->friendlyWarning(sprintf('Piggy bank #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct));
|
||||
|
||||
/** @var null|PiggyBank $piggyBank */
|
||||
$piggyBank = PiggyBank::find($item->id);
|
||||
$piggyBank?->update([$field => $correct]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fixes all transactions in currency $currency.
|
||||
*/
|
||||
@@ -570,6 +554,22 @@ class ForcesDecimalSize extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function determineDatabaseType(): void
|
||||
{
|
||||
// switch stuff based on database connection:
|
||||
$this->operator = 'REGEXP';
|
||||
$this->regularExpression = '\'\\\.[\\\d]{%d}[1-9]+\'';
|
||||
$this->cast = 'CHAR';
|
||||
if ('pgsql' === config('database.default')) {
|
||||
$this->operator = 'SIMILAR TO';
|
||||
$this->regularExpression = '\'%%\.[\d]{%d}[1-9]+%%\'';
|
||||
$this->cast = 'TEXT';
|
||||
}
|
||||
if ('sqlite' === config('database.default')) {
|
||||
$this->regularExpression = '"\.[\d]{%d}[1-9]+"';
|
||||
}
|
||||
}
|
||||
|
||||
private function updateDecimals(): void
|
||||
{
|
||||
$this->friendlyInfo('Going to force the size of DECIMAL columns. Please hold.');
|
||||
|
||||
@@ -55,110 +55,6 @@ class OutputsInstructions extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render upgrade instructions.
|
||||
*/
|
||||
private function updateInstructions(): void
|
||||
{
|
||||
$version = (string) config('firefly.version');
|
||||
|
||||
/** @var array $config */
|
||||
$config = config('upgrade.text.upgrade');
|
||||
$text = '';
|
||||
|
||||
/** @var string $compare */
|
||||
foreach (array_keys($config) as $compare) {
|
||||
// if string starts with:
|
||||
if (str_starts_with($version, $compare)) {
|
||||
$text = (string) $config[$compare];
|
||||
}
|
||||
}
|
||||
|
||||
// validate some settings.
|
||||
if ('' === $text && 'local' === (string) config('app.env')) {
|
||||
$text = 'Please set APP_ENV=production for a safer environment.';
|
||||
}
|
||||
|
||||
$prefix = 'v';
|
||||
if (str_starts_with($version, 'develop') || str_starts_with($version, 'branch')) {
|
||||
$prefix = '';
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->showLogo();
|
||||
$this->newLine();
|
||||
$this->newLine();
|
||||
$this->showLine();
|
||||
|
||||
$this->boxed('');
|
||||
if ('' === $text) {
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, %s%s', $prefix, $version));
|
||||
$this->boxedInfo('There are no extra upgrade instructions.');
|
||||
$this->boxed('Firefly III should be ready for use.');
|
||||
$this->boxed('');
|
||||
$this->donationText();
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, %s%s!', $prefix, $version));
|
||||
$this->boxedInfo($text);
|
||||
$this->boxed('');
|
||||
$this->donationText();
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* The logo takes up 8 lines of code. So 8 colors can be used.
|
||||
*/
|
||||
private function showLogo(): void
|
||||
{
|
||||
$today = Carbon::now()->format('m-d');
|
||||
$month = Carbon::now()->format('m');
|
||||
// variation in colors and effects just because I can!
|
||||
// default is Ukraine flag:
|
||||
$colors = ['blue', 'blue', 'blue', 'yellow', 'yellow', 'yellow', 'default', 'default'];
|
||||
|
||||
// 5th of May is Dutch liberation day and 29th of April is Dutch King's Day and September 17 is my birthday.
|
||||
if ('05-01' === $today || '04-29' === $today || '09-17' === $today) {
|
||||
$colors = ['red', 'red', 'red', 'white', 'white', 'blue', 'blue', 'blue'];
|
||||
}
|
||||
|
||||
// National Coming Out Day, International Day Against Homophobia, Biphobia and Transphobia and Pride Month
|
||||
if ('10-11' === $today || '05-17' === $today || '06' === $month) {
|
||||
$colors = ['red', 'bright-red', 'yellow', 'green', 'blue', 'magenta', 'default', 'default'];
|
||||
}
|
||||
|
||||
// International Transgender Day of Visibility
|
||||
if ('03-31' === $today) {
|
||||
$colors = ['bright-blue', 'bright-red', 'white', 'white', 'bright-red', 'bright-blue', 'default', 'default'];
|
||||
}
|
||||
if ('ru_RU' === config('firefly.default_language')) {
|
||||
$colors = ['blue', 'blue', 'blue', 'yellow', 'yellow', 'yellow', 'default', 'default'];
|
||||
}
|
||||
|
||||
$this->line(sprintf('<fg=%s> ______ _ __ _ _____ _____ _____ </>', $colors[0]));
|
||||
$this->line(sprintf('<fg=%s> | ____(_) / _| | |_ _|_ _|_ _| </>', $colors[1]));
|
||||
$this->line(sprintf('<fg=%s> | |__ _ _ __ ___| |_| |_ _ | | | | | | </>', $colors[2]));
|
||||
$this->line(sprintf('<fg=%s> | __| | | \'__/ _ \ _| | | | | | | | | | | </>', $colors[3]));
|
||||
$this->line(sprintf('<fg=%s> | | | | | | __/ | | | |_| | _| |_ _| |_ _| |_ </>', $colors[4]));
|
||||
$this->line(sprintf('<fg=%s> |_| |_|_| \___|_| |_|\__, | |_____|_____|_____| </>', $colors[5]));
|
||||
$this->line(sprintf('<fg=%s> __/ | </>', $colors[6]));
|
||||
$this->line(sprintf('<fg=%s> |___/ </>', $colors[7]));
|
||||
$this->someQuote();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a line.
|
||||
*/
|
||||
private function showLine(): void
|
||||
{
|
||||
$this->line(sprintf('+%s+', str_repeat('-', 78)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a nice box.
|
||||
*/
|
||||
@@ -242,6 +138,54 @@ class OutputsInstructions extends Command
|
||||
$this->showLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a line.
|
||||
*/
|
||||
private function showLine(): void
|
||||
{
|
||||
$this->line(sprintf('+%s+', str_repeat('-', 78)));
|
||||
}
|
||||
|
||||
/**
|
||||
* The logo takes up 8 lines of code. So 8 colors can be used.
|
||||
*/
|
||||
private function showLogo(): void
|
||||
{
|
||||
$today = Carbon::now()->format('m-d');
|
||||
$month = Carbon::now()->format('m');
|
||||
// variation in colors and effects just because I can!
|
||||
// default is Ukraine flag:
|
||||
$colors = ['blue', 'blue', 'blue', 'yellow', 'yellow', 'yellow', 'default', 'default'];
|
||||
|
||||
// 5th of May is Dutch liberation day and 29th of April is Dutch King's Day and September 17 is my birthday.
|
||||
if ('05-01' === $today || '04-29' === $today || '09-17' === $today) {
|
||||
$colors = ['red', 'red', 'red', 'white', 'white', 'blue', 'blue', 'blue'];
|
||||
}
|
||||
|
||||
// National Coming Out Day, International Day Against Homophobia, Biphobia and Transphobia and Pride Month
|
||||
if ('10-11' === $today || '05-17' === $today || '06' === $month) {
|
||||
$colors = ['red', 'bright-red', 'yellow', 'green', 'blue', 'magenta', 'default', 'default'];
|
||||
}
|
||||
|
||||
// International Transgender Day of Visibility
|
||||
if ('03-31' === $today) {
|
||||
$colors = ['bright-blue', 'bright-red', 'white', 'white', 'bright-red', 'bright-blue', 'default', 'default'];
|
||||
}
|
||||
if ('ru_RU' === config('firefly.default_language')) {
|
||||
$colors = ['blue', 'blue', 'blue', 'yellow', 'yellow', 'yellow', 'default', 'default'];
|
||||
}
|
||||
|
||||
$this->line(sprintf('<fg=%s> ______ _ __ _ _____ _____ _____ </>', $colors[0]));
|
||||
$this->line(sprintf('<fg=%s> | ____(_) / _| | |_ _|_ _|_ _| </>', $colors[1]));
|
||||
$this->line(sprintf('<fg=%s> | |__ _ _ __ ___| |_| |_ _ | | | | | | </>', $colors[2]));
|
||||
$this->line(sprintf('<fg=%s> | __| | | \'__/ _ \ _| | | | | | | | | | | </>', $colors[3]));
|
||||
$this->line(sprintf('<fg=%s> | | | | | | __/ | | | |_| | _| |_ _| |_ _| |_ </>', $colors[4]));
|
||||
$this->line(sprintf('<fg=%s> |_| |_|_| \___|_| |_|\__, | |_____|_____|_____| </>', $colors[5]));
|
||||
$this->line(sprintf('<fg=%s> __/ | </>', $colors[6]));
|
||||
$this->line(sprintf('<fg=%s> |___/ </>', $colors[7]));
|
||||
$this->someQuote();
|
||||
}
|
||||
|
||||
private function someQuote(): void
|
||||
{
|
||||
$lines = [
|
||||
@@ -273,4 +217,60 @@ class OutputsInstructions extends Command
|
||||
}
|
||||
$this->line(sprintf(' %s', $lines[$random]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render upgrade instructions.
|
||||
*/
|
||||
private function updateInstructions(): void
|
||||
{
|
||||
$version = (string) config('firefly.version');
|
||||
|
||||
/** @var array $config */
|
||||
$config = config('upgrade.text.upgrade');
|
||||
$text = '';
|
||||
|
||||
/** @var string $compare */
|
||||
foreach (array_keys($config) as $compare) {
|
||||
// if string starts with:
|
||||
if (str_starts_with($version, $compare)) {
|
||||
$text = (string) $config[$compare];
|
||||
}
|
||||
}
|
||||
|
||||
// validate some settings.
|
||||
if ('' === $text && 'local' === (string) config('app.env')) {
|
||||
$text = 'Please set APP_ENV=production for a safer environment.';
|
||||
}
|
||||
|
||||
$prefix = 'v';
|
||||
if (str_starts_with($version, 'develop') || str_starts_with($version, 'branch')) {
|
||||
$prefix = '';
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->showLogo();
|
||||
$this->newLine();
|
||||
$this->newLine();
|
||||
$this->showLine();
|
||||
|
||||
$this->boxed('');
|
||||
if ('' === $text) {
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, %s%s', $prefix, $version));
|
||||
$this->boxedInfo('There are no extra upgrade instructions.');
|
||||
$this->boxed('Firefly III should be ready for use.');
|
||||
$this->boxed('');
|
||||
$this->donationText();
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->boxed(sprintf('Thank you for updating to Firefly III, %s%s!', $prefix, $version));
|
||||
$this->boxedInfo($text);
|
||||
$this->boxed('');
|
||||
$this->donationText();
|
||||
$this->boxed('');
|
||||
$this->showLine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,44 @@ class ApplyRules extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getRulesToApply(): Collection
|
||||
{
|
||||
Log::debug('getRulesToApply()');
|
||||
$rulesToApply = new Collection();
|
||||
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($this->groups as $group) {
|
||||
Log::debug(sprintf('Scanning rule group #%d', $group->id));
|
||||
$rules = $this->ruleGroupRepository->getActiveStoreRules($group);
|
||||
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
// if in rule selection, or group in selection or all rules, it's included.
|
||||
$test = $this->includeRule($rule, $group);
|
||||
if ($test) {
|
||||
Log::debug(sprintf('Will include rule #%d "%s"', $rule->id, $rule->title));
|
||||
$rulesToApply->push($rule);
|
||||
}
|
||||
if (!$test) {
|
||||
Log::debug(sprintf('Will not include rule #%d', $rule->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Found %d rules to apply.', $rulesToApply->count()));
|
||||
|
||||
return $rulesToApply;
|
||||
}
|
||||
|
||||
private function grabAllRules(): void
|
||||
{
|
||||
$this->groups = $this->ruleGroupRepository->getActiveGroups();
|
||||
}
|
||||
|
||||
private function includeRule(Rule $rule, RuleGroup $group): bool
|
||||
{
|
||||
return in_array((int) $group->id, $this->ruleGroupSelection, true) || in_array((int) $rule->id, $this->ruleSelection, true) || $this->allRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
@@ -234,6 +272,47 @@ class ApplyRules extends Command
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function verifyInputDates(): void
|
||||
{
|
||||
// parse start date.
|
||||
$inputStart = today(config('app.timezone'))->startOfMonth();
|
||||
$startString = $this->option('start_date');
|
||||
if (null === $startString) {
|
||||
/** @var JournalRepositoryInterface $repository */
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
$repository->setUser($this->getUser());
|
||||
$first = $repository->firstNull();
|
||||
if (null !== $first) {
|
||||
$inputStart = $first->date;
|
||||
}
|
||||
}
|
||||
if (null !== $startString && '' !== $startString) {
|
||||
$inputStart = Carbon::createFromFormat('Y-m-d', $startString);
|
||||
}
|
||||
|
||||
// parse end date
|
||||
$inputEnd = today(config('app.timezone'));
|
||||
$endString = $this->option('end_date');
|
||||
if (null !== $endString && '' !== $endString) {
|
||||
$inputEnd = Carbon::createFromFormat('Y-m-d', $endString);
|
||||
}
|
||||
if (!$inputEnd instanceof Carbon || null === $inputStart) {
|
||||
Log::error('Could not parse start or end date in verifyInputDate().');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($inputStart > $inputEnd) {
|
||||
[$inputEnd, $inputStart] = [$inputStart, $inputEnd];
|
||||
}
|
||||
|
||||
$this->startDate = $inputStart;
|
||||
$this->endDate = $inputEnd;
|
||||
}
|
||||
|
||||
private function verifyInputRuleGroups(): bool
|
||||
{
|
||||
$ruleGroupString = $this->option('rule_groups');
|
||||
@@ -302,83 +381,4 @@ class ApplyRules extends Command
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function verifyInputDates(): void
|
||||
{
|
||||
// parse start date.
|
||||
$inputStart = today(config('app.timezone'))->startOfMonth();
|
||||
$startString = $this->option('start_date');
|
||||
if (null === $startString) {
|
||||
/** @var JournalRepositoryInterface $repository */
|
||||
$repository = app(JournalRepositoryInterface::class);
|
||||
$repository->setUser($this->getUser());
|
||||
$first = $repository->firstNull();
|
||||
if (null !== $first) {
|
||||
$inputStart = $first->date;
|
||||
}
|
||||
}
|
||||
if (null !== $startString && '' !== $startString) {
|
||||
$inputStart = Carbon::createFromFormat('Y-m-d', $startString);
|
||||
}
|
||||
|
||||
// parse end date
|
||||
$inputEnd = today(config('app.timezone'));
|
||||
$endString = $this->option('end_date');
|
||||
if (null !== $endString && '' !== $endString) {
|
||||
$inputEnd = Carbon::createFromFormat('Y-m-d', $endString);
|
||||
}
|
||||
if (!$inputEnd instanceof Carbon || null === $inputStart) {
|
||||
Log::error('Could not parse start or end date in verifyInputDate().');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($inputStart > $inputEnd) {
|
||||
[$inputEnd, $inputStart] = [$inputStart, $inputEnd];
|
||||
}
|
||||
|
||||
$this->startDate = $inputStart;
|
||||
$this->endDate = $inputEnd;
|
||||
}
|
||||
|
||||
private function grabAllRules(): void
|
||||
{
|
||||
$this->groups = $this->ruleGroupRepository->getActiveGroups();
|
||||
}
|
||||
|
||||
private function getRulesToApply(): Collection
|
||||
{
|
||||
Log::debug('getRulesToApply()');
|
||||
$rulesToApply = new Collection();
|
||||
|
||||
/** @var RuleGroup $group */
|
||||
foreach ($this->groups as $group) {
|
||||
Log::debug(sprintf('Scanning rule group #%d', $group->id));
|
||||
$rules = $this->ruleGroupRepository->getActiveStoreRules($group);
|
||||
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
// if in rule selection, or group in selection or all rules, it's included.
|
||||
$test = $this->includeRule($rule, $group);
|
||||
if ($test) {
|
||||
Log::debug(sprintf('Will include rule #%d "%s"', $rule->id, $rule->title));
|
||||
$rulesToApply->push($rule);
|
||||
}
|
||||
if (!$test) {
|
||||
Log::debug(sprintf('Will not include rule #%d', $rule->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Found %d rules to apply.', $rulesToApply->count()));
|
||||
|
||||
return $rulesToApply;
|
||||
}
|
||||
|
||||
private function includeRule(Rule $rule, RuleGroup $group): bool
|
||||
{
|
||||
return in_array((int) $group->id, $this->ruleGroupSelection, true) || in_array((int) $rule->id, $this->ruleSelection, true) || $this->allRules;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +142,45 @@ class Cron extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function autoBudgetCronJob(bool $force, ?Carbon $date): void
|
||||
{
|
||||
$autoBudget = new AutoBudgetCronjob();
|
||||
$autoBudget->setForce($force);
|
||||
// set date in cron job:
|
||||
if ($date instanceof Carbon) {
|
||||
$autoBudget->setDate($date);
|
||||
}
|
||||
|
||||
$autoBudget->fire();
|
||||
|
||||
if ($autoBudget->jobErrored) {
|
||||
$this->friendlyError(sprintf('Error in "create auto budgets" cron: %s', $autoBudget->message));
|
||||
}
|
||||
if ($autoBudget->jobFired) {
|
||||
$this->friendlyInfo(sprintf('"Create auto budgets" cron fired: %s', $autoBudget->message));
|
||||
}
|
||||
if ($autoBudget->jobSucceeded) {
|
||||
$this->friendlyPositive(sprintf('"Create auto budgets" cron ran with success: %s', $autoBudget->message));
|
||||
}
|
||||
}
|
||||
|
||||
private function checkForUpdates(bool $force): void
|
||||
{
|
||||
$updateCheck = new UpdateCheckCronjob();
|
||||
$updateCheck->setForce($force);
|
||||
$updateCheck->fire();
|
||||
|
||||
if ($updateCheck->jobErrored) {
|
||||
$this->friendlyError(sprintf('Error in "update check" cron: %s', $updateCheck->message));
|
||||
}
|
||||
if ($updateCheck->jobFired) {
|
||||
$this->friendlyInfo(sprintf('"Update check" cron fired: %s', $updateCheck->message));
|
||||
}
|
||||
if ($updateCheck->jobSucceeded) {
|
||||
$this->friendlyPositive(sprintf('"Update check" cron ran with success: %s', $updateCheck->message));
|
||||
}
|
||||
}
|
||||
|
||||
private function exchangeRatesCronJob(bool $force, ?Carbon $date): void
|
||||
{
|
||||
Log::debug(sprintf('Created new ExchangeRateConverter in %s', __METHOD__));
|
||||
@@ -165,23 +204,6 @@ class Cron extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function checkForUpdates(bool $force): void
|
||||
{
|
||||
$updateCheck = new UpdateCheckCronjob();
|
||||
$updateCheck->setForce($force);
|
||||
$updateCheck->fire();
|
||||
|
||||
if ($updateCheck->jobErrored) {
|
||||
$this->friendlyError(sprintf('Error in "update check" cron: %s', $updateCheck->message));
|
||||
}
|
||||
if ($updateCheck->jobFired) {
|
||||
$this->friendlyInfo(sprintf('"Update check" cron fired: %s', $updateCheck->message));
|
||||
}
|
||||
if ($updateCheck->jobSucceeded) {
|
||||
$this->friendlyPositive(sprintf('"Update check" cron ran with success: %s', $updateCheck->message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -207,28 +229,6 @@ class Cron extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function autoBudgetCronJob(bool $force, ?Carbon $date): void
|
||||
{
|
||||
$autoBudget = new AutoBudgetCronjob();
|
||||
$autoBudget->setForce($force);
|
||||
// set date in cron job:
|
||||
if ($date instanceof Carbon) {
|
||||
$autoBudget->setDate($date);
|
||||
}
|
||||
|
||||
$autoBudget->fire();
|
||||
|
||||
if ($autoBudget->jobErrored) {
|
||||
$this->friendlyError(sprintf('Error in "create auto budgets" cron: %s', $autoBudget->message));
|
||||
}
|
||||
if ($autoBudget->jobFired) {
|
||||
$this->friendlyInfo(sprintf('"Create auto budgets" cron fired: %s', $autoBudget->message));
|
||||
}
|
||||
if ($autoBudget->jobSucceeded) {
|
||||
$this->friendlyPositive(sprintf('"Create auto budgets" cron ran with success: %s', $autoBudget->message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
|
||||
@@ -86,6 +86,44 @@ class AddsTransactionIdentifiers extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function findOpposing(Transaction $transaction, array $exclude): ?Transaction
|
||||
{
|
||||
// find opposing:
|
||||
$amount = bcmul($transaction->amount, '-1');
|
||||
|
||||
try {
|
||||
/** @var Transaction $opposing */
|
||||
$opposing = Transaction::where('transaction_journal_id', $transaction->transaction_journal_id)
|
||||
->where('amount', $amount)
|
||||
->where('identifier', '=', 0)
|
||||
->whereNotIn('id', $exclude)
|
||||
->first()
|
||||
;
|
||||
} catch (QueryException $e) {
|
||||
Log::error($e->getMessage());
|
||||
$this->friendlyError('Firefly III could not find the "identifier" field in the "transactions" table.');
|
||||
$this->friendlyError(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version')));
|
||||
$this->friendlyError('Please run "php artisan migrate --force" to add this field to the table.');
|
||||
$this->friendlyError('Then, run "php artisan firefly:upgrade-database" to try again.');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $opposing;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
@@ -97,13 +135,6 @@ class AddsTransactionIdentifiers extends Command
|
||||
$this->count = 0;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab all positive transactions from this journal that are not deleted. for each one, grab the negative opposing
|
||||
* one which has 0 as an identifier and give it the same identifier.
|
||||
@@ -130,35 +161,4 @@ class AddsTransactionIdentifiers extends Command
|
||||
++$identifier;
|
||||
}
|
||||
}
|
||||
|
||||
private function findOpposing(Transaction $transaction, array $exclude): ?Transaction
|
||||
{
|
||||
// find opposing:
|
||||
$amount = bcmul($transaction->amount, '-1');
|
||||
|
||||
try {
|
||||
/** @var Transaction $opposing */
|
||||
$opposing = Transaction::where('transaction_journal_id', $transaction->transaction_journal_id)
|
||||
->where('amount', $amount)
|
||||
->where('identifier', '=', 0)
|
||||
->whereNotIn('id', $exclude)
|
||||
->first()
|
||||
;
|
||||
} catch (QueryException $e) {
|
||||
Log::error($e->getMessage());
|
||||
$this->friendlyError('Firefly III could not find the "identifier" field in the "transactions" table.');
|
||||
$this->friendlyError(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version')));
|
||||
$this->friendlyError('Please run "php artisan migrate --force" to add this field to the table.');
|
||||
$this->friendlyError('Then, run "php artisan firefly:upgrade-database" to try again.');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $opposing;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,34 +75,6 @@ class RemovesDatabaseDecryption extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function decryptTable(string $table, array $fields): void
|
||||
{
|
||||
if ($this->isDecrypted($table)) {
|
||||
return;
|
||||
}
|
||||
foreach ($fields as $field) {
|
||||
$this->decryptField($table, $field);
|
||||
}
|
||||
$this->friendlyPositive(sprintf('Decrypted the data in table "%s".', $table));
|
||||
// mark as decrypted:
|
||||
$configName = sprintf('is_decrypted_%s', $table);
|
||||
FireflyConfig::set($configName, true);
|
||||
}
|
||||
|
||||
private function isDecrypted(string $table): bool
|
||||
{
|
||||
$configName = sprintf('is_decrypted_%s', $table);
|
||||
$configVar = null;
|
||||
|
||||
try {
|
||||
$configVar = FireflyConfig::get($configName, false);
|
||||
} catch (FireflyException $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function decryptField(string $table, string $field): void
|
||||
{
|
||||
$rows = DB::table($table)->get(['id', $field]);
|
||||
@@ -113,6 +85,29 @@ class RemovesDatabaseDecryption extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function decryptPreferencesRow(int $id, string $value): void
|
||||
{
|
||||
// try to json_decrypt the value.
|
||||
try {
|
||||
$newValue = json_decode($value, true, 512, JSON_THROW_ON_ERROR) ?? $value;
|
||||
} catch (JsonException $e) {
|
||||
$message = sprintf('Could not JSON decode preference row #%d: %s. This does not have to be a problem.', $id, $e->getMessage());
|
||||
$this->friendlyError($message);
|
||||
Log::warning($message);
|
||||
Log::warning($value);
|
||||
Log::warning($e->getTraceAsString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var null|Preference $object */
|
||||
$object = Preference::find($id);
|
||||
if (null !== $object) {
|
||||
$object->data = $newValue;
|
||||
$object->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function decryptRow(string $table, string $field, stdClass $row): void
|
||||
{
|
||||
$original = $row->{$field};
|
||||
@@ -143,6 +138,34 @@ class RemovesDatabaseDecryption extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function decryptTable(string $table, array $fields): void
|
||||
{
|
||||
if ($this->isDecrypted($table)) {
|
||||
return;
|
||||
}
|
||||
foreach ($fields as $field) {
|
||||
$this->decryptField($table, $field);
|
||||
}
|
||||
$this->friendlyPositive(sprintf('Decrypted the data in table "%s".', $table));
|
||||
// mark as decrypted:
|
||||
$configName = sprintf('is_decrypted_%s', $table);
|
||||
FireflyConfig::set($configName, true);
|
||||
}
|
||||
|
||||
private function isDecrypted(string $table): bool
|
||||
{
|
||||
$configName = sprintf('is_decrypted_%s', $table);
|
||||
$configVar = null;
|
||||
|
||||
try {
|
||||
$configVar = FireflyConfig::get($configName, false);
|
||||
} catch (FireflyException $e) {
|
||||
Log::error($e->getMessage());
|
||||
}
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to decrypt data. Will only throw an exception when the MAC is invalid.
|
||||
*
|
||||
@@ -164,27 +187,4 @@ class RemovesDatabaseDecryption extends Command
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function decryptPreferencesRow(int $id, string $value): void
|
||||
{
|
||||
// try to json_decrypt the value.
|
||||
try {
|
||||
$newValue = json_decode($value, true, 512, JSON_THROW_ON_ERROR) ?? $value;
|
||||
} catch (JsonException $e) {
|
||||
$message = sprintf('Could not JSON decode preference row #%d: %s. This does not have to be a problem.', $id, $e->getMessage());
|
||||
$this->friendlyError($message);
|
||||
Log::warning($message);
|
||||
Log::warning($value);
|
||||
Log::warning($e->getTraceAsString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var null|Preference $object */
|
||||
$object = Preference::find($id);
|
||||
if (null !== $object) {
|
||||
$object->data = $newValue;
|
||||
$object->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,11 @@ class RepairsAccountBalances extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function correctBalanceAmounts(): void
|
||||
{
|
||||
AccountBalanceCalculator::recalculateAll(false);
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
@@ -69,9 +74,4 @@ class RepairsAccountBalances extends Command
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function correctBalanceAmounts(): void
|
||||
{
|
||||
AccountBalanceCalculator::recalculateAll(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,18 @@ class UpgradesAccountCurrencies extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
@@ -85,35 +97,6 @@ class UpgradesAccountCurrencies extends Command
|
||||
$this->count = 0;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function updateAccountCurrencies(): void
|
||||
{
|
||||
$users = $this->userRepos->all();
|
||||
foreach ($users as $user) {
|
||||
$this->updateCurrenciesForUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateCurrenciesForUser(User $user): void
|
||||
{
|
||||
$this->accountRepos->setUser($user);
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value]);
|
||||
|
||||
// get user's currency preference:
|
||||
$primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($user->userGroup);
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->updateAccount($account, $primaryCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateAccount(Account $account, TransactionCurrency $currency): void
|
||||
{
|
||||
$this->accountRepos->setUser($account->user);
|
||||
@@ -153,8 +136,25 @@ class UpgradesAccountCurrencies extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
private function updateAccountCurrencies(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
$users = $this->userRepos->all();
|
||||
foreach ($users as $user) {
|
||||
$this->updateCurrenciesForUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateCurrenciesForUser(User $user): void
|
||||
{
|
||||
$this->accountRepos->setUser($user);
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value]);
|
||||
|
||||
// get user's currency preference:
|
||||
$primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($user->userGroup);
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->updateAccount($account, $primaryCurrency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,20 +84,6 @@ class UpgradesBillsToRules extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->userRepository = app(UserRepositoryInterface::class);
|
||||
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
|
||||
$this->billRepository = app(BillRepositoryInterface::class);
|
||||
$this->ruleRepository = app(RuleRepositoryInterface::class);
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
@@ -105,34 +91,9 @@ class UpgradesBillsToRules extends Command
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate bills to new rule structure for a specific user.
|
||||
*/
|
||||
private function migrateUser(User $user): void
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
$this->ruleGroupRepository->setUser($user);
|
||||
$this->billRepository->setUser($user);
|
||||
$this->ruleRepository->setUser($user);
|
||||
|
||||
/** @var Preference $lang */
|
||||
$lang = Preferences::getForUser($user, 'language', 'en_US');
|
||||
$language = null !== $lang->data && !is_array($lang->data) ? (string) $lang->data : 'en_US';
|
||||
$groupTitle = (string) trans('firefly.rulegroup_for_bills_title', [], $language);
|
||||
$ruleGroup = $this->ruleGroupRepository->findByTitle($groupTitle);
|
||||
|
||||
if (!$ruleGroup instanceof RuleGroup) {
|
||||
$ruleGroup = $this->ruleGroupRepository->store([
|
||||
'title' => (string) trans('firefly.rulegroup_for_bills_title', [], $language),
|
||||
'description' => (string) trans('firefly.rulegroup_for_bills_description', [], $language),
|
||||
'active' => true,
|
||||
]);
|
||||
}
|
||||
$bills = $this->billRepository->getBills();
|
||||
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
$this->migrateBill($ruleGroup, $bill, $lang);
|
||||
}
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function migrateBill(RuleGroup $ruleGroup, Bill $bill, Preference $language): void
|
||||
@@ -183,8 +144,47 @@ class UpgradesBillsToRules extends Command
|
||||
++$this->count;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
/**
|
||||
* Migrate bills to new rule structure for a specific user.
|
||||
*/
|
||||
private function migrateUser(User $user): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
$this->ruleGroupRepository->setUser($user);
|
||||
$this->billRepository->setUser($user);
|
||||
$this->ruleRepository->setUser($user);
|
||||
|
||||
/** @var Preference $lang */
|
||||
$lang = Preferences::getForUser($user, 'language', 'en_US');
|
||||
$language = null !== $lang->data && !is_array($lang->data) ? (string) $lang->data : 'en_US';
|
||||
$groupTitle = (string) trans('firefly.rulegroup_for_bills_title', [], $language);
|
||||
$ruleGroup = $this->ruleGroupRepository->findByTitle($groupTitle);
|
||||
|
||||
if (!$ruleGroup instanceof RuleGroup) {
|
||||
$ruleGroup = $this->ruleGroupRepository->store([
|
||||
'title' => (string) trans('firefly.rulegroup_for_bills_title', [], $language),
|
||||
'description' => (string) trans('firefly.rulegroup_for_bills_description', [], $language),
|
||||
'active' => true,
|
||||
]);
|
||||
}
|
||||
$bills = $this->billRepository->getBills();
|
||||
|
||||
/** @var Bill $bill */
|
||||
foreach ($bills as $bill) {
|
||||
$this->migrateBill($ruleGroup, $bill, $lang);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->userRepository = app(UserRepositoryInterface::class);
|
||||
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
|
||||
$this->billRepository = app(BillRepositoryInterface::class);
|
||||
$this->ruleRepository = app(RuleRepositoryInterface::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,23 +57,6 @@ class UpgradesBudgetLimitPeriods extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar->data;
|
||||
}
|
||||
|
||||
private function theresNoLimit(): void
|
||||
{
|
||||
$limits = BudgetLimit::whereNull('period')->get();
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($limits as $limit) {
|
||||
$this->fixLimit($limit);
|
||||
}
|
||||
}
|
||||
|
||||
private function fixLimit(BudgetLimit $limit): void
|
||||
{
|
||||
$period = $this->getLimitPeriod($limit);
|
||||
@@ -91,7 +74,7 @@ class UpgradesBudgetLimitPeriods extends Command
|
||||
return;
|
||||
}
|
||||
$limit->period = $period;
|
||||
$limit->save();
|
||||
$limit->saveQuietly();
|
||||
|
||||
$msg = sprintf(
|
||||
'Budget limit #%d (%s - %s) period is "%s".',
|
||||
@@ -155,8 +138,25 @@ class UpgradesBudgetLimitPeriods extends Command
|
||||
return null;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar->data;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function theresNoLimit(): void
|
||||
{
|
||||
$limits = BudgetLimit::whereNull('period')->get();
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($limits as $limit) {
|
||||
$this->fixLimit($limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class UpgradesBudgetLimits extends Command
|
||||
if (null !== $user) {
|
||||
$currency = Amount::getPrimaryCurrencyByUserGroup($user->userGroup);
|
||||
$budgetLimit->transaction_currency_id = $currency->id;
|
||||
$budgetLimit->save();
|
||||
$budgetLimit->saveQuietly();
|
||||
$this->friendlyInfo(sprintf(
|
||||
'Budget limit #%d (part of budget "%s") now has a currency setting (%s).',
|
||||
$budgetLimit->id,
|
||||
|
||||
@@ -64,6 +64,24 @@ class UpgradesCurrencyPreferences extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getPreference(User $user): string
|
||||
{
|
||||
$preference = Preference::where('user_id', $user->id)
|
||||
->where('name', 'currencyPreference')
|
||||
->first(['id', 'user_id', 'name', 'data', 'updated_at', 'created_at'])
|
||||
;
|
||||
|
||||
if (null === $preference) {
|
||||
return 'EUR';
|
||||
}
|
||||
|
||||
if (null !== $preference->data && !is_array($preference->data)) {
|
||||
return (string) $preference->data;
|
||||
}
|
||||
|
||||
return 'EUR';
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
@@ -71,6 +89,11 @@ class UpgradesCurrencyPreferences extends Command
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function runUpgrade(): void
|
||||
{
|
||||
$groups = UserGroup::get();
|
||||
@@ -126,27 +149,4 @@ class UpgradesCurrencyPreferences extends Command
|
||||
$user->currencies()->updateExistingPivot($primaryCurrency->id, ['user_default' => true]);
|
||||
$user->userGroup->currencies()->updateExistingPivot($primaryCurrency->id, ['group_default' => true]);
|
||||
}
|
||||
|
||||
private function getPreference(User $user): string
|
||||
{
|
||||
$preference = Preference::where('user_id', $user->id)
|
||||
->where('name', 'currencyPreference')
|
||||
->first(['id', 'user_id', 'name', 'data', 'updated_at', 'created_at'])
|
||||
;
|
||||
|
||||
if (null === $preference) {
|
||||
return 'EUR';
|
||||
}
|
||||
|
||||
if (null !== $preference->data && !is_array($preference->data)) {
|
||||
return (string) $preference->data;
|
||||
}
|
||||
|
||||
return 'EUR';
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,11 +68,48 @@ class UpgradesJournalMetaData extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function isMigrated(): bool
|
||||
private function getIdsForBudgets(): array
|
||||
{
|
||||
$configVar = FireflyConfig::get(UpgradesToGroups::CONFIG_NAME, false);
|
||||
$transactions = DB::table('budget_transaction')
|
||||
->distinct()
|
||||
->pluck('transaction_id')
|
||||
->toArray()
|
||||
;
|
||||
$array = [];
|
||||
$chunks = array_chunk($transactions, 500);
|
||||
|
||||
return (bool) $configVar->data;
|
||||
foreach ($chunks as $chunk) {
|
||||
$set = DB::table('transactions')
|
||||
->whereIn('transactions.id', $chunk)
|
||||
->pluck('transaction_journal_id')
|
||||
->toArray()
|
||||
;
|
||||
$array = array_merge($array, $set);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function getIdsForCategories(): array
|
||||
{
|
||||
$transactions = DB::table('category_transaction')
|
||||
->distinct()
|
||||
->pluck('transaction_id')
|
||||
->toArray()
|
||||
;
|
||||
$array = [];
|
||||
$chunks = array_chunk($transactions, 500);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$set = DB::table('transactions')
|
||||
->whereIn('transactions.id', $chunk)
|
||||
->pluck('transaction_journal_id')
|
||||
->toArray()
|
||||
;
|
||||
$array = array_merge($array, $set);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
@@ -82,6 +119,18 @@ class UpgradesJournalMetaData extends Command
|
||||
return (bool) $configVar->data;
|
||||
}
|
||||
|
||||
private function isMigrated(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(UpgradesToGroups::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar->data;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function migrateAll(): void
|
||||
{
|
||||
$this->migrateBudgets();
|
||||
@@ -108,28 +157,6 @@ class UpgradesJournalMetaData extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function getIdsForBudgets(): array
|
||||
{
|
||||
$transactions = DB::table('budget_transaction')
|
||||
->distinct()
|
||||
->pluck('transaction_id')
|
||||
->toArray()
|
||||
;
|
||||
$array = [];
|
||||
$chunks = array_chunk($transactions, 500);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$set = DB::table('transactions')
|
||||
->whereIn('transactions.id', $chunk)
|
||||
->pluck('transaction_journal_id')
|
||||
->toArray()
|
||||
;
|
||||
$array = array_merge($array, $set);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function migrateBudgetsForJournal(TransactionJournal $journal): void
|
||||
{
|
||||
// grab category from first transaction
|
||||
@@ -179,28 +206,6 @@ class UpgradesJournalMetaData extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function getIdsForCategories(): array
|
||||
{
|
||||
$transactions = DB::table('category_transaction')
|
||||
->distinct()
|
||||
->pluck('transaction_id')
|
||||
->toArray()
|
||||
;
|
||||
$array = [];
|
||||
$chunks = array_chunk($transactions, 500);
|
||||
|
||||
foreach ($chunks as $chunk) {
|
||||
$set = DB::table('transactions')
|
||||
->whereIn('transactions.id', $chunk)
|
||||
->pluck('transaction_journal_id')
|
||||
->toArray()
|
||||
;
|
||||
$array = array_merge($array, $set);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
private function migrateCategoriesForJournal(TransactionJournal $journal): void
|
||||
{
|
||||
// grab category from first transaction
|
||||
@@ -229,9 +234,4 @@ class UpgradesJournalMetaData extends Command
|
||||
$journal->categories()->sync([$category->id]);
|
||||
}
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,36 @@ class UpgradesLiabilities extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function correctOpeningBalance(Account $account, TransactionJournal $openingBalance): void
|
||||
{
|
||||
$source = $this->getSourceTransaction($openingBalance);
|
||||
$destination = $this->getDestinationTransaction($openingBalance);
|
||||
if (!$source instanceof Transaction || !$destination instanceof Transaction) {
|
||||
return;
|
||||
}
|
||||
// source MUST be the liability.
|
||||
if ($destination->account_id === $account->id) {
|
||||
// so if not, switch things around:
|
||||
$sourceAccountId = $source->account_id;
|
||||
$source->account_id = $destination->account_id;
|
||||
$destination->account_id = $sourceAccountId;
|
||||
$source->save();
|
||||
$destination->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function getDestinationTransaction(TransactionJournal $journal): ?Transaction
|
||||
{
|
||||
/** @var null|Transaction */
|
||||
return $journal->transactions()->where('amount', '>', 0)->first();
|
||||
}
|
||||
|
||||
private function getSourceTransaction(TransactionJournal $journal): ?Transaction
|
||||
{
|
||||
/** @var null|Transaction */
|
||||
return $journal->transactions()->where('amount', '<', 0)->first();
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
@@ -68,14 +98,9 @@ class UpgradesLiabilities extends Command
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function upgradeLiabilities(): void
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
$users = User::get();
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$this->upgradeForUser($user);
|
||||
}
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function upgradeForUser(User $user): void
|
||||
@@ -96,6 +121,16 @@ class UpgradesLiabilities extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function upgradeLiabilities(): void
|
||||
{
|
||||
$users = User::get();
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$this->upgradeForUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
private function upgradeLiability(Account $account): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
@@ -117,39 +152,4 @@ class UpgradesLiabilities extends Command
|
||||
$factory->crud($account, 'liability_direction', 'debit');
|
||||
}
|
||||
}
|
||||
|
||||
private function correctOpeningBalance(Account $account, TransactionJournal $openingBalance): void
|
||||
{
|
||||
$source = $this->getSourceTransaction($openingBalance);
|
||||
$destination = $this->getDestinationTransaction($openingBalance);
|
||||
if (!$source instanceof Transaction || !$destination instanceof Transaction) {
|
||||
return;
|
||||
}
|
||||
// source MUST be the liability.
|
||||
if ($destination->account_id === $account->id) {
|
||||
// so if not, switch things around:
|
||||
$sourceAccountId = $source->account_id;
|
||||
$source->account_id = $destination->account_id;
|
||||
$destination->account_id = $sourceAccountId;
|
||||
$source->save();
|
||||
$destination->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function getSourceTransaction(TransactionJournal $journal): ?Transaction
|
||||
{
|
||||
/** @var null|Transaction */
|
||||
return $journal->transactions()->where('amount', '<', 0)->first();
|
||||
}
|
||||
|
||||
private function getDestinationTransaction(TransactionJournal $journal): ?Transaction
|
||||
{
|
||||
/** @var null|Transaction */
|
||||
return $journal->transactions()->where('amount', '>', 0)->first();
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,59 +63,40 @@ class UpgradesLiabilitiesEight extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
private function deleteCreditTransaction(Account $account): void
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function upgradeLiabilities(): void
|
||||
{
|
||||
$users = User::get();
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$this->upgradeForUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
private function upgradeForUser(User $user): void
|
||||
{
|
||||
$accounts = $user
|
||||
->accounts()
|
||||
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', config('firefly.valid_liabilities'))
|
||||
->get(['accounts.*'])
|
||||
$liabilityType = TransactionType::whereType(TransactionTypeEnum::LIABILITY_CREDIT->value)->first();
|
||||
$liabilityJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transactions.account_id', $account->id)
|
||||
->where('transaction_journals.transaction_type_id', $liabilityType->id)
|
||||
->first(['transaction_journals.*'])
|
||||
;
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->upgradeLiability($account);
|
||||
$service = app(CreditRecalculateService::class);
|
||||
$service->setAccount($account);
|
||||
$service->recalculate();
|
||||
if (null !== $liabilityJournal && null !== $liabilityJournal->transactionGroup) {
|
||||
$group = $liabilityJournal->transactionGroup;
|
||||
$service = new TransactionGroupDestroyService();
|
||||
$service->destroy($group);
|
||||
}
|
||||
}
|
||||
|
||||
private function upgradeLiability(Account $account): void
|
||||
private function deleteTransactions(Account $account): int
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser($account->user);
|
||||
$count = 0;
|
||||
$journals = TransactionJournal::leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')->where(
|
||||
'transactions.account_id',
|
||||
$account->id
|
||||
)->get(['transaction_journals.*']);
|
||||
|
||||
$direction = $repository->getMetaValue($account, 'liability_direction');
|
||||
if ('credit' === $direction && $this->hasBadOpening($account)) {
|
||||
$this->deleteCreditTransaction($account);
|
||||
$this->reverseOpeningBalance($account);
|
||||
$this->friendlyInfo(sprintf('Corrected opening balance for liability #%d ("%s")', $account->id, $account->name));
|
||||
}
|
||||
if ('credit' === $direction) {
|
||||
$count = $this->deleteTransactions($account);
|
||||
if ($count > 0) {
|
||||
$this->friendlyInfo(sprintf('Removed %d old format transaction(s) for liability #%d ("%s")', $count, $account->id, $account->name));
|
||||
$service = app(TransactionGroupDestroyService::class);
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if (null !== $journal->transactionGroup) {
|
||||
$service->destroy($journal->transactionGroup);
|
||||
++$count;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function hasBadOpening(Account $account): bool
|
||||
@@ -142,19 +123,16 @@ class UpgradesLiabilitiesEight extends Command
|
||||
return (bool) $openingJournal->date->isSameDay($liabilityJournal->date);
|
||||
}
|
||||
|
||||
private function deleteCreditTransaction(Account $account): void
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$liabilityType = TransactionType::whereType(TransactionTypeEnum::LIABILITY_CREDIT->value)->first();
|
||||
$liabilityJournal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->where('transactions.account_id', $account->id)
|
||||
->where('transaction_journals.transaction_type_id', $liabilityType->id)
|
||||
->first(['transaction_journals.*'])
|
||||
;
|
||||
if (null !== $liabilityJournal && null !== $liabilityJournal->transactionGroup) {
|
||||
$group = $liabilityJournal->transactionGroup;
|
||||
$service = new TransactionGroupDestroyService();
|
||||
$service->destroy($group);
|
||||
}
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function reverseOpeningBalance(Account $account): void
|
||||
@@ -186,29 +164,51 @@ class UpgradesLiabilitiesEight extends Command
|
||||
Log::warning('Did not find opening balance.');
|
||||
}
|
||||
|
||||
private function deleteTransactions(Account $account): int
|
||||
private function upgradeForUser(User $user): void
|
||||
{
|
||||
$count = 0;
|
||||
$journals = TransactionJournal::leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')->where(
|
||||
'transactions.account_id',
|
||||
$account->id
|
||||
)->get(['transaction_journals.*']);
|
||||
$accounts = $user
|
||||
->accounts()
|
||||
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', config('firefly.valid_liabilities'))
|
||||
->get(['accounts.*'])
|
||||
;
|
||||
|
||||
$service = app(TransactionGroupDestroyService::class);
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$this->upgradeLiability($account);
|
||||
$service = app(CreditRecalculateService::class);
|
||||
$service->setAccount($account);
|
||||
$service->recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($journals as $journal) {
|
||||
if (null !== $journal->transactionGroup) {
|
||||
$service->destroy($journal->transactionGroup);
|
||||
++$count;
|
||||
private function upgradeLiabilities(): void
|
||||
{
|
||||
$users = User::get();
|
||||
|
||||
/** @var User $user */
|
||||
foreach ($users as $user) {
|
||||
$this->upgradeForUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
private function upgradeLiability(Account $account): void
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$repository->setUser($account->user);
|
||||
|
||||
$direction = $repository->getMetaValue($account, 'liability_direction');
|
||||
if ('credit' === $direction && $this->hasBadOpening($account)) {
|
||||
$this->deleteCreditTransaction($account);
|
||||
$this->reverseOpeningBalance($account);
|
||||
$this->friendlyInfo(sprintf('Corrected opening balance for liability #%d ("%s")', $account->id, $account->name));
|
||||
}
|
||||
if ('credit' === $direction) {
|
||||
$count = $this->deleteTransactions($account);
|
||||
if ($count > 0) {
|
||||
$this->friendlyInfo(sprintf('Removed %d old format transaction(s) for liability #%d ("%s")', $count, $account->id, $account->name));
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,17 +70,9 @@ class UpgradesMultiPiggyBanks extends Command
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function upgradePiggyBanks(): void
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
$this->repository = app(PiggyBankRepositoryInterface::class);
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$set = PiggyBank::whereNotNull('account_id')->get();
|
||||
Log::debug(sprintf('Will update %d piggy banks(s).', $set->count()));
|
||||
|
||||
/** @var PiggyBank $piggyBank */
|
||||
foreach ($set as $piggyBank) {
|
||||
$this->upgradePiggyBank($piggyBank);
|
||||
}
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function upgradePiggyBank(PiggyBank $piggyBank): void
|
||||
@@ -109,8 +101,16 @@ class UpgradesMultiPiggyBanks extends Command
|
||||
$piggyBank->piggyBankRepetitions()->delete();
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
private function upgradePiggyBanks(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
$this->repository = app(PiggyBankRepositoryInterface::class);
|
||||
$this->accountRepository = app(AccountRepositoryInterface::class);
|
||||
$set = PiggyBank::whereNotNull('account_id')->get();
|
||||
Log::debug(sprintf('Will update %d piggy banks(s).', $set->count()));
|
||||
|
||||
/** @var PiggyBank $piggyBank */
|
||||
foreach ($set as $piggyBank) {
|
||||
$this->upgradePiggyBank($piggyBank);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,18 +71,9 @@ class UpgradesRecurrenceMetaData extends Command
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function migrateMetaData(): int
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
$count = 0;
|
||||
// get all recurrence meta data:
|
||||
$collection = RecurrenceMeta::with('recurrence')->get();
|
||||
|
||||
/** @var RecurrenceMeta $meta */
|
||||
foreach ($collection as $meta) {
|
||||
$count += $this->migrateEntry($meta);
|
||||
}
|
||||
|
||||
return $count;
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function migrateEntry(RecurrenceMeta $meta): int
|
||||
@@ -109,8 +100,17 @@ class UpgradesRecurrenceMetaData extends Command
|
||||
return 1;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
private function migrateMetaData(): int
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
$count = 0;
|
||||
// get all recurrence meta data:
|
||||
$collection = RecurrenceMeta::with('recurrence')->get();
|
||||
|
||||
/** @var RecurrenceMeta $meta */
|
||||
foreach ($collection as $meta) {
|
||||
$count += $this->migrateEntry($meta);
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,11 @@ class UpgradesRuleActions extends Command
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function replaceEqualSign(): void
|
||||
{
|
||||
$count = 0;
|
||||
@@ -179,9 +184,4 @@ class UpgradesRuleActions extends Command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,11 @@ class UpgradesTagLocations extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function hasLocationDetails(Tag $tag): bool
|
||||
{
|
||||
return !in_array(null, [$tag->latitude, $tag->longitude, $tag->zoomLevel], true);
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
@@ -63,21 +68,9 @@ class UpgradesTagLocations extends Command
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function migrateTagLocations(): void
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
$tags = Tag::get();
|
||||
|
||||
/** @var Tag $tag */
|
||||
foreach ($tags as $tag) {
|
||||
if ($this->hasLocationDetails($tag)) {
|
||||
$this->migrateLocationDetails($tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function hasLocationDetails(Tag $tag): bool
|
||||
{
|
||||
return !in_array(null, [$tag->latitude, $tag->longitude, $tag->zoomLevel], true);
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function migrateLocationDetails(Tag $tag): void
|
||||
@@ -95,8 +88,15 @@ class UpgradesTagLocations extends Command
|
||||
$tag->save();
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
private function migrateTagLocations(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
$tags = Tag::get();
|
||||
|
||||
/** @var Tag $tag */
|
||||
foreach ($tags as $tag) {
|
||||
if ($this->hasLocationDetails($tag)) {
|
||||
$this->migrateLocationDetails($tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,100 +83,18 @@ class UpgradesToGroups extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->journalRepository = app(JournalRepositoryInterface::class);
|
||||
$this->service = app(JournalDestroyService::class);
|
||||
$this->groupFactory = app(TransactionGroupFactory::class);
|
||||
$this->cliRepository = app(JournalCLIRepositoryInterface::class);
|
||||
}
|
||||
$set = $journal->transactions->filter(static function (Transaction $subject) use ($transaction): bool {
|
||||
$amount = ((float) $transaction->amount * -1) === (float) $subject->amount; // intentional float
|
||||
$identifier = $transaction->identifier === $subject->identifier;
|
||||
Log::debug(sprintf('Amount the same? %s', var_export($amount, true)));
|
||||
Log::debug(sprintf('ID the same? %s', var_export($identifier, true)));
|
||||
|
||||
private function isMigrated(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
return $amount && $identifier;
|
||||
});
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function makeGroupsFromSplitJournals(): void
|
||||
{
|
||||
$splitJournals = $this->cliRepository->getSplitJournals();
|
||||
if ($splitJournals->count() > 0) {
|
||||
$this->friendlyLine(sprintf('Going to convert %d split transaction(s). Please hold..', $splitJournals->count()));
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($splitJournals as $journal) {
|
||||
$this->makeMultiGroup($journal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function makeMultiGroup(TransactionJournal $journal): void
|
||||
{
|
||||
// double check transaction count.
|
||||
if ($journal->transactions->count() <= 2) {
|
||||
Log::debug(sprintf('Will not try to convert journal #%d because it has 2 or fewer transactions.', $journal->id));
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Will now try to convert journal #%d', $journal->id));
|
||||
|
||||
$this->journalRepository->setUser($journal->user);
|
||||
$this->groupFactory->setUser($journal->user);
|
||||
$this->cliRepository->setUser($journal->user);
|
||||
|
||||
$data = [
|
||||
// mandatory fields.
|
||||
'group_title' => $journal->description,
|
||||
'transactions' => [],
|
||||
];
|
||||
$destTransactions = $this->getDestinationTransactions($journal);
|
||||
|
||||
Log::debug(sprintf('Will use %d positive transactions to create a new group.', $destTransactions->count()));
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($destTransactions as $transaction) {
|
||||
$data['transactions'][] = $this->generateTransaction($journal, $transaction);
|
||||
}
|
||||
Log::debug(sprintf('Now calling transaction journal factory (%d transactions in array)', count($data['transactions'])));
|
||||
$group = $this->groupFactory->create($data);
|
||||
Log::debug('Done calling transaction journal factory');
|
||||
|
||||
// delete the old transaction journal.
|
||||
$this->service->destroy($journal);
|
||||
|
||||
++$this->count;
|
||||
|
||||
// report on result:
|
||||
Log::debug(sprintf(
|
||||
'Migrated journal #%d into group #%d with these journals: #%s',
|
||||
$journal->id,
|
||||
$group->id,
|
||||
implode(', #', $group->transactionJournals->pluck('id')->toArray())
|
||||
));
|
||||
$this->friendlyInfo(sprintf(
|
||||
'Migrated journal #%d into group #%d with these journals: #%s',
|
||||
$journal->id,
|
||||
$group->id,
|
||||
implode(', #', $group->transactionJournals->pluck('id')->toArray())
|
||||
));
|
||||
}
|
||||
|
||||
private function getDestinationTransactions(TransactionJournal $journal): Collection
|
||||
{
|
||||
return $journal->transactions->filter(static fn (Transaction $transaction): bool => $transaction->amount > 0);
|
||||
return $set->first();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -268,18 +186,9 @@ class UpgradesToGroups extends Command
|
||||
];
|
||||
}
|
||||
|
||||
private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction
|
||||
private function getDestinationTransactions(TransactionJournal $journal): Collection
|
||||
{
|
||||
$set = $journal->transactions->filter(static function (Transaction $subject) use ($transaction): bool {
|
||||
$amount = ((float) $transaction->amount * -1) === (float) $subject->amount; // intentional float
|
||||
$identifier = $transaction->identifier === $subject->identifier;
|
||||
Log::debug(sprintf('Amount the same? %s', var_export($amount, true)));
|
||||
Log::debug(sprintf('ID the same? %s', var_export($identifier, true)));
|
||||
|
||||
return $amount && $identifier;
|
||||
});
|
||||
|
||||
return $set->first();
|
||||
return $journal->transactions->filter(static fn (Transaction $transaction): bool => $transaction->amount > 0);
|
||||
}
|
||||
|
||||
private function getTransactionBudget(Transaction $left, Transaction $right): ?int
|
||||
@@ -336,6 +245,25 @@ class UpgradesToGroups extends Command
|
||||
return null;
|
||||
}
|
||||
|
||||
private function giveGroup(array $array): void
|
||||
{
|
||||
$groupId = DB::table('transaction_groups')->insertGetId([
|
||||
'created_at' => Carbon::now()->format('Y-m-d H:i:s'),
|
||||
'updated_at' => Carbon::now()->format('Y-m-d H:i:s'),
|
||||
'title' => null,
|
||||
'user_id' => $array['user_id'],
|
||||
]);
|
||||
DB::table('transaction_journals')->where('id', $array['id'])->update(['transaction_group_id' => $groupId]);
|
||||
++$this->count;
|
||||
}
|
||||
|
||||
private function isMigrated(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives all journals without a group a group.
|
||||
*/
|
||||
@@ -354,20 +282,92 @@ class UpgradesToGroups extends Command
|
||||
}
|
||||
}
|
||||
|
||||
private function giveGroup(array $array): void
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function makeGroupsFromSplitJournals(): void
|
||||
{
|
||||
$groupId = DB::table('transaction_groups')->insertGetId([
|
||||
'created_at' => Carbon::now()->format('Y-m-d H:i:s'),
|
||||
'updated_at' => Carbon::now()->format('Y-m-d H:i:s'),
|
||||
'title' => null,
|
||||
'user_id' => $array['user_id'],
|
||||
]);
|
||||
DB::table('transaction_journals')->where('id', $array['id'])->update(['transaction_group_id' => $groupId]);
|
||||
$splitJournals = $this->cliRepository->getSplitJournals();
|
||||
if ($splitJournals->count() > 0) {
|
||||
$this->friendlyLine(sprintf('Going to convert %d split transaction(s). Please hold..', $splitJournals->count()));
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($splitJournals as $journal) {
|
||||
$this->makeMultiGroup($journal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function makeMultiGroup(TransactionJournal $journal): void
|
||||
{
|
||||
// double check transaction count.
|
||||
if ($journal->transactions->count() <= 2) {
|
||||
Log::debug(sprintf('Will not try to convert journal #%d because it has 2 or fewer transactions.', $journal->id));
|
||||
|
||||
return;
|
||||
}
|
||||
Log::debug(sprintf('Will now try to convert journal #%d', $journal->id));
|
||||
|
||||
$this->journalRepository->setUser($journal->user);
|
||||
$this->groupFactory->setUser($journal->user);
|
||||
$this->cliRepository->setUser($journal->user);
|
||||
|
||||
$data = [
|
||||
// mandatory fields.
|
||||
'group_title' => $journal->description,
|
||||
'transactions' => [],
|
||||
];
|
||||
$destTransactions = $this->getDestinationTransactions($journal);
|
||||
|
||||
Log::debug(sprintf('Will use %d positive transactions to create a new group.', $destTransactions->count()));
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($destTransactions as $transaction) {
|
||||
$data['transactions'][] = $this->generateTransaction($journal, $transaction);
|
||||
}
|
||||
Log::debug(sprintf('Now calling transaction journal factory (%d transactions in array)', count($data['transactions'])));
|
||||
$group = $this->groupFactory->create($data);
|
||||
Log::debug('Done calling transaction journal factory');
|
||||
|
||||
// delete the old transaction journal.
|
||||
$this->service->destroy($journal);
|
||||
|
||||
++$this->count;
|
||||
|
||||
// report on result:
|
||||
Log::debug(sprintf(
|
||||
'Migrated journal #%d into group #%d with these journals: #%s',
|
||||
$journal->id,
|
||||
$group->id,
|
||||
implode(', #', $group->transactionJournals->pluck('id')->toArray())
|
||||
));
|
||||
$this->friendlyInfo(sprintf(
|
||||
'Migrated journal #%d into group #%d with these journals: #%s',
|
||||
$journal->id,
|
||||
$group->id,
|
||||
implode(', #', $group->transactionJournals->pluck('id')->toArray())
|
||||
));
|
||||
}
|
||||
|
||||
private function markAsMigrated(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->journalRepository = app(JournalRepositoryInterface::class);
|
||||
$this->service = app(JournalDestroyService::class);
|
||||
$this->groupFactory = app(TransactionGroupFactory::class);
|
||||
$this->cliRepository = app(JournalCLIRepositoryInterface::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,17 +79,303 @@ class UpgradesTransferCurrencies extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
* The destination transaction must have the correct currency. If not, it will be set by
|
||||
* taking it from the destination account's preference.
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
private function fixDestinationUnmatchedCurrency(): void
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->accountRepos = app(AccountRepositoryInterface::class);
|
||||
$this->cliRepos = app(JournalCLIRepositoryInterface::class);
|
||||
$this->accountCurrencies = [];
|
||||
$this->resetInformation();
|
||||
if (
|
||||
$this->destinationCurrency instanceof TransactionCurrency
|
||||
&& null === $this->destinationTransaction->foreign_amount
|
||||
&& (int) $this->destinationTransaction->transaction_currency_id !== $this->destinationCurrency->id
|
||||
) {
|
||||
$message = sprintf(
|
||||
'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
|
||||
$this->destinationTransaction->id,
|
||||
$this->destinationTransaction->transaction_currency_id,
|
||||
$this->destinationAccount->id,
|
||||
$this->destinationTransaction->amount
|
||||
);
|
||||
$this->friendlyWarning($message);
|
||||
++$this->count;
|
||||
$this->destinationTransaction->transaction_currency_id = $this->destinationCurrency->id;
|
||||
$this->destinationTransaction->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The destination transaction must have a currency. If not, it will be added by
|
||||
* taking it from the destination account's preference.
|
||||
*/
|
||||
private function fixDestNoCurrency(): void
|
||||
{
|
||||
if (null === $this->destinationTransaction->transaction_currency_id && $this->destinationCurrency instanceof TransactionCurrency) {
|
||||
$this->destinationTransaction->transaction_currency_id = $this->destinationCurrency->id;
|
||||
$message = sprintf(
|
||||
'Transaction #%d has no currency setting, now set to %s.',
|
||||
$this->destinationTransaction->id,
|
||||
$this->destinationCurrency->code
|
||||
);
|
||||
$this->friendlyInfo($message);
|
||||
++$this->count;
|
||||
$this->destinationTransaction->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the foreign amount of the destination transaction is null, but that of the other isn't, use this piece of code
|
||||
* to restore it.
|
||||
*/
|
||||
private function fixDestNullForeignAmount(): void
|
||||
{
|
||||
if (null === $this->destinationTransaction->foreign_amount && null !== $this->sourceTransaction->foreign_amount) {
|
||||
$this->destinationTransaction->foreign_amount = bcmul($this->sourceTransaction->foreign_amount, '-1');
|
||||
$this->destinationTransaction->save();
|
||||
++$this->count;
|
||||
$this->friendlyInfo(sprintf(
|
||||
'Restored foreign amount of destination transaction #%d to %s',
|
||||
$this->destinationTransaction->id,
|
||||
$this->destinationTransaction->foreign_amount
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the destination account currency is the same as the source currency,
|
||||
* both foreign_amount and foreign_currency_id fields must be NULL
|
||||
* for both transactions (because foreign currency info would not make sense)
|
||||
*/
|
||||
private function fixInvalidForeignCurrency(): void
|
||||
{
|
||||
if ($this->destinationCurrency->id === $this->sourceCurrency->id) {
|
||||
// update both transactions to match:
|
||||
$this->sourceTransaction->foreign_amount = null;
|
||||
$this->sourceTransaction->foreign_currency_id = null;
|
||||
|
||||
$this->destinationTransaction->foreign_amount = null;
|
||||
$this->destinationTransaction->foreign_currency_id = null;
|
||||
|
||||
$this->sourceTransaction->save();
|
||||
$this->destinationTransaction->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If destination account currency is different from source account currency,
|
||||
* then both transactions must get the source account's currency as normal currency
|
||||
* and the opposing account's currency as foreign currency.
|
||||
*/
|
||||
private function fixMismatchedForeignCurrency(): void
|
||||
{
|
||||
if ($this->sourceCurrency->id !== $this->destinationCurrency->id) {
|
||||
$this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id;
|
||||
$this->sourceTransaction->foreign_currency_id = $this->destinationCurrency->id;
|
||||
$this->destinationTransaction->transaction_currency_id = $this->sourceCurrency->id;
|
||||
$this->destinationTransaction->foreign_currency_id = $this->destinationCurrency->id;
|
||||
|
||||
$this->sourceTransaction->save();
|
||||
$this->destinationTransaction->save();
|
||||
++$this->count;
|
||||
$this->friendlyInfo(sprintf(
|
||||
'Verified foreign currency ID of transaction #%d and #%d',
|
||||
$this->sourceTransaction->id,
|
||||
$this->destinationTransaction->id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The source transaction must have a currency. If not, it will be added by
|
||||
* taking it from the source account's preference.
|
||||
*/
|
||||
private function fixSourceNoCurrency(): void
|
||||
{
|
||||
if (null === $this->sourceTransaction->transaction_currency_id && $this->sourceCurrency instanceof TransactionCurrency) {
|
||||
$this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id;
|
||||
$message = sprintf(
|
||||
'Transaction #%d has no currency setting, now set to %s.',
|
||||
$this->sourceTransaction->id,
|
||||
$this->sourceCurrency->code
|
||||
);
|
||||
$this->friendlyInfo($message);
|
||||
++$this->count;
|
||||
$this->sourceTransaction->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the foreign amount of the source transaction is null, but that of the other isn't, use this piece of code
|
||||
* to restore it.
|
||||
*/
|
||||
private function fixSourceNullForeignAmount(): void
|
||||
{
|
||||
if (null === $this->sourceTransaction->foreign_amount && null !== $this->destinationTransaction->foreign_amount) {
|
||||
$this->sourceTransaction->foreign_amount = bcmul($this->destinationTransaction->foreign_amount, '-1');
|
||||
$this->sourceTransaction->save();
|
||||
++$this->count;
|
||||
$this->friendlyInfo(sprintf(
|
||||
'Restored foreign amount of source transaction #%d to %s',
|
||||
$this->sourceTransaction->id,
|
||||
$this->sourceTransaction->foreign_amount
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The source transaction must have the correct currency. If not, it will be set by
|
||||
* taking it from the source account's preference.
|
||||
*/
|
||||
private function fixSourceUnmatchedCurrency(): void
|
||||
{
|
||||
if (
|
||||
$this->sourceCurrency instanceof TransactionCurrency
|
||||
&& null === $this->sourceTransaction->foreign_amount
|
||||
&& (int) $this->sourceTransaction->transaction_currency_id !== $this->sourceCurrency->id
|
||||
) {
|
||||
$message = sprintf(
|
||||
'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
|
||||
$this->sourceTransaction->id,
|
||||
$this->sourceTransaction->transaction_currency_id,
|
||||
$this->sourceAccount->id,
|
||||
$this->sourceTransaction->amount
|
||||
);
|
||||
$this->friendlyWarning($message);
|
||||
++$this->count;
|
||||
$this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id;
|
||||
$this->sourceTransaction->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method makes sure that the transaction journal uses the currency given in the source transaction.
|
||||
*/
|
||||
private function fixTransactionJournalCurrency(TransactionJournal $journal): void
|
||||
{
|
||||
if ((int) $journal->transaction_currency_id !== $this->sourceCurrency->id) {
|
||||
$oldCurrencyCode = $journal->transactionCurrency->code ?? '(nothing)';
|
||||
$journal->transaction_currency_id = $this->sourceCurrency->id;
|
||||
$message = sprintf(
|
||||
'Transfer #%d ("%s") has been updated to use %s instead of %s.',
|
||||
$journal->id,
|
||||
$journal->description,
|
||||
$this->sourceCurrency->code,
|
||||
$oldCurrencyCode
|
||||
);
|
||||
++$this->count;
|
||||
$this->friendlyInfo($message);
|
||||
$journal->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function getCurrency(Account $account): ?TransactionCurrency
|
||||
{
|
||||
$accountId = $account->id;
|
||||
if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) {
|
||||
return null;
|
||||
}
|
||||
if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) {
|
||||
return $this->accountCurrencies[$accountId];
|
||||
}
|
||||
$currency = $this->accountRepos->getAccountCurrency($account);
|
||||
if (!$currency instanceof TransactionCurrency) {
|
||||
$this->accountCurrencies[$accountId] = 0;
|
||||
|
||||
return null;
|
||||
}
|
||||
$this->accountCurrencies[$accountId] = $currency;
|
||||
|
||||
return $currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract destination transaction, destination account + destination account currency from the journal.
|
||||
*/
|
||||
private function getDestinationInformation(TransactionJournal $journal): void
|
||||
{
|
||||
$this->destinationTransaction = $this->getDestinationTransaction($journal);
|
||||
$this->destinationAccount = $this->destinationTransaction?->account;
|
||||
$this->destinationCurrency = $this->destinationAccount instanceof Account ? $this->getCurrency($this->destinationAccount) : null;
|
||||
}
|
||||
|
||||
private function getDestinationTransaction(TransactionJournal $transfer): ?Transaction
|
||||
{
|
||||
/** @var null|Transaction */
|
||||
return $transfer->transactions()->where('amount', '>', 0)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract source transaction, source account + source account currency from the journal.
|
||||
*/
|
||||
private function getSourceInformation(TransactionJournal $journal): void
|
||||
{
|
||||
$this->sourceTransaction = $this->getSourceTransaction($journal);
|
||||
$this->sourceAccount = $this->sourceTransaction?->account;
|
||||
$this->sourceCurrency = $this->sourceAccount instanceof Account ? $this->getCurrency($this->sourceAccount) : null;
|
||||
}
|
||||
|
||||
private function getSourceTransaction(TransactionJournal $transfer): ?Transaction
|
||||
{
|
||||
/** @var null|Transaction */
|
||||
return $transfer->transactions()->where('amount', '<', 0)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is either the source or destination transaction NULL?
|
||||
*/
|
||||
private function isEmptyTransactions(): bool
|
||||
{
|
||||
return
|
||||
!$this->sourceTransaction instanceof Transaction
|
||||
|| !$this->destinationTransaction instanceof Transaction
|
||||
|| !$this->sourceAccount instanceof Account
|
||||
|| !$this->destinationAccount instanceof Account;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function isNoCurrencyPresent(): bool
|
||||
{
|
||||
// source account must have a currency preference.
|
||||
if (!$this->sourceCurrency instanceof TransactionCurrency) {
|
||||
$message = sprintf('Account #%d ("%s") must have currency preference but has none.', $this->sourceAccount->id, $this->sourceAccount->name);
|
||||
Log::error($message);
|
||||
$this->friendlyError($message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// destination account must have a currency preference.
|
||||
if (!$this->destinationCurrency instanceof TransactionCurrency) {
|
||||
$message = sprintf(
|
||||
'Account #%d ("%s") must have currency preference but has none.',
|
||||
$this->destinationAccount->id,
|
||||
$this->destinationAccount->name
|
||||
);
|
||||
Log::error($message);
|
||||
$this->friendlyError($message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a split transaction journal?
|
||||
*/
|
||||
private function isSplitJournal(TransactionJournal $transfer): bool
|
||||
{
|
||||
return $transfer->transactions->count() > 2;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,13 +391,6 @@ class UpgradesTransferCurrencies extends Command
|
||||
$this->destinationCurrency = null;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine verifies that transfers have the correct currency settings for the accounts they are linked to.
|
||||
* For transfers, this is can be a destructive routine since we FORCE them into a currency setting whether they
|
||||
@@ -130,6 +409,20 @@ class UpgradesTransferCurrencies extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
* be called from the handle method instead of using the constructor to initialize the command.
|
||||
*/
|
||||
private function stupidLaravel(): void
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->accountRepos = app(AccountRepositoryInterface::class);
|
||||
$this->cliRepos = app(JournalCLIRepositoryInterface::class);
|
||||
$this->accountCurrencies = [];
|
||||
$this->resetInformation();
|
||||
}
|
||||
|
||||
private function updateTransferCurrency(TransactionJournal $transfer): void
|
||||
{
|
||||
$this->resetInformation();
|
||||
@@ -187,297 +480,4 @@ class UpgradesTransferCurrencies extends Command
|
||||
// fix journal itself:
|
||||
$this->fixTransactionJournalCurrency($transfer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a split transaction journal?
|
||||
*/
|
||||
private function isSplitJournal(TransactionJournal $transfer): bool
|
||||
{
|
||||
return $transfer->transactions->count() > 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract source transaction, source account + source account currency from the journal.
|
||||
*/
|
||||
private function getSourceInformation(TransactionJournal $journal): void
|
||||
{
|
||||
$this->sourceTransaction = $this->getSourceTransaction($journal);
|
||||
$this->sourceAccount = $this->sourceTransaction?->account;
|
||||
$this->sourceCurrency = $this->sourceAccount instanceof Account ? $this->getCurrency($this->sourceAccount) : null;
|
||||
}
|
||||
|
||||
private function getSourceTransaction(TransactionJournal $transfer): ?Transaction
|
||||
{
|
||||
/** @var null|Transaction */
|
||||
return $transfer->transactions()->where('amount', '<', 0)->first();
|
||||
}
|
||||
|
||||
private function getCurrency(Account $account): ?TransactionCurrency
|
||||
{
|
||||
$accountId = $account->id;
|
||||
if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) {
|
||||
return null;
|
||||
}
|
||||
if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) {
|
||||
return $this->accountCurrencies[$accountId];
|
||||
}
|
||||
$currency = $this->accountRepos->getAccountCurrency($account);
|
||||
if (!$currency instanceof TransactionCurrency) {
|
||||
$this->accountCurrencies[$accountId] = 0;
|
||||
|
||||
return null;
|
||||
}
|
||||
$this->accountCurrencies[$accountId] = $currency;
|
||||
|
||||
return $currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract destination transaction, destination account + destination account currency from the journal.
|
||||
*/
|
||||
private function getDestinationInformation(TransactionJournal $journal): void
|
||||
{
|
||||
$this->destinationTransaction = $this->getDestinationTransaction($journal);
|
||||
$this->destinationAccount = $this->destinationTransaction?->account;
|
||||
$this->destinationCurrency = $this->destinationAccount instanceof Account ? $this->getCurrency($this->destinationAccount) : null;
|
||||
}
|
||||
|
||||
private function getDestinationTransaction(TransactionJournal $transfer): ?Transaction
|
||||
{
|
||||
/** @var null|Transaction */
|
||||
return $transfer->transactions()->where('amount', '>', 0)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is either the source or destination transaction NULL?
|
||||
*/
|
||||
private function isEmptyTransactions(): bool
|
||||
{
|
||||
return
|
||||
!$this->sourceTransaction instanceof Transaction
|
||||
|| !$this->destinationTransaction instanceof Transaction
|
||||
|| !$this->sourceAccount instanceof Account
|
||||
|| !$this->destinationAccount instanceof Account;
|
||||
}
|
||||
|
||||
private function isNoCurrencyPresent(): bool
|
||||
{
|
||||
// source account must have a currency preference.
|
||||
if (!$this->sourceCurrency instanceof TransactionCurrency) {
|
||||
$message = sprintf('Account #%d ("%s") must have currency preference but has none.', $this->sourceAccount->id, $this->sourceAccount->name);
|
||||
Log::error($message);
|
||||
$this->friendlyError($message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// destination account must have a currency preference.
|
||||
if (!$this->destinationCurrency instanceof TransactionCurrency) {
|
||||
$message = sprintf(
|
||||
'Account #%d ("%s") must have currency preference but has none.',
|
||||
$this->destinationAccount->id,
|
||||
$this->destinationAccount->name
|
||||
);
|
||||
Log::error($message);
|
||||
$this->friendlyError($message);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The source transaction must have a currency. If not, it will be added by
|
||||
* taking it from the source account's preference.
|
||||
*/
|
||||
private function fixSourceNoCurrency(): void
|
||||
{
|
||||
if (null === $this->sourceTransaction->transaction_currency_id && $this->sourceCurrency instanceof TransactionCurrency) {
|
||||
$this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id;
|
||||
$message = sprintf(
|
||||
'Transaction #%d has no currency setting, now set to %s.',
|
||||
$this->sourceTransaction->id,
|
||||
$this->sourceCurrency->code
|
||||
);
|
||||
$this->friendlyInfo($message);
|
||||
++$this->count;
|
||||
$this->sourceTransaction->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The source transaction must have the correct currency. If not, it will be set by
|
||||
* taking it from the source account's preference.
|
||||
*/
|
||||
private function fixSourceUnmatchedCurrency(): void
|
||||
{
|
||||
if (
|
||||
$this->sourceCurrency instanceof TransactionCurrency
|
||||
&& null === $this->sourceTransaction->foreign_amount
|
||||
&& (int) $this->sourceTransaction->transaction_currency_id !== $this->sourceCurrency->id
|
||||
) {
|
||||
$message = sprintf(
|
||||
'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
|
||||
$this->sourceTransaction->id,
|
||||
$this->sourceTransaction->transaction_currency_id,
|
||||
$this->sourceAccount->id,
|
||||
$this->sourceTransaction->amount
|
||||
);
|
||||
$this->friendlyWarning($message);
|
||||
++$this->count;
|
||||
$this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id;
|
||||
$this->sourceTransaction->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The destination transaction must have a currency. If not, it will be added by
|
||||
* taking it from the destination account's preference.
|
||||
*/
|
||||
private function fixDestNoCurrency(): void
|
||||
{
|
||||
if (null === $this->destinationTransaction->transaction_currency_id && $this->destinationCurrency instanceof TransactionCurrency) {
|
||||
$this->destinationTransaction->transaction_currency_id = $this->destinationCurrency->id;
|
||||
$message = sprintf(
|
||||
'Transaction #%d has no currency setting, now set to %s.',
|
||||
$this->destinationTransaction->id,
|
||||
$this->destinationCurrency->code
|
||||
);
|
||||
$this->friendlyInfo($message);
|
||||
++$this->count;
|
||||
$this->destinationTransaction->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The destination transaction must have the correct currency. If not, it will be set by
|
||||
* taking it from the destination account's preference.
|
||||
*/
|
||||
private function fixDestinationUnmatchedCurrency(): void
|
||||
{
|
||||
if (
|
||||
$this->destinationCurrency instanceof TransactionCurrency
|
||||
&& null === $this->destinationTransaction->foreign_amount
|
||||
&& (int) $this->destinationTransaction->transaction_currency_id !== $this->destinationCurrency->id
|
||||
) {
|
||||
$message = sprintf(
|
||||
'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.',
|
||||
$this->destinationTransaction->id,
|
||||
$this->destinationTransaction->transaction_currency_id,
|
||||
$this->destinationAccount->id,
|
||||
$this->destinationTransaction->amount
|
||||
);
|
||||
$this->friendlyWarning($message);
|
||||
++$this->count;
|
||||
$this->destinationTransaction->transaction_currency_id = $this->destinationCurrency->id;
|
||||
$this->destinationTransaction->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the destination account currency is the same as the source currency,
|
||||
* both foreign_amount and foreign_currency_id fields must be NULL
|
||||
* for both transactions (because foreign currency info would not make sense)
|
||||
*/
|
||||
private function fixInvalidForeignCurrency(): void
|
||||
{
|
||||
if ($this->destinationCurrency->id === $this->sourceCurrency->id) {
|
||||
// update both transactions to match:
|
||||
$this->sourceTransaction->foreign_amount = null;
|
||||
$this->sourceTransaction->foreign_currency_id = null;
|
||||
|
||||
$this->destinationTransaction->foreign_amount = null;
|
||||
$this->destinationTransaction->foreign_currency_id = null;
|
||||
|
||||
$this->sourceTransaction->save();
|
||||
$this->destinationTransaction->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If destination account currency is different from source account currency,
|
||||
* then both transactions must get the source account's currency as normal currency
|
||||
* and the opposing account's currency as foreign currency.
|
||||
*/
|
||||
private function fixMismatchedForeignCurrency(): void
|
||||
{
|
||||
if ($this->sourceCurrency->id !== $this->destinationCurrency->id) {
|
||||
$this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id;
|
||||
$this->sourceTransaction->foreign_currency_id = $this->destinationCurrency->id;
|
||||
$this->destinationTransaction->transaction_currency_id = $this->sourceCurrency->id;
|
||||
$this->destinationTransaction->foreign_currency_id = $this->destinationCurrency->id;
|
||||
|
||||
$this->sourceTransaction->save();
|
||||
$this->destinationTransaction->save();
|
||||
++$this->count;
|
||||
$this->friendlyInfo(sprintf(
|
||||
'Verified foreign currency ID of transaction #%d and #%d',
|
||||
$this->sourceTransaction->id,
|
||||
$this->destinationTransaction->id
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the foreign amount of the source transaction is null, but that of the other isn't, use this piece of code
|
||||
* to restore it.
|
||||
*/
|
||||
private function fixSourceNullForeignAmount(): void
|
||||
{
|
||||
if (null === $this->sourceTransaction->foreign_amount && null !== $this->destinationTransaction->foreign_amount) {
|
||||
$this->sourceTransaction->foreign_amount = bcmul($this->destinationTransaction->foreign_amount, '-1');
|
||||
$this->sourceTransaction->save();
|
||||
++$this->count;
|
||||
$this->friendlyInfo(sprintf(
|
||||
'Restored foreign amount of source transaction #%d to %s',
|
||||
$this->sourceTransaction->id,
|
||||
$this->sourceTransaction->foreign_amount
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the foreign amount of the destination transaction is null, but that of the other isn't, use this piece of code
|
||||
* to restore it.
|
||||
*/
|
||||
private function fixDestNullForeignAmount(): void
|
||||
{
|
||||
if (null === $this->destinationTransaction->foreign_amount && null !== $this->sourceTransaction->foreign_amount) {
|
||||
$this->destinationTransaction->foreign_amount = bcmul($this->sourceTransaction->foreign_amount, '-1');
|
||||
$this->destinationTransaction->save();
|
||||
++$this->count;
|
||||
$this->friendlyInfo(sprintf(
|
||||
'Restored foreign amount of destination transaction #%d to %s',
|
||||
$this->destinationTransaction->id,
|
||||
$this->destinationTransaction->foreign_amount
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method makes sure that the transaction journal uses the currency given in the source transaction.
|
||||
*/
|
||||
private function fixTransactionJournalCurrency(TransactionJournal $journal): void
|
||||
{
|
||||
if ((int) $journal->transaction_currency_id !== $this->sourceCurrency->id) {
|
||||
$oldCurrencyCode = $journal->transactionCurrency->code ?? '(nothing)';
|
||||
$journal->transaction_currency_id = $this->sourceCurrency->id;
|
||||
$message = sprintf(
|
||||
'Transfer #%d ("%s") has been updated to use %s instead of %s.',
|
||||
$journal->id,
|
||||
$journal->description,
|
||||
$this->sourceCurrency->code,
|
||||
$oldCurrencyCode
|
||||
);
|
||||
++$this->count;
|
||||
$this->friendlyInfo($message);
|
||||
$journal->save();
|
||||
}
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,99 @@ class UpgradesVariousCurrencyInformation extends Command
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getCurrency(Account $account): ?TransactionCurrency
|
||||
{
|
||||
$accountId = $account->id;
|
||||
if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) {
|
||||
return null;
|
||||
}
|
||||
if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) {
|
||||
return $this->accountCurrencies[$accountId];
|
||||
}
|
||||
$currency = $this->accountRepos->getAccountCurrency($account);
|
||||
if (!$currency instanceof TransactionCurrency) {
|
||||
$this->accountCurrencies[$accountId] = 0;
|
||||
|
||||
return null;
|
||||
}
|
||||
$this->accountCurrencies[$accountId] = $currency;
|
||||
|
||||
return $currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the transaction that determines the transaction that "leads" and will determine
|
||||
* the currency to be used by all transactions, and the journal itself.
|
||||
*/
|
||||
private function getLeadTransaction(TransactionJournal $journal): ?Transaction
|
||||
{
|
||||
/** @var null|Transaction $lead */
|
||||
$lead = null;
|
||||
|
||||
switch ($journal->transactionType->type) {
|
||||
default:
|
||||
break;
|
||||
|
||||
case TransactionTypeEnum::WITHDRAWAL->value:
|
||||
$lead = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
|
||||
break;
|
||||
|
||||
case TransactionTypeEnum::DEPOSIT->value:
|
||||
$lead = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
|
||||
break;
|
||||
|
||||
case TransactionTypeEnum::OPENING_BALANCE->value:
|
||||
// whichever isn't an initial balance account:
|
||||
$lead = $journal
|
||||
->transactions()
|
||||
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id')
|
||||
->where('account_types.type', '!=', AccountTypeEnum::INITIAL_BALANCE->value)
|
||||
->first(['transactions.*'])
|
||||
;
|
||||
|
||||
break;
|
||||
|
||||
case TransactionTypeEnum::RECONCILIATION->value:
|
||||
// whichever isn't the reconciliation account:
|
||||
$lead = $journal
|
||||
->transactions()
|
||||
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id')
|
||||
->where('account_types.type', '!=', AccountTypeEnum::RECONCILIATION->value)
|
||||
->first(['transactions.*'])
|
||||
;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $lead;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function isMultiCurrency(Account $account): bool
|
||||
{
|
||||
$value = $this->accountRepos->getMetaValue($account, 'is_multi_currency');
|
||||
if (null === $value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return '1' === $value;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is
|
||||
* executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should
|
||||
@@ -86,34 +179,6 @@ class UpgradesVariousCurrencyInformation extends Command
|
||||
$this->cliRepos = app(JournalCLIRepositoryInterface::class);
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = FireflyConfig::get(self::CONFIG_NAME, false);
|
||||
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for
|
||||
* the accounts they are linked to.
|
||||
* Both source and destination must match the respective currency preference of the related asset account.
|
||||
* So FF3 must verify all transactions.
|
||||
*/
|
||||
private function updateOtherJournalsCurrencies(): void
|
||||
{
|
||||
$set = $this->cliRepos->getAllJournals([
|
||||
TransactionTypeEnum::WITHDRAWAL->value,
|
||||
TransactionTypeEnum::DEPOSIT->value,
|
||||
TransactionTypeEnum::OPENING_BALANCE->value,
|
||||
TransactionTypeEnum::RECONCILIATION->value,
|
||||
]);
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
$this->updateJournalCurrency($journal);
|
||||
}
|
||||
}
|
||||
|
||||
private function updateJournalCurrency(TransactionJournal $journal): void
|
||||
{
|
||||
$this->accountRepos->setUser($journal->user);
|
||||
@@ -166,88 +231,23 @@ class UpgradesVariousCurrencyInformation extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the transaction that determines the transaction that "leads" and will determine
|
||||
* the currency to be used by all transactions, and the journal itself.
|
||||
* This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for
|
||||
* the accounts they are linked to.
|
||||
* Both source and destination must match the respective currency preference of the related asset account.
|
||||
* So FF3 must verify all transactions.
|
||||
*/
|
||||
private function getLeadTransaction(TransactionJournal $journal): ?Transaction
|
||||
private function updateOtherJournalsCurrencies(): void
|
||||
{
|
||||
/** @var null|Transaction $lead */
|
||||
$lead = null;
|
||||
$set = $this->cliRepos->getAllJournals([
|
||||
TransactionTypeEnum::WITHDRAWAL->value,
|
||||
TransactionTypeEnum::DEPOSIT->value,
|
||||
TransactionTypeEnum::OPENING_BALANCE->value,
|
||||
TransactionTypeEnum::RECONCILIATION->value,
|
||||
]);
|
||||
|
||||
switch ($journal->transactionType->type) {
|
||||
default:
|
||||
break;
|
||||
|
||||
case TransactionTypeEnum::WITHDRAWAL->value:
|
||||
$lead = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
|
||||
break;
|
||||
|
||||
case TransactionTypeEnum::DEPOSIT->value:
|
||||
$lead = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
|
||||
break;
|
||||
|
||||
case TransactionTypeEnum::OPENING_BALANCE->value:
|
||||
// whichever isn't an initial balance account:
|
||||
$lead = $journal
|
||||
->transactions()
|
||||
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id')
|
||||
->where('account_types.type', '!=', AccountTypeEnum::INITIAL_BALANCE->value)
|
||||
->first(['transactions.*'])
|
||||
;
|
||||
|
||||
break;
|
||||
|
||||
case TransactionTypeEnum::RECONCILIATION->value:
|
||||
// whichever isn't the reconciliation account:
|
||||
$lead = $journal
|
||||
->transactions()
|
||||
->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id')
|
||||
->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id')
|
||||
->where('account_types.type', '!=', AccountTypeEnum::RECONCILIATION->value)
|
||||
->first(['transactions.*'])
|
||||
;
|
||||
|
||||
break;
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
$this->updateJournalCurrency($journal);
|
||||
}
|
||||
|
||||
return $lead;
|
||||
}
|
||||
|
||||
private function getCurrency(Account $account): ?TransactionCurrency
|
||||
{
|
||||
$accountId = $account->id;
|
||||
if (array_key_exists($accountId, $this->accountCurrencies) && 0 === $this->accountCurrencies[$accountId]) {
|
||||
return null;
|
||||
}
|
||||
if (array_key_exists($accountId, $this->accountCurrencies) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) {
|
||||
return $this->accountCurrencies[$accountId];
|
||||
}
|
||||
$currency = $this->accountRepos->getAccountCurrency($account);
|
||||
if (!$currency instanceof TransactionCurrency) {
|
||||
$this->accountCurrencies[$accountId] = 0;
|
||||
|
||||
return null;
|
||||
}
|
||||
$this->accountCurrencies[$accountId] = $currency;
|
||||
|
||||
return $currency;
|
||||
}
|
||||
|
||||
private function isMultiCurrency(Account $account): bool
|
||||
{
|
||||
$value = $this->accountRepos->getMetaValue($account, 'is_multi_currency');
|
||||
if (null === $value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return '1' === $value;
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,18 +69,9 @@ class UpgradesWebhooks extends Command
|
||||
return (bool) $configVar?->data;
|
||||
}
|
||||
|
||||
private function upgradeWebhooks(): void
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
$set = Webhook::where('delivery', '>', 1)
|
||||
->orWhere('trigger', '>', 1)
|
||||
->orWhere('response', '>', 1)
|
||||
->get()
|
||||
;
|
||||
|
||||
/** @var Webhook $webhook */
|
||||
foreach ($set as $webhook) {
|
||||
$this->upgradeWebhook($webhook);
|
||||
}
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
}
|
||||
|
||||
private function upgradeWebhook(Webhook $webhook): void
|
||||
@@ -111,8 +102,17 @@ class UpgradesWebhooks extends Command
|
||||
$this->friendlyPositive(sprintf('Webhook #%d upgraded.', $webhook->id));
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
private function upgradeWebhooks(): void
|
||||
{
|
||||
FireflyConfig::set(self::CONFIG_NAME, true);
|
||||
$set = Webhook::where('delivery', '>', 1)
|
||||
->orWhere('trigger', '>', 1)
|
||||
->orWhere('response', '>', 1)
|
||||
->get()
|
||||
;
|
||||
|
||||
/** @var Webhook $webhook */
|
||||
foreach ($set as $webhook) {
|
||||
$this->upgradeWebhook($webhook);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,15 @@ use Illuminate\Support\Facades\Log;
|
||||
*/
|
||||
trait VerifiesAccessToken
|
||||
{
|
||||
/**
|
||||
* Abstract method to make sure trait knows about method "option".
|
||||
*
|
||||
* @param null|string $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function option($key = null);
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -54,15 +63,6 @@ trait VerifiesAccessToken
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method to make sure trait knows about method "option".
|
||||
*
|
||||
* @param null|string $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function option($key = null);
|
||||
|
||||
/**
|
||||
* Returns false when given token does not match given user token.
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* UpdatedAccount.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
* CreatedNewAccount.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -20,17 +22,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
namespace FireflyIII\Events\Model\Account;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class UpdatedAccount
|
||||
*/
|
||||
class UpdatedAccount extends Event
|
||||
class CreatedNewAccount extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* StoredAccount.php
|
||||
* Copyright (c) 2021 james@firefly-iii.org
|
||||
* UpdatedExistingAccount.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -20,17 +22,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
namespace FireflyIII\Events\Model\Account;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\Account;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class StoredAccount
|
||||
*/
|
||||
class StoredAccount extends Event
|
||||
class UpdatedExistingAccount extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
39
app/Events/Model/Budget/CreatedBudget.php
Normal file
39
app/Events/Model/Budget/CreatedBudget.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* CreatedBudget.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Events\Model\Budget;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\Budget;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreatedBudget extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public Budget $budget,
|
||||
public bool $createWebhookMessages
|
||||
) {}
|
||||
}
|
||||
39
app/Events/Model/Budget/DestroyedBudget.php
Normal file
39
app/Events/Model/Budget/DestroyedBudget.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* DestroyedBudget.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Events\Model\Budget;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DestroyedBudget extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Log::debug('Created event DestroyedBudget');
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DestroyedTransactionGroup.php
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* DestroyingBudget.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -20,27 +22,20 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace FireflyIII\Events\Model\Budget;
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\Budget;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class DestroyedTransactionGroup.
|
||||
*/
|
||||
class DestroyedTransactionGroup extends Event
|
||||
class DestroyingBudget extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public TransactionGroup $transactionGroup
|
||||
public Budget $budget
|
||||
) {
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Created event DestroyingBudget(#%d)', $budget->id));
|
||||
}
|
||||
}
|
||||
39
app/Events/Model/Budget/UpdatedBudget.php
Normal file
39
app/Events/Model/Budget/UpdatedBudget.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* UpdatedBudget.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Events\Model\Budget;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\Budget;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UpdatedBudget extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public Budget $budget,
|
||||
public bool $createWebhookMessages
|
||||
) {}
|
||||
}
|
||||
39
app/Events/Model/BudgetLimit/CreatedBudgetLimit.php
Normal file
39
app/Events/Model/BudgetLimit/CreatedBudgetLimit.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* CreatedBudgetLimit.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Events\Model\BudgetLimit;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CreatedBudgetLimit extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public BudgetLimit $budgetLimit,
|
||||
public bool $createWebhookMessages
|
||||
) {}
|
||||
}
|
||||
44
app/Events/Model/BudgetLimit/DestroyedBudgetLimit.php
Normal file
44
app/Events/Model/BudgetLimit/DestroyedBudgetLimit.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* DestroyedBudgetLimit.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Events\Model\BudgetLimit;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class DestroyedBudgetLimit extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public User $user,
|
||||
public Budget $budget,
|
||||
public Carbon $start,
|
||||
public Carbon $end,
|
||||
public bool $createWebhookMessages
|
||||
) {}
|
||||
}
|
||||
39
app/Events/Model/BudgetLimit/UpdatedBudgetLimit.php
Normal file
39
app/Events/Model/BudgetLimit/UpdatedBudgetLimit.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* UpdatedBudgetLimit.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Events\Model\BudgetLimit;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UpdatedBudgetLimit extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public BudgetLimit $budgetLimit,
|
||||
public bool $createWebhookMessages
|
||||
) {}
|
||||
}
|
||||
@@ -25,9 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Events\Model\TransactionGroup;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class CreatedSingleTransactionGroup extends Event
|
||||
{
|
||||
@@ -37,9 +35,7 @@ class CreatedSingleTransactionGroup extends Event
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public TransactionGroup $transactionGroup,
|
||||
public TransactionGroupEventFlags $flags
|
||||
) {
|
||||
Log::debug(__METHOD__);
|
||||
}
|
||||
public TransactionGroupEventFlags $flags,
|
||||
public TransactionGroupEventObjects $objects
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* StoredTransactionGroup.php
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* DestroyedSingleTransactionGroup.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -20,17 +22,12 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
namespace FireflyIII\Events\Model\TransactionGroup;
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class StoredTransactionGroup.
|
||||
*/
|
||||
class StoredTransactionGroup extends Event
|
||||
class DestroyedSingleTransactionGroup extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
@@ -38,8 +35,7 @@ class StoredTransactionGroup extends Event
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public TransactionGroup $transactionGroup,
|
||||
public bool $applyRules,
|
||||
public bool $fireWebhooks
|
||||
public TransactionGroupEventFlags $flags,
|
||||
public TransactionGroupEventObjects $objects
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\TransactionGroup;
|
||||
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* This class collects all objects before and after the creation, removal or updating
|
||||
* of a transaction group. The idea is that this class contains all relevant objects.
|
||||
* Right now, that means journals, tags, accounts, budgets and categories.
|
||||
*
|
||||
* By collecting these objects (in case of an update: before AND after update) there
|
||||
* is a unified set of objects to manage: update balances, recalculate credits, etc.
|
||||
*/
|
||||
class TransactionGroupEventObjects
|
||||
{
|
||||
public Collection $accounts;
|
||||
public Collection $budgets;
|
||||
public Collection $categories;
|
||||
public Collection $tags;
|
||||
public Collection $transactionGroups;
|
||||
public Collection $transactionJournals;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->accounts = new Collection();
|
||||
$this->budgets = new Collection();
|
||||
$this->categories = new Collection();
|
||||
$this->tags = new Collection();
|
||||
$this->transactionGroups = new Collection();
|
||||
$this->transactionJournals = new Collection();
|
||||
}
|
||||
|
||||
public static function collectFromTransactionGroup(TransactionGroup $transactionGroup): self
|
||||
{
|
||||
Log::debug(sprintf('collectFromTransactionGroup(#%d)', $transactionGroup->id));
|
||||
$object = new self();
|
||||
$object->appendFromTransactionGroup($transactionGroup);
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function appendFromTransactionGroup(TransactionGroup $transactionGroup): void
|
||||
{
|
||||
$this->transactionGroups->push($transactionGroup);
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($transactionGroup->transactionJournals as $journal) {
|
||||
$this->transactionJournals->push($journal);
|
||||
$this->budgets = $this->budgets->merge($journal->budgets);
|
||||
$this->categories = $this->categories->merge($journal->categories);
|
||||
$this->tags = $this->tags->merge($journal->tags);
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journal->transactions as $transaction) {
|
||||
$this->accounts->push($transaction->account);
|
||||
}
|
||||
}
|
||||
$this->transactionGroups = $this->transactionGroups->unique('id');
|
||||
$this->transactionJournals = $this->transactionJournals->unique('id');
|
||||
$this->budgets = $this->budgets->unique('id');
|
||||
$this->categories = $this->categories->unique('id');
|
||||
$this->tags = $this->tags->unique('id');
|
||||
$this->accounts = $this->accounts->unique('id');
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* TriggeredStoredTransactionGroup.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\Events\Model\TransactionGroup;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TriggeredStoredTransactionGroup extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
public ?RuleGroup $ruleGroup = null;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public TransactionGroup $transactionGroup,
|
||||
?RuleGroup $ruleGroup = null
|
||||
) {
|
||||
$this->ruleGroup = $ruleGroup;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* CreatedTransactionGroupBatch.php
|
||||
* UpdatedSingleTransactionGroup.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
@@ -26,9 +26,8 @@ namespace FireflyIII\Events\Model\TransactionGroup;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CreatedTransactionGroupInBatch extends Event
|
||||
class UpdatedSingleTransactionGroup extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
@@ -36,7 +35,7 @@ class CreatedTransactionGroupInBatch extends Event
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public Collection $collection,
|
||||
public array $flags
|
||||
public TransactionGroupEventFlags $flags,
|
||||
public TransactionGroupEventObjects $objects
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* UserRequestedBatchProcessing.php
|
||||
* Copyright (c) 2026 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Events\Model\TransactionGroup;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UserRequestedBatchProcessing extends Event
|
||||
{
|
||||
public function __construct(
|
||||
public TransactionGroupEventFlags $flags
|
||||
) {
|
||||
Log::debug(__METHOD__);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* UpdatedTransactionGroup.php
|
||||
* Copyright (c) 2019 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events;
|
||||
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Class UpdatedTransactionGroup.
|
||||
*/
|
||||
class UpdatedTransactionGroup extends Event
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public TransactionGroup $transactionGroup,
|
||||
public bool $applyRules,
|
||||
public bool $fireWebhooks,
|
||||
public bool $runRecalculations
|
||||
) {}
|
||||
}
|
||||
@@ -181,46 +181,6 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
return redirect(route('accounts.index', [$shortType]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function handleGroup(Request $request, Throwable $exception)
|
||||
{
|
||||
Log::debug('404 page is probably a deleted group. Redirect to overview of group types.');
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$route = $request->route();
|
||||
$param = $route->parameter('transactionGroup');
|
||||
$groupId = is_object($param) ? 0 : (int) $param;
|
||||
|
||||
/** @var null|TransactionGroup $group */
|
||||
$group = $user->transactionGroups()->withTrashed()->find($groupId);
|
||||
if (null === $group) {
|
||||
Log::error(sprintf('Could not find group %d, so give big fat error.', $groupId));
|
||||
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
|
||||
/** @var null|TransactionJournal $journal */
|
||||
$journal = $group->transactionJournals()->withTrashed()->first();
|
||||
if (null === $journal) {
|
||||
Log::error(sprintf('Could not find journal for group %d, so give big fat error.', $groupId));
|
||||
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
$type = $journal->transactionType->type;
|
||||
$request->session()->reflash();
|
||||
|
||||
if (TransactionTypeEnum::RECONCILIATION->value === $type) {
|
||||
return redirect(route('accounts.index', ['asset']));
|
||||
}
|
||||
|
||||
return redirect(route('transactions.index', [strtolower((string) $type)]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*
|
||||
@@ -265,4 +225,44 @@ class GracefulNotFoundHandler extends ExceptionHandler
|
||||
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function handleGroup(Request $request, Throwable $exception)
|
||||
{
|
||||
Log::debug('404 page is probably a deleted group. Redirect to overview of group types.');
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$route = $request->route();
|
||||
$param = $route->parameter('transactionGroup');
|
||||
$groupId = is_object($param) ? 0 : (int) $param;
|
||||
|
||||
/** @var null|TransactionGroup $group */
|
||||
$group = $user->transactionGroups()->withTrashed()->find($groupId);
|
||||
if (null === $group) {
|
||||
Log::error(sprintf('Could not find group %d, so give big fat error.', $groupId));
|
||||
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
|
||||
/** @var null|TransactionJournal $journal */
|
||||
$journal = $group->transactionJournals()->withTrashed()->first();
|
||||
if (null === $journal) {
|
||||
Log::error(sprintf('Could not find journal for group %d, so give big fat error.', $groupId));
|
||||
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
$type = $journal->transactionType->type;
|
||||
$request->session()->reflash();
|
||||
|
||||
if (TransactionTypeEnum::RECONCILIATION->value === $type) {
|
||||
return redirect(route('accounts.index', ['asset']));
|
||||
}
|
||||
|
||||
return redirect(route('transactions.index', [strtolower((string) $type)]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ use FireflyIII\Jobs\MailError;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -71,6 +72,7 @@ class Handler extends ExceptionHandler
|
||||
AuthenticationException::class,
|
||||
LaravelValidationException::class,
|
||||
NotFoundHttpException::class,
|
||||
ModelNotFoundException::class,
|
||||
GoneHttpException::class,
|
||||
OAuthServerException::class,
|
||||
LaravelOAuthException::class,
|
||||
@@ -258,11 +260,6 @@ class Handler extends ExceptionHandler
|
||||
parent::report($e);
|
||||
}
|
||||
|
||||
private function shouldntReportLocal(Throwable $e): bool
|
||||
{
|
||||
return null !== Arr::first($this->dontReport, static fn ($type): bool => $e instanceof $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a validation exception into a response.
|
||||
*
|
||||
@@ -296,4 +293,9 @@ class Handler extends ExceptionHandler
|
||||
|
||||
return null !== $previousHost && $previousHost === $safeHost ? $previous : $safe;
|
||||
}
|
||||
|
||||
private function shouldntReportLocal(Throwable $e): bool
|
||||
{
|
||||
return null !== Arr::first($this->dontReport, static fn ($type): bool => $e instanceof $type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Factory;
|
||||
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Events\StoredAccount;
|
||||
use FireflyIII\Events\Model\Account\CreatedNewAccount;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
@@ -67,6 +67,43 @@ class AccountFactory
|
||||
$this->validFields = config('firefly.valid_account_fields');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function create(array $data): Account
|
||||
{
|
||||
Log::debug('Now in AccountFactory::create()');
|
||||
$type = $this->getAccountType($data);
|
||||
$data['iban'] = $this->filterIban($data['iban'] ?? null);
|
||||
|
||||
// account may exist already:
|
||||
$return = $this->find($data['name'], $type->type);
|
||||
|
||||
if ($return instanceof Account) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
$return = $this->createAccount($type, $data);
|
||||
|
||||
event(new CreatedNewAccount($return));
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function find(string $accountName, string $accountType): ?Account
|
||||
{
|
||||
Log::debug(sprintf('Now in AccountFactory::find("%s", "%s")', $accountName, $accountType));
|
||||
$type = AccountType::whereType($accountType)->first();
|
||||
|
||||
/** @var null|Account */
|
||||
return $this->user
|
||||
->accounts()
|
||||
->where('account_type_id', $type->id)
|
||||
->where('name', $accountName)
|
||||
->first()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -104,27 +141,10 @@ class AccountFactory
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function create(array $data): Account
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
Log::debug('Now in AccountFactory::create()');
|
||||
$type = $this->getAccountType($data);
|
||||
$data['iban'] = $this->filterIban($data['iban'] ?? null);
|
||||
|
||||
// account may exist already:
|
||||
$return = $this->find($data['name'], $type->type);
|
||||
|
||||
if ($return instanceof Account) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
$return = $this->createAccount($type, $data);
|
||||
|
||||
event(new StoredAccount($return));
|
||||
|
||||
return $return;
|
||||
$this->user = $user;
|
||||
$this->accountRepository->setUser($user);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,18 +180,28 @@ class AccountFactory
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function find(string $accountName, string $accountType): ?Account
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function cleanMetaDataArray(Account $account, array $data): array
|
||||
{
|
||||
Log::debug(sprintf('Now in AccountFactory::find("%s", "%s")', $accountName, $accountType));
|
||||
$type = AccountType::whereType($accountType)->first();
|
||||
$currencyId = array_key_exists('currency_id', $data) ? (int) $data['currency_id'] : 0;
|
||||
$currencyCode = array_key_exists('currency_code', $data) ? (string) $data['currency_code'] : '';
|
||||
$accountRole = array_key_exists('account_role', $data) ? (string) $data['account_role'] : null;
|
||||
$currency = $this->getCurrency($currencyId, $currencyCode);
|
||||
|
||||
/** @var null|Account */
|
||||
return $this->user
|
||||
->accounts()
|
||||
->where('account_type_id', $type->id)
|
||||
->where('name', $accountName)
|
||||
->first()
|
||||
;
|
||||
// only asset account may have a role:
|
||||
if (AccountTypeEnum::ASSET->value !== $account->accountType->type) {
|
||||
$accountRole = '';
|
||||
}
|
||||
// only liability may have direction:
|
||||
if (array_key_exists('liability_direction', $data) && !in_array($account->accountType->type, config('firefly.valid_liabilities'), true)) {
|
||||
$data['liability_direction'] = null;
|
||||
}
|
||||
$data['account_role'] = $accountRole;
|
||||
$data['currency_id'] = $currency->id;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,25 +275,27 @@ class AccountFactory
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function cleanMetaDataArray(Account $account, array $data): array
|
||||
private function storeCreditLiability(Account $account, array $data): void
|
||||
{
|
||||
$currencyId = array_key_exists('currency_id', $data) ? (int) $data['currency_id'] : 0;
|
||||
$currencyCode = array_key_exists('currency_code', $data) ? (string) $data['currency_code'] : '';
|
||||
$accountRole = array_key_exists('account_role', $data) ? (string) $data['account_role'] : null;
|
||||
$currency = $this->getCurrency($currencyId, $currencyCode);
|
||||
|
||||
// only asset account may have a role:
|
||||
if (AccountTypeEnum::ASSET->value !== $account->accountType->type) {
|
||||
$accountRole = '';
|
||||
Log::debug('storeCreditLiability');
|
||||
$account->refresh();
|
||||
$accountType = $account->accountType->type;
|
||||
$direction = $this->accountRepository->getMetaValue($account, 'liability_direction');
|
||||
$valid = config('firefly.valid_liabilities');
|
||||
if (in_array($accountType, $valid, true)) {
|
||||
Log::debug('Is a liability with credit ("i am owed") direction.');
|
||||
if ($this->validOBData($data)) {
|
||||
Log::debug('Has valid CL data.');
|
||||
$openingBalance = $data['opening_balance'];
|
||||
$openingBalanceDate = $data['opening_balance_date'];
|
||||
// store credit transaction.
|
||||
$this->updateCreditTransaction($account, $direction, $openingBalance, $openingBalanceDate);
|
||||
}
|
||||
if (!$this->validOBData($data)) {
|
||||
Log::debug('Does NOT have valid CL data, deletr any CL transaction.');
|
||||
$this->deleteCreditTransaction($account);
|
||||
}
|
||||
}
|
||||
// only liability may have direction:
|
||||
if (array_key_exists('liability_direction', $data) && !in_array($account->accountType->type, config('firefly.valid_liabilities'), true)) {
|
||||
$data['liability_direction'] = null;
|
||||
}
|
||||
$data['account_role'] = $accountRole;
|
||||
$data['currency_id'] = $currency->id;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function storeMetaData(Account $account, array $data): void
|
||||
@@ -324,32 +356,6 @@ class AccountFactory
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function storeCreditLiability(Account $account, array $data): void
|
||||
{
|
||||
Log::debug('storeCreditLiability');
|
||||
$account->refresh();
|
||||
$accountType = $account->accountType->type;
|
||||
$direction = $this->accountRepository->getMetaValue($account, 'liability_direction');
|
||||
$valid = config('firefly.valid_liabilities');
|
||||
if (in_array($accountType, $valid, true)) {
|
||||
Log::debug('Is a liability with credit ("i am owed") direction.');
|
||||
if ($this->validOBData($data)) {
|
||||
Log::debug('Has valid CL data.');
|
||||
$openingBalance = $data['opening_balance'];
|
||||
$openingBalanceDate = $data['opening_balance_date'];
|
||||
// store credit transaction.
|
||||
$this->updateCreditTransaction($account, $direction, $openingBalance, $openingBalanceDate);
|
||||
}
|
||||
if (!$this->validOBData($data)) {
|
||||
Log::debug('Does NOT have valid CL data, deletr any CL transaction.');
|
||||
$this->deleteCreditTransaction($account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*/
|
||||
@@ -370,10 +376,4 @@ class AccountFactory
|
||||
$updateService->setUser($account->user);
|
||||
$updateService->update($account, ['order' => $order]);
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->accountRepository->setUser($user);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user