mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 10:47:00 +00:00 
			
		
		
		
	Compare commits
	
		
			110 Commits
		
	
	
		
			develop-20
			...
			develop-20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | c43821e29c | ||
|  | b7570b2651 | ||
|  | cb77609f27 | ||
|  | 99651bb61e | ||
|  | bf1e14f66d | ||
|  | d0e55804f8 | ||
|  | 0a9715b8c1 | ||
|  | 61390e67f6 | ||
|  | 536d25980f | ||
|  | 3253b2e569 | ||
|  | 11ac955303 | ||
|  | caf9a31bc4 | ||
|  | 8c84ab5855 | ||
|  | 91a56a7396 | ||
|  | f5806ea6de | ||
|  | 60b3692ac9 | ||
|  | 0bdb4f2e69 | ||
|  | f89f50c2db | ||
|  | 394df46961 | ||
|  | 535e5e4f50 | ||
|  | 00ba2a46d2 | ||
|  | a3ff26e3e4 | ||
|  | 54aeb4b4ef | ||
|  | 9eb3ad62dd | ||
|  | 4f0e978687 | ||
|  | e1cf9f7a79 | ||
|  | 7ce055a22c | ||
|  | 7bd915930c | ||
|  | 75aa2d99fd | ||
|  | f52bc0e242 | ||
|  | 55cf924794 | ||
|  | df3e4a6554 | ||
|  | 7c4ada458e | ||
|  | 2a4a98dd10 | ||
|  | a3bf845851 | ||
|  | 78e832cdba | ||
|  | d47e4c4f24 | ||
|  | 056329291f | ||
|  | 977946064d | ||
|  | 1870345ddf | ||
|  | 6980717075 | ||
|  | 1fe0aebacb | ||
|  | cbf7aef0c1 | ||
|  | 8c3e6c0189 | ||
|  | cf7ee79c1c | ||
|  | 7e344e4332 | ||
|  | 0a55e9fb4e | ||
|  | ed2e0e86dc | ||
|  | 9d1fb2cd6a | ||
|  | 57617b750f | ||
|  | f6411fdc5a | ||
|  | 02e24fc919 | ||
|  | dbc0210304 | ||
|  | a709e224d4 | ||
|  | 83f3eddf44 | ||
|  | 7cfc4c2671 | ||
|  | 1c6055cb2d | ||
|  | 4f4576e458 | ||
|  | 84d3bcbb37 | ||
|  | c91c87d646 | ||
|  | e09b6034f7 | ||
|  | ad67bb80f3 | ||
|  | a88d0de34d | ||
|  | ecf498cc81 | ||
|  | 569f553d26 | ||
|  | 7d45bc46b8 | ||
|  | 08553fcfb2 | ||
|  | 58c76bee94 | ||
|  | 3fa1b6dd27 | ||
|  | 63aa8adab7 | ||
|  | 70b8ea0acb | ||
|  | d800a01e33 | ||
|  | 52f3ec7d3d | ||
|  | d4978a09ee | ||
|  | 77095276e2 | ||
|  | b77a8591dc | ||
|  | 132d7d9ff8 | ||
|  | a981e2c5cb | ||
|  | 77b88b7758 | ||
|  | b9894eea57 | ||
|  | 469319a240 | ||
|  | 5a55593e34 | ||
|  | 8979e5ad5a | ||
|  | 3ab65c27ac | ||
|  | a3cac6fd0f | ||
|  | 1acb5d8681 | ||
|  | f24cdc7897 | ||
|  | a2b611253b | ||
|  | 252459c29b | ||
|  | 3582baf9f7 | ||
|  | b03ecab035 | ||
|  | 547a4e9dbb | ||
|  | 62e33a51bd | ||
|  | 8b0ee7e20a | ||
|  | 0e0ec89b26 | ||
|  | ec08485c2b | ||
|  | d91d30c8f0 | ||
|  | 634a43c361 | ||
|  | f2f86e1139 | ||
|  | a1c870c962 | ||
|  | 98be3a1414 | ||
|  | e3c3a0a84b | ||
|  | b254074867 | ||
|  | f21a5b3000 | ||
|  | 6029fe2e84 | ||
|  | 651e11ed1c | ||
|  | 5ba29cdacd | ||
|  | f948cc95b4 | ||
|  | 11e721c6c9 | ||
|  | 2ddc012549 | 
							
								
								
									
										13
									
								
								.ci/php-cs-fixer/composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										13
									
								
								.ci/php-cs-fixer/composer.lock
									
									
									
										generated
									
									
									
								
							| @@ -402,16 +402,16 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "friendsofphp/php-cs-fixer", |             "name": "friendsofphp/php-cs-fixer", | ||||||
|             "version": "v3.88.2", |             "version": "v3.89.1", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", |                 "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", | ||||||
|                 "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99" |                 "reference": "f34967da2866ace090a2b447de1f357356474573" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a8d15584bafb0f0d9d938827840060fd4a3ebc99", |                 "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/f34967da2866ace090a2b447de1f357356474573", | ||||||
|                 "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99", |                 "reference": "f34967da2866ace090a2b447de1f357356474573", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
| @@ -426,7 +426,6 @@ | |||||||
|                 "php": "^7.4 || ^8.0", |                 "php": "^7.4 || ^8.0", | ||||||
|                 "react/child-process": "^0.6.6", |                 "react/child-process": "^0.6.6", | ||||||
|                 "react/event-loop": "^1.5", |                 "react/event-loop": "^1.5", | ||||||
|                 "react/promise": "^3.3", |  | ||||||
|                 "react/socket": "^1.16", |                 "react/socket": "^1.16", | ||||||
|                 "react/stream": "^1.4", |                 "react/stream": "^1.4", | ||||||
|                 "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", |                 "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", | ||||||
| @@ -494,7 +493,7 @@ | |||||||
|             ], |             ], | ||||||
|             "support": { |             "support": { | ||||||
|                 "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", |                 "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", | ||||||
|                 "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.88.2" |                 "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.89.1" | ||||||
|             }, |             }, | ||||||
|             "funding": [ |             "funding": [ | ||||||
|                 { |                 { | ||||||
| @@ -502,7 +501,7 @@ | |||||||
|                     "type": "github" |                     "type": "github" | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             "time": "2025-09-27T00:24:15+00:00" |             "time": "2025-10-24T12:05:10+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "psr/container", |             "name": "psr/container", | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ 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. | Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution. | ||||||
| 
 | 
 | ||||||
| ## 2025 | ## 2025 | ||||||
|  | - jreyesr | ||||||
|  | - codearena-bot | ||||||
| - Nicky De Maeyer | - Nicky De Maeyer | ||||||
| - Denis Iskandarov | - Denis Iskandarov | ||||||
| - Lompi | - Lompi | ||||||
|   | |||||||
| @@ -84,7 +84,7 @@ class AccountController extends Controller | |||||||
|         $data        = $request->getData(); |         $data        = $request->getData(); | ||||||
|         $types       = $data['types']; |         $types       = $data['types']; | ||||||
|         $query       = $data['query']; |         $query       = $data['query']; | ||||||
|         $date        = $data['date'] ?? today(config('app.timezone')); |         $date        = $data['date']; | ||||||
|         $return      = []; |         $return      = []; | ||||||
|         $timer       = Timer::getInstance(); |         $timer       = Timer::getInstance(); | ||||||
|         $timer->start(sprintf('AC accounts "%s"', $query)); |         $timer->start(sprintf('AC accounts "%s"', $query)); | ||||||
|   | |||||||
| @@ -67,6 +67,8 @@ abstract class Controller extends BaseController | |||||||
| 
 | 
 | ||||||
|     protected bool                $convertToPrimary = false; |     protected bool                $convertToPrimary = false; | ||||||
|     protected TransactionCurrency $primaryCurrency; |     protected TransactionCurrency $primaryCurrency; | ||||||
|  | 
 | ||||||
|  |     /** @deprecated use Request classes */ | ||||||
|     protected ParameterBag        $parameters; |     protected ParameterBag        $parameters; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @@ -98,7 +100,8 @@ abstract class Controller extends BaseController | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Method to grab all parameters from the URL. |      * @deprecated use Request classes | ||||||
|  |      * Method to grab all parameters from the URL | ||||||
|      */ |      */ | ||||||
|     private function getParameters(): ParameterBag |     private function getParameters(): ParameterBag | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ declare(strict_types=1); | |||||||
| namespace FireflyIII\Api\V1\Controllers\Models\Account; | namespace FireflyIII\Api\V1\Controllers\Models\Account; | ||||||
| 
 | 
 | ||||||
| use FireflyIII\Api\V1\Controllers\Controller; | use FireflyIII\Api\V1\Controllers\Controller; | ||||||
|  | use FireflyIII\Api\V1\Requests\PaginationRequest; | ||||||
| use FireflyIII\Helpers\Collector\GroupCollectorInterface; | use FireflyIII\Helpers\Collector\GroupCollectorInterface; | ||||||
| use FireflyIII\Models\Account; | use FireflyIII\Models\Account; | ||||||
| use FireflyIII\Repositories\Account\AccountRepositoryInterface; | use FireflyIII\Repositories\Account\AccountRepositoryInterface; | ||||||
| @@ -69,22 +70,25 @@ class ListController extends Controller | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function attachments(Account $account): JsonResponse |     public function attachments(Account $account, PaginationRequest $request): JsonResponse | ||||||
|     { |     { | ||||||
|         $manager     = $this->getManager(); |         $manager     = $this->getManager(); | ||||||
|         $pageSize    = $this->parameters->get('limit'); |         [ | ||||||
|  |             'limit'  => $limit, | ||||||
|  |             'offset' => $offset, | ||||||
|  |             'page'   => $page, | ||||||
|  |         ]            = $request->attributes->all(); | ||||||
|         $collection  = $this->repository->getAttachments($account); |         $collection  = $this->repository->getAttachments($account); | ||||||
| 
 | 
 | ||||||
|         $count       = $collection->count(); |         $count       = $collection->count(); | ||||||
|         $attachments = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); |         $attachments = $collection->slice($offset, $limit); | ||||||
| 
 | 
 | ||||||
|         // make paginator:
 |         // make paginator:
 | ||||||
|         $paginator   = new LengthAwarePaginator($attachments, $count, $pageSize, $this->parameters->get('page')); |         $paginator   = new LengthAwarePaginator($attachments, $count, $limit, $page); | ||||||
|         $paginator->setPath(route('api.v1.accounts.attachments', [$account->id]).$this->buildParams()); |         $paginator->setPath(route('api.v1.accounts.attachments', [$account->id]).$this->buildParams()); | ||||||
| 
 | 
 | ||||||
|         /** @var AttachmentTransformer $transformer */ |         /** @var AttachmentTransformer $transformer */ | ||||||
|         $transformer = app(AttachmentTransformer::class); |         $transformer = app(AttachmentTransformer::class); | ||||||
|         $transformer->setParameters($this->parameters); |  | ||||||
| 
 | 
 | ||||||
|         $resource    = new FractalCollection($attachments, $transformer, 'attachments'); |         $resource    = new FractalCollection($attachments, $transformer, 'attachments'); | ||||||
|         $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); |         $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); | ||||||
|   | |||||||
| @@ -71,43 +71,45 @@ class ShowController extends Controller | |||||||
|     public function index(ShowRequest $request): JsonResponse |     public function index(ShowRequest $request): JsonResponse | ||||||
|     { |     { | ||||||
|         $manager     = $this->getManager(); |         $manager     = $this->getManager(); | ||||||
|         $params      = $request->getParameters(); |         [ | ||||||
|         $this->parameters->set('type', $params['type']); |             'types'  => $types, | ||||||
| 
 |             'page'   => $page, | ||||||
|         // types to get, page size:
 |             'limit'  => $limit, | ||||||
|         $types       = $this->mapAccountTypes($params['type']); |             'offset' => $offset, | ||||||
|  |             'sort'   => $sort, | ||||||
|  |             'start'  => $start, | ||||||
|  |             'end'    => $end, | ||||||
|  |             'date'   => $date, | ||||||
|  |         ] | ||||||
|  |                      = $request->attributes->all(); | ||||||
| 
 | 
 | ||||||
|         // get list of accounts. Count it and split it.
 |         // get list of accounts. Count it and split it.
 | ||||||
|         $this->repository->resetAccountOrder(); |         $this->repository->resetAccountOrder(); | ||||||
|         $collection  = $this->repository->getAccountsByType($types, $params['sort']); |         $collection  = $this->repository->getAccountsByType($types, $sort); | ||||||
|         $count       = $collection->count(); |         $count       = $collection->count(); | ||||||
| 
 | 
 | ||||||
|         // continue sort:
 |         // continue sort:
 | ||||||
|         // TODO if the user sorts on DB dependent field there must be no slice before enrichment, only after.
 |         // TODO if the user sorts on DB dependent field there must be no slice before enrichment, only after.
 | ||||||
|         // TODO still need to figure out how to do this easily.
 |         // TODO still need to figure out how to do this easily.
 | ||||||
|         $accounts    = $collection->slice(($this->parameters->get('page') - 1) * $params['limit'], $params['limit']); |         $accounts    = $collection->slice($offset, $limit); | ||||||
| 
 |  | ||||||
|         // #11007 go to the end of the previous day.
 |  | ||||||
|         $this->parameters->set('start', $this->parameters->get('start')?->subSecond()); |  | ||||||
| 
 | 
 | ||||||
|         // enrich
 |         // enrich
 | ||||||
|         /** @var User $admin */ |         /** @var User $admin */ | ||||||
|         $admin       = auth()->user(); |         $admin       = auth()->user(); | ||||||
|         $enrichment  = new AccountEnrichment(); |         $enrichment  = new AccountEnrichment(); | ||||||
|         $enrichment->setSort($params['sort']); |         $enrichment->setSort($sort); | ||||||
|         $enrichment->setDate($this->parameters->get('date')); |         $enrichment->setDate($date); | ||||||
|         $enrichment->setStart($this->parameters->get('start')); |         $enrichment->setStart($start); | ||||||
|         $enrichment->setEnd($this->parameters->get('end')); |         $enrichment->setEnd($end); | ||||||
|         $enrichment->setUser($admin); |         $enrichment->setUser($admin); | ||||||
|         $accounts    = $enrichment->enrich($accounts); |         $accounts    = $enrichment->enrich($accounts); | ||||||
| 
 | 
 | ||||||
|         // make paginator:
 |         // make paginator:
 | ||||||
|         $paginator   = new LengthAwarePaginator($accounts, $count, $params['limit'], $this->parameters->get('page')); |         $paginator   = new LengthAwarePaginator($accounts, $count, $limit, $page); | ||||||
|         $paginator->setPath(route('api.v1.accounts.index').$this->buildParams()); |         $paginator->setPath(route('api.v1.accounts.index').$this->buildParams()); | ||||||
| 
 | 
 | ||||||
|         /** @var AccountTransformer $transformer */ |         /** @var AccountTransformer $transformer */ | ||||||
|         $transformer = app(AccountTransformer::class); |         $transformer = app(AccountTransformer::class); | ||||||
|         $transformer->setParameters($this->parameters); |  | ||||||
| 
 | 
 | ||||||
|         $resource    = new FractalCollection($accounts, $transformer, self::RESOURCE_KEY); |         $resource    = new FractalCollection($accounts, $transformer, self::RESOURCE_KEY); | ||||||
|         $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); |         $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); | ||||||
| @@ -126,26 +128,25 @@ class ShowController extends Controller | |||||||
|         // get list of accounts. Count it and split it.
 |         // get list of accounts. Count it and split it.
 | ||||||
|         $this->repository->resetAccountOrder(); |         $this->repository->resetAccountOrder(); | ||||||
|         $account->refresh(); |         $account->refresh(); | ||||||
|         $manager     = $this->getManager(); |         $manager                = $this->getManager(); | ||||||
| 
 |         ['start'     => $start, | ||||||
|         // #11007 go to the end of the previous day.
 |             'end'    => $end, | ||||||
|         $this->parameters->set('start', $this->parameters->get('start')?->subSecond()); |             'date'   => $date,] = $request->attributes->all(); | ||||||
| 
 | 
 | ||||||
|         // enrich
 |         // enrich
 | ||||||
|         /** @var User $admin */ |         /** @var User $admin */ | ||||||
|         $admin       = auth()->user(); |         $admin                  = auth()->user(); | ||||||
|         $enrichment  = new AccountEnrichment(); |         $enrichment             = new AccountEnrichment(); | ||||||
|         $enrichment->setDate($this->parameters->get('date')); |         $enrichment->setDate($date); | ||||||
|         $enrichment->setStart($this->parameters->get('start')); |         $enrichment->setStart($start); | ||||||
|         $enrichment->setEnd($this->parameters->get('end')); |         $enrichment->setEnd($end); | ||||||
|         $enrichment->setUser($admin); |         $enrichment->setUser($admin); | ||||||
|         $account     = $enrichment->enrichSingle($account); |         $account                = $enrichment->enrichSingle($account); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         /** @var AccountTransformer $transformer */ |         /** @var AccountTransformer $transformer */ | ||||||
|         $transformer = app(AccountTransformer::class); |         $transformer            = app(AccountTransformer::class); | ||||||
|         $transformer->setParameters($this->parameters); |         $resource               = new Item($account, $transformer, self::RESOURCE_KEY); | ||||||
|         $resource    = new Item($account, $transformer, self::RESOURCE_KEY); |  | ||||||
| 
 | 
 | ||||||
|         return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); |         return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -172,7 +172,6 @@ class ShowController extends Controller | |||||||
| 
 | 
 | ||||||
|         /** @var BudgetLimitTransformer $transformer */ |         /** @var BudgetLimitTransformer $transformer */ | ||||||
|         $transformer = app(BudgetLimitTransformer::class); |         $transformer = app(BudgetLimitTransformer::class); | ||||||
|         $transformer->setParameters($this->parameters); |  | ||||||
| 
 | 
 | ||||||
|         $resource    = new Item($budgetLimit, $transformer, 'budget_limits'); |         $resource    = new Item($budgetLimit, $transformer, 'budget_limits'); | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ use Carbon\Carbon; | |||||||
| use FireflyIII\Api\V1\Controllers\Controller; | use FireflyIII\Api\V1\Controllers\Controller; | ||||||
| use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\DestroyRequest; | use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\DestroyRequest; | ||||||
| use FireflyIII\Enums\UserRoleEnum; | use FireflyIII\Enums\UserRoleEnum; | ||||||
|  | use FireflyIII\Exceptions\FireflyException; | ||||||
| use FireflyIII\Models\CurrencyExchangeRate; | use FireflyIII\Models\CurrencyExchangeRate; | ||||||
| use FireflyIII\Models\TransactionCurrency; | use FireflyIII\Models\TransactionCurrency; | ||||||
| use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface; | use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface; | ||||||
| @@ -75,6 +76,9 @@ class DestroyController extends Controller | |||||||
|         if ($exchangeRate instanceof CurrencyExchangeRate) { |         if ($exchangeRate instanceof CurrencyExchangeRate) { | ||||||
|             $this->repository->deleteRate($exchangeRate); |             $this->repository->deleteRate($exchangeRate); | ||||||
|         } |         } | ||||||
|  |         if (!$exchangeRate instanceof CurrencyExchangeRate) { | ||||||
|  |             throw new FireflyException('Bla'); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         return response()->json([], 204); |         return response()->json([], 204); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -82,7 +82,6 @@ class UpdateController extends Controller | |||||||
|         $exchangeRate = $this->repository->updateExchangeRate($exchangeRate, $rate, $date); |         $exchangeRate = $this->repository->updateExchangeRate($exchangeRate, $rate, $date); | ||||||
| 
 | 
 | ||||||
|         $transformer  = new ExchangeRateTransformer(); |         $transformer  = new ExchangeRateTransformer(); | ||||||
|         $transformer->setParameters($this->parameters); |  | ||||||
| 
 | 
 | ||||||
|         return response() |         return response() | ||||||
|             ->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer)) |             ->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer)) | ||||||
|   | |||||||
| @@ -102,6 +102,8 @@ class ListController extends Controller | |||||||
| 
 | 
 | ||||||
|         // #11007 go to the end of the previous day.
 |         // #11007 go to the end of the previous day.
 | ||||||
|         $this->parameters->set('start', $this->parameters->get('start')?->subSecond()); |         $this->parameters->set('start', $this->parameters->get('start')?->subSecond()); | ||||||
|  |         // #11018 also end of the day.
 | ||||||
|  |         $this->parameters->set('end', $this->parameters->get('end')?->endOfDay()); | ||||||
| 
 | 
 | ||||||
|         // enrich
 |         // enrich
 | ||||||
|         /** @var User $admin */ |         /** @var User $admin */ | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ declare(strict_types=1); | |||||||
| namespace FireflyIII\Api\V1\Controllers\Models\UserGroup; | namespace FireflyIII\Api\V1\Controllers\Models\UserGroup; | ||||||
| 
 | 
 | ||||||
| use FireflyIII\Api\V1\Controllers\Controller; | use FireflyIII\Api\V1\Controllers\Controller; | ||||||
| use FireflyIII\Api\V1\Requests\Data\DateRequest; | use FireflyIII\Api\V1\Requests\PaginationRequest; | ||||||
| use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface; | use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface; | ||||||
| use FireflyIII\Transformers\UserGroupTransformer; | use FireflyIII\Transformers\UserGroupTransformer; | ||||||
| use Illuminate\Http\JsonResponse; | use Illuminate\Http\JsonResponse; | ||||||
| @@ -52,17 +52,19 @@ class IndexController extends Controller | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function index(DateRequest $request): JsonResponse |     public function index(PaginationRequest $request): JsonResponse | ||||||
|     { |     { | ||||||
|         $administrations = $this->repository->get(); |         $administrations = $this->repository->get(); | ||||||
|         $pageSize        = $this->parameters->get('limit'); |         [ | ||||||
|  |             'page'   => $page, | ||||||
|  |             'limit'  => $limit, | ||||||
|  |             'offset' => $offset, | ||||||
|  |         ]                = $request->attributes->all(); | ||||||
|         $count           = $administrations->count(); |         $count           = $administrations->count(); | ||||||
|         $administrations = $administrations->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); |         $administrations = $administrations->slice($offset, $limit); | ||||||
|         $paginator       = new LengthAwarePaginator($administrations, $count, $pageSize, $this->parameters->get('page')); |         $paginator       = new LengthAwarePaginator($administrations, $count, $limit, $page); | ||||||
|         $transformer     = new UserGroupTransformer(); |         $transformer     = new UserGroupTransformer(); | ||||||
| 
 | 
 | ||||||
|         $transformer->setParameters($this->parameters); // give params to transformer
 |  | ||||||
| 
 |  | ||||||
|         return response() |         return response() | ||||||
|             ->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer)) |             ->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer)) | ||||||
|             ->header('Content-Type', self::CONTENT_TYPE) |             ->header('Content-Type', self::CONTENT_TYPE) | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Summary; | |||||||
| use Carbon\Carbon; | use Carbon\Carbon; | ||||||
| use Exception; | use Exception; | ||||||
| use FireflyIII\Api\V1\Controllers\Controller; | use FireflyIII\Api\V1\Controllers\Controller; | ||||||
| use FireflyIII\Api\V1\Requests\Data\DateRequest; | use FireflyIII\Api\V1\Requests\Summary\BasicRequest; | ||||||
| use FireflyIII\Enums\AccountTypeEnum; | use FireflyIII\Enums\AccountTypeEnum; | ||||||
| use FireflyIII\Enums\TransactionTypeEnum; | use FireflyIII\Enums\TransactionTypeEnum; | ||||||
| use FireflyIII\Helpers\Collector\GroupCollectorInterface; | use FireflyIII\Helpers\Collector\GroupCollectorInterface; | ||||||
| @@ -88,34 +88,25 @@ class BasicController extends Controller | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     public function basic(BasicRequest $request): JsonResponse | ||||||
|      * This endpoint is documented at: |  | ||||||
|      * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/summary/getBasicSummary
 |  | ||||||
|      * |  | ||||||
|      * @throws Exception |  | ||||||
|      */ |  | ||||||
|     public function basic(DateRequest $request): JsonResponse |  | ||||||
|     { |     { | ||||||
|         // parameters for boxes:
 |         // parameters for boxes:
 | ||||||
|         $dates        = $request->getAll(); |         ['start' => $start, 'end' => $end, 'code' => $code] = $request->attributes->all(); | ||||||
|         $start        = $dates['start']; |  | ||||||
|         $end          = $dates['end']; |  | ||||||
|         $code         = $request->get('currency_code'); |  | ||||||
|         // balance information:
 |         // balance information:
 | ||||||
|         $balanceData  = $this->getBalanceInformation($start, $end); |         $balanceData                                        = $this->getBalanceInformation($start, $end); | ||||||
|         $billData     = $this->getSubscriptionInformation($start, $end); |         $billData                                           = $this->getSubscriptionInformation($start, $end); | ||||||
|         $spentData    = $this->getLeftToSpendInfo($start, $end); |         $spentData                                          = $this->getLeftToSpendInfo($start, $end); | ||||||
|         $netWorthData = $this->getNetWorthInfo($end); |         $netWorthData                                       = $this->getNetWorthInfo($end); | ||||||
|         //                        $balanceData  = [];
 |         //                        $balanceData  = [];
 | ||||||
|         //                        $billData     = [];
 |         //                        $billData     = [];
 | ||||||
|         //                $spentData    = [];
 |         //                $spentData    = [];
 | ||||||
|         //                        $netWorthData = [];
 |         //                        $netWorthData = [];
 | ||||||
|         $total        = array_merge($balanceData, $billData, $spentData, $netWorthData); |         $total                                              = array_merge($balanceData, $billData, $spentData, $netWorthData); | ||||||
| 
 | 
 | ||||||
|         // give new keys
 |         // give new keys
 | ||||||
|         $return       = []; |         $return                                             = []; | ||||||
|         foreach ($total as $entry) { |         foreach ($total as $entry) { | ||||||
|             if (null === $code || ($code === $entry['currency_code'])) { |             if ('' === $code || ($code === $entry['currency_code'])) { | ||||||
|                 $return[$entry['key']] = $entry; |                 $return[$entry['key']] = $entry; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -589,8 +580,6 @@ class BasicController extends Controller | |||||||
| 
 | 
 | ||||||
|     private function getNetWorthInfo(Carbon $end): array |     private function getNetWorthInfo(Carbon $end): array | ||||||
|     { |     { | ||||||
|         $end->endOfDay(); |  | ||||||
| 
 |  | ||||||
|         /** @var User $user */ |         /** @var User $user */ | ||||||
|         $user           = auth()->user(); |         $user           = auth()->user(); | ||||||
|         Log::debug(sprintf('getNetWorthInfo up until "%s".', $end->format('Y-m-d H:i:s'))); |         Log::debug(sprintf('getNetWorthInfo up until "%s".', $end->format('Y-m-d H:i:s'))); | ||||||
|   | |||||||
							
								
								
									
										97
									
								
								app/Api/V1/Requests/AggregateFormRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								app/Api/V1/Requests/AggregateFormRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2025 https://github.com/ctrl-f5 | ||||||
|  |  * | ||||||
|  |  * 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\Api\V1\Requests; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Foundation\Http\FormRequest; | ||||||
|  | use Illuminate\Http\Request; | ||||||
|  | use Illuminate\Support\Facades\Log; | ||||||
|  | use Illuminate\Validation\Validator; | ||||||
|  | use RuntimeException; | ||||||
|  | 
 | ||||||
|  | abstract class AggregateFormRequest extends ApiRequest | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * @var ApiRequest[] | ||||||
|  |      */ | ||||||
|  |     protected array $requests = []; | ||||||
|  | 
 | ||||||
|  |     /** @return class-string[] */ | ||||||
|  |     abstract protected function getRequests(): array; | ||||||
|  | 
 | ||||||
|  |     public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): void | ||||||
|  |     { | ||||||
|  |         parent::initialize($query, $request, $attributes, $cookies, $files, $server, $content); | ||||||
|  | 
 | ||||||
|  |         // instantiate all subrequests and share current requests' bags with them
 | ||||||
|  |         Log::debug('Initializing AggregateFormRequest.'); | ||||||
|  |         foreach ($this->getRequests() as $config) { | ||||||
|  |             $requestClass         = is_array($config) ? array_shift($config) : $config; | ||||||
|  | 
 | ||||||
|  |             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)); | ||||||
|  | 
 | ||||||
|  |             $instance             = $this->requests[] = new $requestClass(); | ||||||
|  |             $instance->request    = $this->request; | ||||||
|  |             $instance->query      = $this->query; | ||||||
|  |             $instance->attributes = $this->attributes; | ||||||
|  |             $instance->cookies    = $this->cookies; | ||||||
|  |             $instance->files      = $this->files; | ||||||
|  |             $instance->server     = $this->server; | ||||||
|  |             $instance->headers    = $this->headers; | ||||||
|  | 
 | ||||||
|  |             if ($instance instanceof ApiRequest) { | ||||||
|  |                 $instance->handleConfig(is_array($config) ? $config : []); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Log::debug('Done initializing AggregateFormRequest.'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function rules(): array | ||||||
|  |     { | ||||||
|  |         // check all subrequests for rules and combine them
 | ||||||
|  |         return array_reduce( | ||||||
|  |             $this->requests, | ||||||
|  |             static fn (array $rules, FormRequest $request) => $rules | ||||||
|  |                 + ( | ||||||
|  |                     method_exists($request, 'rules') | ||||||
|  |                     ? $request->rules() | ||||||
|  |                     : [] | ||||||
|  |                 ), | ||||||
|  |             [], | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function withValidator(Validator $validator): void | ||||||
|  |     { | ||||||
|  |         // register all subrequests' validators
 | ||||||
|  |         foreach ($this->requests as $request) { | ||||||
|  |             if (method_exists($request, 'withValidator')) { | ||||||
|  |                 Log::debug(sprintf('Process withValidator from class %s', get_class($request))); | ||||||
|  |                 $request->withValidator($validator); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								app/Api/V1/Requests/ApiRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								app/Api/V1/Requests/ApiRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2025 https://github.com/ctrl-f5 | ||||||
|  |  * | ||||||
|  |  * 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\Api\V1\Requests; | ||||||
|  | 
 | ||||||
|  | use FireflyIII\Support\Request\ChecksLogin; | ||||||
|  | use FireflyIII\Support\Request\ConvertsDataTypes; | ||||||
|  | use Illuminate\Foundation\Http\FormRequest; | ||||||
|  | 
 | ||||||
|  | class ApiRequest extends FormRequest | ||||||
|  | { | ||||||
|  |     use ChecksLogin; | ||||||
|  |     use ConvertsDataTypes; | ||||||
|  | 
 | ||||||
|  |     protected string $required = ''; | ||||||
|  | 
 | ||||||
|  |     public function handleConfig(array $config): void | ||||||
|  |     { | ||||||
|  |         if (in_array('required', $config, true)) { | ||||||
|  |             $this->required = 'required'; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -48,10 +48,12 @@ class AutocompleteRequest extends FormRequest | |||||||
|         // remove 'initial balance' from allowed types. its internal
 |         // remove 'initial balance' from allowed types. its internal
 | ||||||
|         $array = array_diff($array, [AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::RECONCILIATION->value]); |         $array = array_diff($array, [AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::RECONCILIATION->value]); | ||||||
| 
 | 
 | ||||||
|  |         $date  = $this->getCarbonDate('date') ?? today(config('app.timezone')); | ||||||
|  | 
 | ||||||
|         return [ |         return [ | ||||||
|             'types' => $array, |             'types' => $array, | ||||||
|             'query' => $this->convertString('query'), |             'query' => $this->convertString('query'), | ||||||
|             'date'  => $this->getCarbonDate('date'), |             'date'  => $date->endOfDay(), | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -1,83 +0,0 @@ | |||||||
| <?php |  | ||||||
| 
 |  | ||||||
| /* |  | ||||||
|  * DateRequest.php |  | ||||||
|  * Copyright (c) 2021 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\Api\V1\Requests\Data; |  | ||||||
| 
 |  | ||||||
| use FireflyIII\Exceptions\ValidationException; |  | ||||||
| use FireflyIII\Support\Request\ChecksLogin; |  | ||||||
| use FireflyIII\Support\Request\ConvertsDataTypes; |  | ||||||
| use Illuminate\Foundation\Http\FormRequest; |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Request class for end points that require date parameters. |  | ||||||
|  * |  | ||||||
|  * Class DateRequest |  | ||||||
|  */ |  | ||||||
| class DateRequest extends FormRequest |  | ||||||
| { |  | ||||||
|     use ChecksLogin; |  | ||||||
|     use ConvertsDataTypes; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Get all data from the request. |  | ||||||
|      */ |  | ||||||
|     public function getAll(): array |  | ||||||
|     { |  | ||||||
|         $start         = $this->getCarbonDate('start'); |  | ||||||
|         $end           = $this->getCarbonDate('end'); |  | ||||||
|         if (null === $start) { |  | ||||||
|             $start = now()->startOfMonth(); |  | ||||||
|         } |  | ||||||
|         if (null === $end) { |  | ||||||
|             $end = now()->endOfMonth(); |  | ||||||
|         } |  | ||||||
|         // sanity check on dates:
 |  | ||||||
|         [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         $start->startOfDay(); |  | ||||||
|         $end->endOfDay(); |  | ||||||
|         if ($start->diffInYears($end, true) > 5) { |  | ||||||
|             throw new ValidationException('Date range out of range.'); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return [ |  | ||||||
|             'start' => $start, |  | ||||||
|             'end'   => $end, |  | ||||||
|             'date'  => $this->getCarbonDate('date'), |  | ||||||
|         ]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * The rules that the incoming request must be matched against. |  | ||||||
|      */ |  | ||||||
|     public function rules(): array |  | ||||||
|     { |  | ||||||
|         return [ |  | ||||||
|             'date'  => 'date|after:1970-01-02|before:2038-01-17', |  | ||||||
|             'start' => 'date|after:1970-01-02|before:2038-01-17|before:end|required_with:end', |  | ||||||
|             'end'   => 'date|after:1970-01-02|before:2038-01-17|after:start|required_with:start', |  | ||||||
|         ]; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										57
									
								
								app/Api/V1/Requests/DateRangeRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								app/Api/V1/Requests/DateRangeRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2025 https://github.com/ctrl-f5 | ||||||
|  |  * | ||||||
|  |  * 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\Api\V1\Requests; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Validation\Validator; | ||||||
|  | 
 | ||||||
|  | class DateRangeRequest extends ApiRequest | ||||||
|  | { | ||||||
|  |     public function rules(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'start' => sprintf('date|after:1970-01-02|before:2038-01-17|before:end|required_with:end|%s', $this->required), | ||||||
|  |             'end'   => sprintf('date|after:1970-01-02|before:2038-01-17|after:start|required_with:start|%s', $this->required), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function withValidator(Validator $validator): void | ||||||
|  |     { | ||||||
|  |         $validator->after( | ||||||
|  |             function (Validator $validator): void { | ||||||
|  |                 if ($validator->failed()) { | ||||||
|  |                     // set null values
 | ||||||
|  |                     $this->attributes->set('start', null); | ||||||
|  |                     $this->attributes->set('end', null); | ||||||
|  | 
 | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 $start = $this->getCarbonDate('start')?->startOfDay(); | ||||||
|  |                 $end   = $this->getCarbonDate('end')?->endOfDay(); | ||||||
|  | 
 | ||||||
|  |                 $this->attributes->set('start', $start); | ||||||
|  |                 $this->attributes->set('end', $end); | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										58
									
								
								app/Api/V1/Requests/DateRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								app/Api/V1/Requests/DateRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2025 https://github.com/ctrl-f5 | ||||||
|  |  * | ||||||
|  |  * 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\Api\V1\Requests; | ||||||
|  | 
 | ||||||
|  | use Carbon\Carbon; | ||||||
|  | use Illuminate\Validation\Validator; | ||||||
|  | 
 | ||||||
|  | class DateRequest extends ApiRequest | ||||||
|  | { | ||||||
|  |     public function rules(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'date'  => 'date|after:1970-01-02|before:2038-01-17|'.$this->required, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function withValidator(Validator $validator): void | ||||||
|  |     { | ||||||
|  |         $validator->after( | ||||||
|  |             function (Validator $validator): void { | ||||||
|  |                 if ($validator->failed()) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 $date  = $this->getCarbonDate('date')?->endOfDay(); | ||||||
|  | 
 | ||||||
|  |                 // if we also have a range, date must be in that range
 | ||||||
|  |                 $start = $this->attributes->get('start'); | ||||||
|  |                 $end   = $this->attributes->get('end'); | ||||||
|  |                 if ($date instanceof Carbon && $start instanceof Carbon && $end instanceof Carbon && !$date->between($start, $end)) { | ||||||
|  |                     $validator->errors()->add('date', (string)trans('validation.between_date')); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $this->attributes->set('date', $date); | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										57
									
								
								app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | declare(strict_types=1); | ||||||
|  | /* | ||||||
|  |  * AccountTypeApiRequest.php | ||||||
|  |  * Copyright (c) 2025 https://github.com/ctrl-f5 | ||||||
|  |  * | ||||||
|  |  * 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\Api\V1\Requests\Models\Account; | ||||||
|  | 
 | ||||||
|  | use FireflyIII\Api\V1\Requests\ApiRequest; | ||||||
|  | use FireflyIII\Support\Http\Api\AccountFilter; | ||||||
|  | use Illuminate\Validation\Validator; | ||||||
|  | 
 | ||||||
|  | class AccountTypeApiRequest extends ApiRequest | ||||||
|  | { | ||||||
|  |     use AccountFilter; | ||||||
|  | 
 | ||||||
|  |     public function rules(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'type'  => sprintf('in:%s', implode(',', array_keys($this->types))), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function withValidator(Validator $validator): void | ||||||
|  |     { | ||||||
|  |         $validator->after( | ||||||
|  |             function (Validator $validator): void { | ||||||
|  |                 if ($validator->failed()) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $type = $this->convertString('type', 'all'); | ||||||
|  |                 $this->attributes->add([ | ||||||
|  |                     'type'  => $type, | ||||||
|  |                     'types' => $this->mapAccountTypes($type), | ||||||
|  |                 ]); | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -23,77 +23,21 @@ declare(strict_types=1); | |||||||
| 
 | 
 | ||||||
| namespace FireflyIII\Api\V1\Requests\Models\Account; | namespace FireflyIII\Api\V1\Requests\Models\Account; | ||||||
| 
 | 
 | ||||||
| use Carbon\Carbon; | use FireflyIII\Api\V1\Requests\AggregateFormRequest; | ||||||
|  | use FireflyIII\Api\V1\Requests\DateRangeRequest; | ||||||
|  | use FireflyIII\Api\V1\Requests\DateRequest; | ||||||
|  | use FireflyIII\Api\V1\Requests\PaginationRequest; | ||||||
| use FireflyIII\Models\Account; | use FireflyIII\Models\Account; | ||||||
| use FireflyIII\Rules\IsValidSortInstruction; |  | ||||||
| use FireflyIII\Support\Facades\Preferences; |  | ||||||
| use FireflyIII\Support\Http\Api\AccountFilter; |  | ||||||
| use FireflyIII\Support\Request\ConvertsDataTypes; |  | ||||||
| use FireflyIII\User; |  | ||||||
| use Illuminate\Foundation\Http\FormRequest; |  | ||||||
| use Illuminate\Validation\Validator; |  | ||||||
| 
 | 
 | ||||||
| class ShowRequest extends FormRequest | class ShowRequest extends AggregateFormRequest | ||||||
| { | { | ||||||
|     use AccountFilter; |     protected function getRequests(): array | ||||||
|     use ConvertsDataTypes; |  | ||||||
| 
 |  | ||||||
|     public function getParameters(): array |  | ||||||
|     { |     { | ||||||
|         $limit = $this->convertInteger('limit'); |  | ||||||
|         if (0 === $limit) { |  | ||||||
|             // get default for user:
 |  | ||||||
|             /** @var User $user */ |  | ||||||
|             $user  = auth()->user(); |  | ||||||
|             $limit = (int)Preferences::getForUser($user, 'listPageSize', 50)->data; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         $page  = $this->convertInteger('page'); |  | ||||||
|         $page  = min(max(1, $page), 2 ** 16); |  | ||||||
| 
 |  | ||||||
|         return [ |         return [ | ||||||
|             'type'  => $this->convertString('type', 'all'), |             [PaginationRequest::class, 'sort_class' => Account::class], | ||||||
|             'limit' => $limit, |             DateRangeRequest::class, | ||||||
|             'sort'  => $this->convertSortParameters('sort', Account::class), |             DateRequest::class, | ||||||
|             'page'  => $page, |             AccountTypeApiRequest::class, | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     public function rules(): array |  | ||||||
|     { |  | ||||||
|         $keys = implode(',', array_keys($this->types)); |  | ||||||
| 
 |  | ||||||
|         return [ |  | ||||||
|             'date'  => 'date', |  | ||||||
|             'start' => 'date|present_with:end|before_or_equal:end|before:2038-01-17|after:1970-01-02', |  | ||||||
|             'end'   => 'date|present_with:start|after_or_equal:start|before:2038-01-17|after:1970-01-02', |  | ||||||
|             'sort'  => ['nullable', new IsValidSortInstruction(Account::class)], |  | ||||||
|             'type'  => sprintf('in:%s', $keys), |  | ||||||
|             'limit' => 'numeric|min:1|max:131337', |  | ||||||
|             'page'  => 'numeric|min:1|max:131337', |  | ||||||
|         ]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function withValidator(Validator $validator): void |  | ||||||
|     { |  | ||||||
|         $validator->after( |  | ||||||
|             function (Validator $validator): void { |  | ||||||
|                 if (count($validator->failed()) > 0) { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
|                 $data = $validator->getData(); |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|                 if (array_key_exists('date', $data) && array_key_exists('start', $data) && array_key_exists('end', $data)) { |  | ||||||
|                     // assume valid dates, before we got here.
 |  | ||||||
|                     $start = Carbon::parse($data['start'], config('app.timezone'))->startOfDay(); |  | ||||||
|                     $end   = Carbon::parse($data['end'], config('app.timezone'))->endOfDay(); |  | ||||||
|                     $date  = Carbon::parse($data['date'], config('app.timezone')); |  | ||||||
|                     if (!$date->between($start, $end)) { |  | ||||||
|                         $validator->errors()->add('date', (string)trans('validation.between_date')); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | declare(strict_types=1); | ||||||
|  | /* | ||||||
|  |  * CurrencyCodeRequest.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\Api\V1\Requests\Models\TransactionCurrency; | ||||||
|  | 
 | ||||||
|  | use FireflyIII\Api\V1\Requests\ApiRequest; | ||||||
|  | use Illuminate\Validation\Validator; | ||||||
|  | 
 | ||||||
|  | class CurrencyCodeRequest extends ApiRequest | ||||||
|  | { | ||||||
|  |     public function rules(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'code' => sprintf('exists:transaction_currencies,code|%s', $this->required), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function withValidator(Validator $validator): void | ||||||
|  |     { | ||||||
|  |         $validator->after( | ||||||
|  |             function (Validator $validator): void { | ||||||
|  |                 if (!$validator->valid()) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 $code = $this->convertString('code', ''); | ||||||
|  |                 $this->attributes->set('code', $code); | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										84
									
								
								app/Api/V1/Requests/PaginationRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								app/Api/V1/Requests/PaginationRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  * Copyright (c) 2025 https://github.com/ctrl-f5 | ||||||
|  |  * | ||||||
|  |  * 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\Api\V1\Requests; | ||||||
|  | 
 | ||||||
|  | use FireflyIII\Rules\IsValidSortInstruction; | ||||||
|  | use FireflyIII\Support\Facades\Preferences; | ||||||
|  | use FireflyIII\User; | ||||||
|  | use Illuminate\Validation\Validator; | ||||||
|  | use RuntimeException; | ||||||
|  | 
 | ||||||
|  | class PaginationRequest extends ApiRequest | ||||||
|  | { | ||||||
|  |     private ?string $sortClass = null; | ||||||
|  | 
 | ||||||
|  |     public function handleConfig(array $config): void | ||||||
|  |     { | ||||||
|  |         parent::handleConfig($config); | ||||||
|  | 
 | ||||||
|  |         $this->sortClass = $config['sort_class'] ?? null; | ||||||
|  | 
 | ||||||
|  |         if (!$this->sortClass) { | ||||||
|  |             throw new RuntimeException('PaginationRequest requires a sort_class config'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function rules(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'sort'  => ['nullable', new IsValidSortInstruction((string)$this->sortClass)], | ||||||
|  |             'limit' => 'numeric|min:1|max:131337', | ||||||
|  |             'page'  => 'numeric|min:1|max:131337', | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function withValidator(Validator $validator): void | ||||||
|  |     { | ||||||
|  |         $validator->after( | ||||||
|  |             function (Validator $validator): void { | ||||||
|  |                 if ($validator->failed()) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $limit  = $this->convertInteger('limit'); | ||||||
|  |                 if (0 === $limit) { | ||||||
|  |                     // get default for user:
 | ||||||
|  |                     /** @var User $user */ | ||||||
|  |                     $user  = auth()->user(); | ||||||
|  |                     $limit = (int)Preferences::getForUser($user, 'listPageSize', 50)->data; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $page   = $this->convertInteger('page'); | ||||||
|  |                 $page   = min(max(1, $page), 2 ** 16); | ||||||
|  |                 $offset = ($page - 1) * $limit; | ||||||
|  |                 $sort   = $this->sortClass ? $this->convertSortParameters('sort', $this->sortClass) : $this->get('sort'); | ||||||
|  | 
 | ||||||
|  |                 $this->attributes->set('limit', $limit); | ||||||
|  |                 $this->attributes->set('sort', $sort); | ||||||
|  |                 $this->attributes->set('page', $page); | ||||||
|  |                 $this->attributes->set('offset', $offset); | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								app/Api/V1/Requests/Summary/BasicRequest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/Api/V1/Requests/Summary/BasicRequest.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | declare(strict_types=1); | ||||||
|  | /* | ||||||
|  |  * BasicRequest.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\Api\V1\Requests\Summary; | ||||||
|  | 
 | ||||||
|  | use FireflyIII\Api\V1\Requests\AggregateFormRequest; | ||||||
|  | use FireflyIII\Api\V1\Requests\DateRangeRequest; | ||||||
|  | use FireflyIII\Api\V1\Requests\Models\TransactionCurrency\CurrencyCodeRequest; | ||||||
|  | 
 | ||||||
|  | class BasicRequest extends AggregateFormRequest | ||||||
|  | { | ||||||
|  |     protected function getRequests(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             [DateRangeRequest::class, 'required'], | ||||||
|  |             CurrencyCodeRequest::class, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -46,7 +46,7 @@ class SetsLatestVersion extends Command | |||||||
| 
 | 
 | ||||||
|             return 0; |             return 0; | ||||||
|         } |         } | ||||||
|         FireflyConfig::set('ff3_version', config('firefly.version')); |         FireflyConfig::set('ff3_build_time', (int) config('firefly.build_time')); | ||||||
|         $this->friendlyInfo('Updated version.'); |         $this->friendlyInfo('Updated version.'); | ||||||
| 
 | 
 | ||||||
|         return 0; |         return 0; | ||||||
|   | |||||||
| @@ -87,7 +87,7 @@ class UpgradesDatabase extends Command | |||||||
|             $this->call($command, $args); |             $this->call($command, $args); | ||||||
|         } |         } | ||||||
|         // index will set FF3 version.
 |         // index will set FF3 version.
 | ||||||
|         FireflyConfig::set('ff3_version', (string) config('firefly.version')); |         FireflyConfig::set('ff3_build_time', (int) config('firefly.build_time')); | ||||||
| 
 | 
 | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -84,8 +84,10 @@ class AttachmentFactory | |||||||
|         return $attachment; |         return $attachment; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function setUser(User $user): void |     public function setUser(User $user): static | ||||||
|     { |     { | ||||||
|         $this->user = $user; |         $this->user = $user; | ||||||
|  | 
 | ||||||
|  |         return $this; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ namespace FireflyIII\Factory; | |||||||
| use FireflyIII\Models\PiggyBank; | use FireflyIII\Models\PiggyBank; | ||||||
| use FireflyIII\Models\TransactionJournal; | use FireflyIII\Models\TransactionJournal; | ||||||
| use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; | use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; | ||||||
|  | use Illuminate\Support\Facades\Log; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Create piggy bank events. |  * Create piggy bank events. | ||||||
| @@ -36,9 +37,9 @@ class PiggyBankEventFactory | |||||||
| { | { | ||||||
|     public function create(TransactionJournal $journal, ?PiggyBank $piggyBank): void |     public function create(TransactionJournal $journal, ?PiggyBank $piggyBank): void | ||||||
|     { |     { | ||||||
|         app('log')->debug(sprintf('Now in PiggyBankEventCreate for a %s', $journal->transactionType->type)); |         Log::debug(sprintf('Now in PiggyBankEventCreate for a %s', $journal->transactionType->type)); | ||||||
|         if (!$piggyBank instanceof PiggyBank) { |         if (!$piggyBank instanceof PiggyBank) { | ||||||
|             app('log')->debug('Piggy bank is null'); |             Log::debug('Piggy bank is null'); | ||||||
| 
 | 
 | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @@ -49,7 +50,7 @@ class PiggyBankEventFactory | |||||||
| 
 | 
 | ||||||
|         $amount     = $piggyRepos->getExactAmount($piggyBank, $journal); |         $amount     = $piggyRepos->getExactAmount($piggyBank, $journal); | ||||||
|         if (0 === bccomp($amount, '0')) { |         if (0 === bccomp($amount, '0')) { | ||||||
|             app('log')->debug('Amount is zero, will not create event.'); |             Log::debug('Amount is zero, will not create event.'); | ||||||
| 
 | 
 | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -221,10 +221,26 @@ class TransactionJournalFactory | |||||||
|         ]; |         ]; | ||||||
|         Log::debug('Source info:', $sourceInfo); |         Log::debug('Source info:', $sourceInfo); | ||||||
|         Log::debug('Destination info:', $destInfo); |         Log::debug('Destination info:', $destInfo); | ||||||
|         $sourceAccount         = $this->getAccount($type->type, 'source', $sourceInfo); |         $destinationAccount    = null; | ||||||
|         $destinationAccount    = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount); |         $sourceAccount         = null; | ||||||
|  |         if (TransactionTypeEnum::DEPOSIT->value === $type->type) { | ||||||
|  |             Log::debug('Transaction type is deposit, start with destination first.'); | ||||||
|  |             $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo); | ||||||
|  |             $sourceAccount      = $this->getAccount($type->type, 'source', $sourceInfo, $destinationAccount); | ||||||
|  |         } | ||||||
|  |         if (TransactionTypeEnum::DEPOSIT->value !== $type->type) { | ||||||
|  |             Log::debug('Transaction type is not deposit, start with source first.'); | ||||||
|  |             $sourceAccount      = $this->getAccount($type->type, 'source', $sourceInfo); | ||||||
|  |             $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         Log::debug('Done with getAccount(2x)'); |         Log::debug('Done with getAccount(2x)'); | ||||||
| 
 | 
 | ||||||
|  |         // there is a safety catch here. If either account is NULL, they will be replaced with the cash account.
 | ||||||
|  |         if (null === $destinationAccount) { | ||||||
|  |             Log::warning('Destination account is NULL, will replace with cash account.'); | ||||||
|  |             $destinationAccount = $this->accountRepository->getCashAccount(); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         // this is the moment for a reconciliation sanity check (again).
 |         // this is the moment for a reconciliation sanity check (again).
 | ||||||
|         if (TransactionTypeEnum::RECONCILIATION->value === $type->type) { |         if (TransactionTypeEnum::RECONCILIATION->value === $type->type) { | ||||||
|   | |||||||
| @@ -137,9 +137,12 @@ class MonthReportGenerator implements ReportGeneratorInterface | |||||||
|         ; |         ; | ||||||
|         $journals          = $collector->getExtractedJournals(); |         $journals          = $collector->getExtractedJournals(); | ||||||
|         $journals          = array_reverse($journals, true); |         $journals          = array_reverse($journals, true); | ||||||
|         // this call is correct.
 | 
 | ||||||
|         Log::debug(sprintf('getAuditReport: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); |         Log::debug(sprintf('getAuditReport: Call accountsBalancesOptimized with date/time "%s"', $date->toIso8601String())); | ||||||
|         $dayBeforeBalance  = Steam::finalAccountBalance($account, $date); |         // 2025-10-08 replace with accountsBalancesOptimized.
 | ||||||
|  |         // $dayBeforeBalance  = Steam::finalAccountBalance($account, $date);
 | ||||||
|  |         $dayBeforeBalance  = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]; | ||||||
|  | 
 | ||||||
|         $startBalance      = $dayBeforeBalance['balance']; |         $startBalance      = $dayBeforeBalance['balance']; | ||||||
|         $primaryCurrency   = app('amount')->getPrimaryCurrencyByUserGroup($account->user->userGroup); |         $primaryCurrency   = app('amount')->getPrimaryCurrencyByUserGroup($account->user->userGroup); | ||||||
|         $currency          = $accountRepository->getAccountCurrency($account) ?? $primaryCurrency; |         $currency          = $accountRepository->getAccountCurrency($account) ?? $primaryCurrency; | ||||||
| @@ -176,12 +179,14 @@ class MonthReportGenerator implements ReportGeneratorInterface | |||||||
|         // call is correct.
 |         // call is correct.
 | ||||||
|         Log::debug(sprintf('getAuditReport end: Call finalAccountBalance with date/time "%s"', $this->end->toIso8601String())); |         Log::debug(sprintf('getAuditReport end: Call finalAccountBalance with date/time "%s"', $this->end->toIso8601String())); | ||||||
| 
 | 
 | ||||||
|  |         // 2025-10-08 replace with accountsBalancesOptimized:
 | ||||||
|         return [ |         return [ | ||||||
|             'journals'         => $journals, |             'journals'         => $journals, | ||||||
|             'currency'         => $currency, |             'currency'         => $currency, | ||||||
|             'exists'           => 0 !== count($journals), |             'exists'           => 0 !== count($journals), | ||||||
|             'end'              => $this->end->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)), |             'end'              => $this->end->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)), | ||||||
|             'endBalance'       => Steam::finalAccountBalance($account, $this->end)['balance'], |             // 'endBalance'       => Steam::finalAccountBalance($account, $this->end)['balance'],
 | ||||||
|  |             'endBalance'       => Steam::accountsBalancesOptimized(new Collection()->push($account), $this->end)[$account->id]['balance'], | ||||||
|             'dayBefore'        => $date->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)), |             'dayBefore'        => $date->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)), | ||||||
|             'dayBeforeBalance' => $dayBeforeBalance, |             'dayBeforeBalance' => $dayBeforeBalance, | ||||||
|         ]; |         ]; | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ use FireflyIII\Enums\WebhookTrigger; | |||||||
| use FireflyIII\Events\RequestedSendWebhookMessages; | use FireflyIII\Events\RequestedSendWebhookMessages; | ||||||
| use FireflyIII\Events\StoredTransactionGroup; | use FireflyIII\Events\StoredTransactionGroup; | ||||||
| use FireflyIII\Generator\Webhook\MessageGeneratorInterface; | use FireflyIII\Generator\Webhook\MessageGeneratorInterface; | ||||||
|  | use FireflyIII\Models\Transaction; | ||||||
| use FireflyIII\Models\TransactionJournal; | use FireflyIII\Models\TransactionJournal; | ||||||
| use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface; | use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface; | ||||||
| use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; | use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; | ||||||
| @@ -105,10 +106,18 @@ class StoredGroupEventHandler | |||||||
| 
 | 
 | ||||||
|         /** @var TransactionJournal $journal */ |         /** @var TransactionJournal $journal */ | ||||||
|         foreach ($event->transactionGroup->transactionJournals as $journal) { |         foreach ($event->transactionGroup->transactionJournals as $journal) { | ||||||
|  |             /** @var null|Transaction $source */ | ||||||
|             $source     = $journal->transactions()->where('amount', '<', '0')->first(); |             $source     = $journal->transactions()->where('amount', '<', '0')->first(); | ||||||
|  | 
 | ||||||
|  |             /** @var null|Transaction $dest */ | ||||||
|             $dest       = $journal->transactions()->where('amount', '>', '0')->first(); |             $dest       = $journal->transactions()->where('amount', '>', '0')->first(); | ||||||
|             $repository->deleteStatisticsForModel($source->account, $journal->date); | 
 | ||||||
|             $repository->deleteStatisticsForModel($dest->account, $journal->date); |             if (null !== $source) { | ||||||
|  |                 $repository->deleteStatisticsForModel($source->account, $journal->date); | ||||||
|  |             } | ||||||
|  |             if (null !== $dest) { | ||||||
|  |                 $repository->deleteStatisticsForModel($dest->account, $journal->date); | ||||||
|  |             } | ||||||
|             $categories = $journal->categories; |             $categories = $journal->categories; | ||||||
|             $tags       = $journal->tags; |             $tags       = $journal->tags; | ||||||
|             $budgets    = $journal->budgets; |             $budgets    = $journal->budgets; | ||||||
|   | |||||||
| @@ -62,12 +62,13 @@ trait AccountCollection | |||||||
|                 if (null === $account) { |                 if (null === $account) { | ||||||
|                     continue; |                     continue; | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|  |                 // 2025-10-08 replace with accountsBalancesOptimized
 | ||||||
|                 // the balance must be found BEFORE the transaction date.
 |                 // the balance must be found BEFORE the transaction date.
 | ||||||
|                 // so sub one second. This is not perfect, but works well enough.
 |                 // so inclusive = false
 | ||||||
|                 $date      = clone $transaction['date']; |                 Log::debug(sprintf('accountBalanceIs: Call accountsBalancesOptimized with date/time "%s"', $transaction['date']->toIso8601String())); | ||||||
|                 $date->subSecond(); |                 $balance   = Steam::accountsBalancesOptimized(new Collection()->push($account), $transaction['date'], convertToPrimary: null, inclusive: false)[$account->id]; | ||||||
|                 Log::debug(sprintf('accountBalanceIs: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); |                 // $balance   = Steam::finalAccountBalance($account, $date);
 | ||||||
|                 $balance   = Steam::finalAccountBalance($account, $date); |  | ||||||
|                 $result    = bccomp((string) $balance['balance'], $value); |                 $result    = bccomp((string) $balance['balance'], $value); | ||||||
|                 Log::debug(sprintf('"%s" vs "%s" is %d', $balance['balance'], $value, $result)); |                 Log::debug(sprintf('"%s" vs "%s" is %d', $balance['balance'], $value, $result)); | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -25,8 +25,6 @@ declare(strict_types=1); | |||||||
| namespace FireflyIII\Helpers\Report; | namespace FireflyIII\Helpers\Report; | ||||||
| 
 | 
 | ||||||
| use Carbon\Carbon; | use Carbon\Carbon; | ||||||
| use Deprecated; |  | ||||||
| use FireflyIII\Enums\AccountTypeEnum; |  | ||||||
| use FireflyIII\Exceptions\FireflyException; | use FireflyIII\Exceptions\FireflyException; | ||||||
| use FireflyIII\Models\Account; | use FireflyIII\Models\Account; | ||||||
| use FireflyIII\Models\UserGroup; | use FireflyIII\Models\UserGroup; | ||||||
| @@ -130,55 +128,4 @@ class NetWorth implements NetWorthInterface | |||||||
|         $this->accountRepository->setUserGroup($userGroup); |         $this->accountRepository->setUserGroup($userGroup); | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     #[Deprecated]
 |  | ||||||
|     public function sumNetWorthByCurrency(Carbon $date): array |  | ||||||
|     { |  | ||||||
|         /** |  | ||||||
|          * Collect accounts |  | ||||||
|          */ |  | ||||||
|         $accounts = $this->getAccounts(); |  | ||||||
|         $return   = []; |  | ||||||
|         Log::debug(sprintf('SumNetWorth: accountsBalancesOptimized("%s")', $date->format('Y-m-d H:i:s'))); |  | ||||||
|         $balances = Steam::accountsBalancesOptimized($accounts, $date); |  | ||||||
|         foreach ($accounts as $account) { |  | ||||||
|             $currency                     = $this->accountRepository->getAccountCurrency($account); |  | ||||||
|             $balance                      = $balances[$account->id]['balance'] ?? '0'; |  | ||||||
| 
 |  | ||||||
|             // always subtract virtual balance.
 |  | ||||||
|             $virtualBalance               = $account->virtual_balance; |  | ||||||
|             if ('' !== $virtualBalance) { |  | ||||||
|                 $balance = bcsub($balance, (string) $virtualBalance); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             $return[$currency->id] ??= [ |  | ||||||
|                 'id'             => (string) $currency->id, |  | ||||||
|                 'name'           => $currency->name, |  | ||||||
|                 'symbol'         => $currency->symbol, |  | ||||||
|                 'code'           => $currency->code, |  | ||||||
|                 'decimal_places' => $currency->decimal_places, |  | ||||||
|                 'sum'            => '0', |  | ||||||
|             ]; |  | ||||||
|             $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], (string) $balance); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return $return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private function getAccounts(): Collection |  | ||||||
|     { |  | ||||||
|         $accounts = $this->accountRepository->getAccountsByType( |  | ||||||
|             [AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value] |  | ||||||
|         ); |  | ||||||
|         $filtered = new Collection(); |  | ||||||
| 
 |  | ||||||
|         /** @var Account $account */ |  | ||||||
|         foreach ($accounts as $account) { |  | ||||||
|             if (1 === (int) $this->accountRepository->getMetaValue($account, 'include_net_worth')) { |  | ||||||
|                 $filtered->push($account); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return $filtered; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -25,7 +25,6 @@ declare(strict_types=1); | |||||||
| namespace FireflyIII\Helpers\Report; | namespace FireflyIII\Helpers\Report; | ||||||
| 
 | 
 | ||||||
| use Carbon\Carbon; | use Carbon\Carbon; | ||||||
| use Deprecated; |  | ||||||
| use FireflyIII\Models\UserGroup; | use FireflyIII\Models\UserGroup; | ||||||
| use FireflyIII\User; | use FireflyIII\User; | ||||||
| use Illuminate\Contracts\Auth\Authenticatable; | use Illuminate\Contracts\Auth\Authenticatable; | ||||||
| @@ -49,12 +48,4 @@ interface NetWorthInterface | |||||||
|     public function setUser(Authenticatable|User|null $user): void; |     public function setUser(Authenticatable|User|null $user): void; | ||||||
| 
 | 
 | ||||||
|     public function setUserGroup(UserGroup $userGroup): void; |     public function setUserGroup(UserGroup $userGroup): void; | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * TODO move to repository |  | ||||||
|      * |  | ||||||
|      * Same as above but cleaner function with less dependencies. |  | ||||||
|      */ |  | ||||||
|     #[Deprecated]
 |  | ||||||
|     public function sumNetWorthByCurrency(Carbon $date): array; |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -74,32 +74,30 @@ class IndexController extends Controller | |||||||
|      */ |      */ | ||||||
|     public function inactive(Request $request, string $objectType) |     public function inactive(Request $request, string $objectType) | ||||||
|     { |     { | ||||||
|         $inactivePage  = true; |         $inactivePage = true; | ||||||
|         $subTitle      = (string) trans(sprintf('firefly.%s_accounts_inactive', $objectType)); |         $subTitle     = (string) trans(sprintf('firefly.%s_accounts_inactive', $objectType)); | ||||||
|         $subTitleIcon  = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); |         $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); | ||||||
|         $types         = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); |         $types        = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); | ||||||
|         $collection    = $this->repository->getInactiveAccountsByType($types); |         $collection   = $this->repository->getInactiveAccountsByType($types); | ||||||
|         $total         = $collection->count(); |         $total        = $collection->count(); | ||||||
|         $page          = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page'); |         $page         = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page'); | ||||||
|         $pageSize      = (int) app('preferences')->get('listPageSize', 50)->data; |         $pageSize     = (int) app('preferences')->get('listPageSize', 50)->data; | ||||||
|         $accounts      = $collection->slice(($page - 1) * $pageSize, $pageSize); |         $accounts     = $collection->slice(($page - 1) * $pageSize, $pageSize); | ||||||
|         unset($collection); |         unset($collection); | ||||||
| 
 | 
 | ||||||
|         /** @var Carbon $start */ |         /** @var Carbon $start */ | ||||||
|         $start         = clone session('start', today(config('app.timezone'))->startOfMonth()); |         $start        = clone session('start', today(config('app.timezone'))->startOfMonth()); | ||||||
| 
 | 
 | ||||||
|         /** @var Carbon $end */ |         /** @var Carbon $end */ | ||||||
|         $end           = clone session('end', today(config('app.timezone'))->endOfMonth()); |         $end          = clone session('end', today(config('app.timezone'))->endOfMonth()); | ||||||
| 
 | 
 | ||||||
|         // #10618 go to the end of the previous day.
 |         $ids          = $accounts->pluck('id')->toArray(); | ||||||
|         $start->subSecond(); |         Log::debug(sprintf('inactive start: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); | ||||||
| 
 |         [ | ||||||
|         $ids           = $accounts->pluck('id')->toArray(); |             $startBalances, | ||||||
|         Log::debug(sprintf('inactive start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); |             $endBalances, | ||||||
|         Log::debug(sprintf('inactive end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); |         ]             = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary); | ||||||
|         $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); |         $activities   = Steam::getLastActivities($ids); | ||||||
|         $endBalances   = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); |  | ||||||
|         $activities    = Steam::getLastActivities($ids); |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         $accounts->each( |         $accounts->each( | ||||||
| @@ -119,7 +117,7 @@ class IndexController extends Controller | |||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         // make paginator:
 |         // make paginator:
 | ||||||
|         $accounts      = new LengthAwarePaginator($accounts, $total, $pageSize, $page); |         $accounts     = new LengthAwarePaginator($accounts, $total, $pageSize, $page); | ||||||
|         $accounts->setPath(route('accounts.inactive.index', [$objectType])); |         $accounts->setPath(route('accounts.inactive.index', [$objectType])); | ||||||
| 
 | 
 | ||||||
|         return view('accounts.index', compact('objectType', 'inactivePage', 'subTitleIcon', 'subTitle', 'page', 'accounts')); |         return view('accounts.index', compact('objectType', 'inactivePage', 'subTitleIcon', 'subTitle', 'page', 'accounts')); | ||||||
| @@ -169,14 +167,12 @@ class IndexController extends Controller | |||||||
|         /** @var Carbon $end */ |         /** @var Carbon $end */ | ||||||
|         $end           = clone session('end', today(config('app.timezone'))->endOfMonth()); |         $end           = clone session('end', today(config('app.timezone'))->endOfMonth()); | ||||||
| 
 | 
 | ||||||
|         // #10618 go to the end of the previous day.
 |  | ||||||
|         $start->subSecond(); |  | ||||||
| 
 |  | ||||||
|         $ids           = $accounts->pluck('id')->toArray(); |         $ids           = $accounts->pluck('id')->toArray(); | ||||||
|         Log::debug(sprintf('index start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); |         Log::debug(sprintf('index: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); | ||||||
|         Log::debug(sprintf('index end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); |         [ | ||||||
|         $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); |             $startBalances, | ||||||
|         $endBalances   = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); |             $endBalances, | ||||||
|  |         ]              = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary); | ||||||
|         $activities    = Steam::getLastActivities($ids); |         $activities    = Steam::getLastActivities($ids); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ use FireflyIII\User; | |||||||
| use Illuminate\Contracts\View\Factory; | use Illuminate\Contracts\View\Factory; | ||||||
| use Illuminate\Http\RedirectResponse; | use Illuminate\Http\RedirectResponse; | ||||||
| use Illuminate\Routing\Redirector; | use Illuminate\Routing\Redirector; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
| use Illuminate\Support\Facades\Log; | use Illuminate\Support\Facades\Log; | ||||||
| use Illuminate\View\View; | use Illuminate\View\View; | ||||||
| 
 | 
 | ||||||
| @@ -113,13 +114,20 @@ class ReconcileController extends Controller | |||||||
|         $start->startOfDay(); |         $start->startOfDay(); | ||||||
|         $end->endOfDay(); |         $end->endOfDay(); | ||||||
| 
 | 
 | ||||||
|         $startDate       = clone $start; |         //        $startDate       = clone $start;
 | ||||||
|         $startDate->subDay()->endOfDay(); // this is correct, subday endofday ends at 23:59:59
 |         //        $startDate->subDay()->endOfDay(); // this is correct, subday endofday ends at 23:59:59
 | ||||||
|         // both are validated and are correct.
 |         // both are validated and are correct.
 | ||||||
|         Log::debug(sprintf('reconcile: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String())); |         //        Log::debug(sprintf('reconcile: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String()));
 | ||||||
|         Log::debug(sprintf('reconcile2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); |         //        Log::debug(sprintf('reconcile2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
 | ||||||
|         $startBalance    = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places); |         //        $startBalance    = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
 | ||||||
|         $endBalance      = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places); |         //        $endBalance      = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
 | ||||||
|  | 
 | ||||||
|  |         // 2025-10-08 replace with accountsBalancesOptimized
 | ||||||
|  |         // no longer need to do subday->endofday on $start, set inclusive = false for the same effect.
 | ||||||
|  |         $startBalance    = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $start, convertToPrimary: null, inclusive: false)[$account->id]['balance'], $currency->decimal_places); | ||||||
|  |         $endBalance      = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id]['balance'], $currency->decimal_places); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|         $subTitleIcon    = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type)); |         $subTitleIcon    = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type)); | ||||||
|         $subTitle        = (string) trans('firefly.reconcile_account', ['account' => $account->name]); |         $subTitle        = (string) trans('firefly.reconcile_account', ['account' => $account->name]); | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -169,8 +169,9 @@ class ShowController extends Controller | |||||||
|             $now = $end; |             $now = $end; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         Log::debug(sprintf('show: Call finalAccountBalance with date/time "%s"', $now->toIso8601String())); |         // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
 | ||||||
|         $balances         = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToPrimary, $accountCurrency); |         $balances         = Steam::accountsBalancesOptimized(new Collection()->push($account), $now)[$account->id]; | ||||||
|  |         // $balances         = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToPrimary, $accountCurrency);
 | ||||||
| 
 | 
 | ||||||
|         return view( |         return view( | ||||||
|             'accounts.show', |             'accounts.show', | ||||||
| @@ -237,8 +238,12 @@ class ShowController extends Controller | |||||||
|         $chartUrl        = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); |         $chartUrl        = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); | ||||||
|         $showAll         = true; |         $showAll         = true; | ||||||
|         // correct
 |         // correct
 | ||||||
|         Log::debug(sprintf('showAll: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); |         Log::debug(sprintf('showAll: Call accountsBalancesOptimized with date/time "%s"', $end->toIso8601String())); | ||||||
|         $balances        = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $end), $account, $this->convertToPrimary, $accountCurrency); | 
 | ||||||
|  |         // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
 | ||||||
|  |         // $balances = Steam::finalAccountBalance($account, $end);
 | ||||||
|  |         // $balances        = Steam::filterAccountBalance($balances, $account, $this->convertToPrimary, $accountCurrency);
 | ||||||
|  |         $balances        = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id]; | ||||||
| 
 | 
 | ||||||
|         return view( |         return view( | ||||||
|             'accounts.show', |             'accounts.show', | ||||||
|   | |||||||
| @@ -116,10 +116,11 @@ class AccountController extends Controller | |||||||
|         $accountNames  = $this->extractNames($accounts); |         $accountNames  = $this->extractNames($accounts); | ||||||
| 
 | 
 | ||||||
|         // grab all balances
 |         // grab all balances
 | ||||||
|         Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); |         Log::debug(sprintf('expenseAccounts: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); | ||||||
|         Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); |         [ | ||||||
|         $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); |             $startBalances, | ||||||
|         $endBalances   = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); |             $endBalances, | ||||||
|  |         ]              = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary); | ||||||
|         Log::debug('Done collecting balances'); |         Log::debug('Done collecting balances'); | ||||||
|         // loop the accounts, then check for balance and currency info.
 |         // loop the accounts, then check for balance and currency info.
 | ||||||
|         foreach ($accounts as $account) { |         foreach ($accounts as $account) { | ||||||
| @@ -521,9 +522,9 @@ class AccountController extends Controller | |||||||
|         $range           = Steam::filterAccountBalances($range, $account, $this->convertToPrimary, $accountCurrency); |         $range           = Steam::filterAccountBalances($range, $account, $this->convertToPrimary, $accountCurrency); | ||||||
|         Log::debug('Get and filter balance for entire range end'); |         Log::debug('Get and filter balance for entire range end'); | ||||||
|         // temp, get end balance.
 |         // temp, get end balance.
 | ||||||
|         Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); |         //        Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
 | ||||||
|         Steam::finalAccountBalance($account, $end); |         //        Steam::finalAccountBalance($account, $end);
 | ||||||
|         Log::debug('END temp get end balance done'); |         //        Log::debug('END temp get end balance done');
 | ||||||
| 
 | 
 | ||||||
|         $previous        = array_values($range)[0]; |         $previous        = array_values($range)[0]; | ||||||
|         $accountCurrency ??= $this->primaryCurrency; // do this AFTER getting the balances.
 |         $accountCurrency ??= $this->primaryCurrency; // do this AFTER getting the balances.
 | ||||||
| @@ -666,10 +667,11 @@ class AccountController extends Controller | |||||||
|         $accountNames  = $this->extractNames($accounts); |         $accountNames  = $this->extractNames($accounts); | ||||||
| 
 | 
 | ||||||
|         // grab all balances
 |         // grab all balances
 | ||||||
|         Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); |         Log::debug(sprintf('revAccounts: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); | ||||||
|         Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); |         [ | ||||||
|         $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); |             $startBalances, | ||||||
|         $endBalances   = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); |             $endBalances, | ||||||
|  |         ]              = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         // loop the accounts, then check for balance and currency info.
 |         // loop the accounts, then check for balance and currency info.
 | ||||||
|   | |||||||
| @@ -77,6 +77,7 @@ abstract class Controller extends BaseController | |||||||
|         View::share('DEMO_USERNAME', config('firefly.demo_username')); |         View::share('DEMO_USERNAME', config('firefly.demo_username')); | ||||||
|         View::share('DEMO_PASSWORD', config('firefly.demo_password')); |         View::share('DEMO_PASSWORD', config('firefly.demo_password')); | ||||||
|         View::share('FF_VERSION', config('firefly.version')); |         View::share('FF_VERSION', config('firefly.version')); | ||||||
|  |         View::share('FF_BUILD_TIME', config('firefly.build_time')); | ||||||
| 
 | 
 | ||||||
|         // is webhooks enabled?
 |         // is webhooks enabled?
 | ||||||
|         View::share('featuringWebhooks', true === config('firefly.feature_flags.webhooks') && true === config('firefly.allow_webhooks')); |         View::share('featuringWebhooks', true === config('firefly.feature_flags.webhooks') && true === config('firefly.allow_webhooks')); | ||||||
| @@ -131,6 +132,7 @@ abstract class Controller extends BaseController | |||||||
|                 $this->primaryCurrency   = null; |                 $this->primaryCurrency   = null; | ||||||
|                 // get shown-intro-preference:
 |                 // get shown-intro-preference:
 | ||||||
|                 if (auth()->check()) { |                 if (auth()->check()) { | ||||||
|  |                     View::share('anonymous', Steam::anonymous()); | ||||||
|                     $this->primaryCurrency  = Amount::getPrimaryCurrency(); |                     $this->primaryCurrency  = Amount::getPrimaryCurrency(); | ||||||
|                     $language               = Steam::getLanguage(); |                     $language               = Steam::getLanguage(); | ||||||
|                     $locale                 = Steam::getLocale(); |                     $locale                 = Steam::getLocale(); | ||||||
|   | |||||||
| @@ -81,11 +81,11 @@ class DebugController extends Controller | |||||||
|      */ |      */ | ||||||
|     public function displayError(): void |     public function displayError(): void | ||||||
|     { |     { | ||||||
|         app('log')->debug('This is a test message at the DEBUG level.'); |         Log::debug('This is a test message at the DEBUG level.'); | ||||||
|         app('log')->info('This is a test message at the INFO level.'); |         Log::info('This is a test message at the INFO level.'); | ||||||
|         Log::notice('This is a test message at the NOTICE level.'); |         Log::notice('This is a test message at the NOTICE level.'); | ||||||
|         app('log')->warning('This is a test message at the WARNING level.'); |         Log::warning('This is a test message at the WARNING level.'); | ||||||
|         app('log')->error('This is a test message at the ERROR level.'); |         Log::error('This is a test message at the ERROR level.'); | ||||||
|         Log::critical('This is a test message at the CRITICAL level.'); |         Log::critical('This is a test message at the CRITICAL level.'); | ||||||
|         Log::alert('This is a test message at the ALERT level.'); |         Log::alert('This is a test message at the ALERT level.'); | ||||||
|         Log::emergency('This is a test message at the EMERGENCY level.'); |         Log::emergency('This is a test message at the EMERGENCY level.'); | ||||||
| @@ -187,6 +187,8 @@ class DebugController extends Controller | |||||||
|         return [ |         return [ | ||||||
|             'php_version'     => PHP_VERSION, |             'php_version'     => PHP_VERSION, | ||||||
|             'php_os'          => PHP_OS, |             'php_os'          => PHP_OS, | ||||||
|  |             'build_time'      => config('firefly.build_time'), | ||||||
|  |             'build_time_nice' => Carbon::parse(config('firefly.build_time'), 'Europe/Amsterdam')->setTimezone('Europe/Amsterdam')->format('Y-m-d H:i:s e'), | ||||||
|             'uname'           => php_uname('m'), |             'uname'           => php_uname('m'), | ||||||
|             'interface'       => PHP_SAPI, |             'interface'       => PHP_SAPI, | ||||||
|             'bits'            => PHP_INT_SIZE * 8, |             'bits'            => PHP_INT_SIZE * 8, | ||||||
| @@ -212,11 +214,11 @@ class DebugController extends Controller | |||||||
|         try { |         try { | ||||||
|             if (file_exists('/var/www/counter-main.txt')) { |             if (file_exists('/var/www/counter-main.txt')) { | ||||||
|                 $return['build'] = trim(file_get_contents('/var/www/counter-main.txt')); |                 $return['build'] = trim(file_get_contents('/var/www/counter-main.txt')); | ||||||
|                 app('log')->debug(sprintf('build is now "%s"', $return['build'])); |                 Log::debug(sprintf('build is now "%s"', $return['build'])); | ||||||
|             } |             } | ||||||
|         } catch (Exception $e) { |         } catch (Exception $e) { | ||||||
|             app('log')->debug('Could not check build counter, but thats ok.'); |             Log::debug('Could not check build counter, but thats ok.'); | ||||||
|             app('log')->warning($e->getMessage()); |             Log::warning($e->getMessage()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
| @@ -224,8 +226,8 @@ class DebugController extends Controller | |||||||
|                 $return['build_date'] = trim(file_get_contents('/var/www/build-date-main.txt')); |                 $return['build_date'] = trim(file_get_contents('/var/www/build-date-main.txt')); | ||||||
|             } |             } | ||||||
|         } catch (Exception $e) { |         } catch (Exception $e) { | ||||||
|             app('log')->debug('Could not check build date, but thats ok.'); |             Log::debug('Could not check build date, but thats ok.'); | ||||||
|             app('log')->warning($e->getMessage()); |             Log::warning($e->getMessage()); | ||||||
|         } |         } | ||||||
|         if ('' !== (string) env('BASE_IMAGE_BUILD')) {       // @phpstan-ignore-line
 |         if ('' !== (string) env('BASE_IMAGE_BUILD')) {       // @phpstan-ignore-line
 | ||||||
|             $return['base_build'] = env('BASE_IMAGE_BUILD'); // @phpstan-ignore-line
 |             $return['base_build'] = env('BASE_IMAGE_BUILD'); // @phpstan-ignore-line
 | ||||||
| @@ -282,7 +284,7 @@ class DebugController extends Controller | |||||||
|         $parts          = Steam::getLocaleArray(Steam::getLocale()); |         $parts          = Steam::getLocaleArray(Steam::getLocale()); | ||||||
|         foreach ($parts as $code) { |         foreach ($parts as $code) { | ||||||
|             $code                  = trim($code); |             $code                  = trim($code); | ||||||
|             app('log')->debug(sprintf('Trying to set %s', $code)); |             Log::debug(sprintf('Trying to set %s', $code)); | ||||||
|             $result                = setlocale(LC_ALL, $code); |             $result                = setlocale(LC_ALL, $code); | ||||||
|             $localeAttempts[$code] = $result === $code; |             $localeAttempts[$code] = $result === $code; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ use FireflyIII\Models\Account; | |||||||
| use FireflyIII\Models\TransactionCurrency; | use FireflyIII\Models\TransactionCurrency; | ||||||
| use FireflyIII\Repositories\Account\AccountRepositoryInterface; | use FireflyIII\Repositories\Account\AccountRepositoryInterface; | ||||||
| use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; | use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; | ||||||
|  | use FireflyIII\Support\Facades\Steam; | ||||||
| use FireflyIII\Support\Http\Controllers\GetConfigurationData; | use FireflyIII\Support\Http\Controllers\GetConfigurationData; | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Illuminate\Http\Response; | use Illuminate\Http\Response; | ||||||
| @@ -114,6 +115,7 @@ class JavascriptController extends Controller | |||||||
|             'currencyCode'         => $currency->code, |             'currencyCode'         => $currency->code, | ||||||
|             'currencySymbol'       => $currency->symbol, |             'currencySymbol'       => $currency->symbol, | ||||||
|             'accountingLocaleInfo' => $accounting, |             'accountingLocaleInfo' => $accounting, | ||||||
|  |             'anonymous'            => var_export(Steam::anonymous(), true), | ||||||
|             'language'             => $lang, |             'language'             => $lang, | ||||||
|             'dateRangeTitle'       => $dateRange['title'], |             'dateRangeTitle'       => $dateRange['title'], | ||||||
|             'locale'               => $locale, |             'locale'               => $locale, | ||||||
|   | |||||||
| @@ -38,6 +38,7 @@ use FireflyIII\Support\Facades\Amount; | |||||||
| use FireflyIII\Support\Facades\Steam; | use FireflyIII\Support\Facades\Steam; | ||||||
| use FireflyIII\Support\Http\Controllers\DateCalculation; | use FireflyIII\Support\Http\Controllers\DateCalculation; | ||||||
| use Illuminate\Http\JsonResponse; | use Illuminate\Http\JsonResponse; | ||||||
|  | use Illuminate\Support\Facades\Log; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Class BoxController. |  * Class BoxController. | ||||||
| @@ -165,7 +166,7 @@ class BoxController extends Controller | |||||||
|         $allAccounts       = $accountRepository->getActiveAccountsByType( |         $allAccounts       = $accountRepository->getActiveAccountsByType( | ||||||
|             [AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value] |             [AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value] | ||||||
|         ); |         ); | ||||||
|         app('log')->debug(sprintf('Found %d accounts.', $allAccounts->count())); |         Log::debug(sprintf('Found %d accounts.', $allAccounts->count())); | ||||||
| 
 | 
 | ||||||
|         // filter list on preference of being included.
 |         // filter list on preference of being included.
 | ||||||
|         $filtered          = $allAccounts->filter( |         $filtered          = $allAccounts->filter( | ||||||
| @@ -173,7 +174,7 @@ class BoxController extends Controller | |||||||
|                 $includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth'); |                 $includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth'); | ||||||
|                 $result          = null === $includeNetWorth || '1' === $includeNetWorth; |                 $result          = null === $includeNetWorth || '1' === $includeNetWorth; | ||||||
|                 if (false === $result) { |                 if (false === $result) { | ||||||
|                     app('log')->debug(sprintf('Will not include "%s" in net worth charts.', $account->name)); |                     Log::debug(sprintf('Will not include "%s" in net worth charts.', $account->name)); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 return $result; |                 return $result; | ||||||
|   | |||||||
| @@ -81,6 +81,12 @@ class ReconcileController extends Controller | |||||||
|         if (!$start instanceof Carbon && !$end instanceof Carbon) { |         if (!$start instanceof Carbon && !$end instanceof Carbon) { | ||||||
|             throw new FireflyException('Invalid dates submitted.'); |             throw new FireflyException('Invalid dates submitted.'); | ||||||
|         } |         } | ||||||
|  |         if (!is_numeric($startBalance)) { | ||||||
|  |             $startBalance = '0'; | ||||||
|  |         } | ||||||
|  |         if (!is_numeric($endBalance)) { | ||||||
|  |             $endBalance = '0'; | ||||||
|  |         } | ||||||
|         if ($end->lt($start)) { |         if ($end->lt($start)) { | ||||||
|             [$start, $end] = [$end, $start]; |             [$start, $end] = [$end, $start]; | ||||||
|         } |         } | ||||||
| @@ -197,10 +203,16 @@ class ReconcileController extends Controller | |||||||
| 
 | 
 | ||||||
|         $currency       = $this->accountRepos->getAccountCurrency($account) ?? $this->primaryCurrency; |         $currency       = $this->accountRepos->getAccountCurrency($account) ?? $this->primaryCurrency; | ||||||
|         // correct
 |         // correct
 | ||||||
|         Log::debug(sprintf('transactions: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String())); |         Log::debug(sprintf('transactions: Call accountsBalancesOptimized with date/time "%s"', $startDate->toIso8601String())); | ||||||
|         Log::debug(sprintf('transactions2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); |         Log::debug(sprintf('transactions2: Call accountsBalancesOptimized with date/time "%s"', $end->toIso8601String())); | ||||||
|         $startBalance   = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places); | 
 | ||||||
|         $endBalance     = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places); |         // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized
 | ||||||
|  |         //        $startBalance   = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places);
 | ||||||
|  |         //        $endBalance     = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places);
 | ||||||
|  | 
 | ||||||
|  |         $startBalance   = Steam::accountsBalancesOptimized(new Collection()->push($account), $startDate)[$account->id]; | ||||||
|  |         $endBalance     = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id]; | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|         // get the transactions
 |         // get the transactions
 | ||||||
|         $selectionStart = clone $start; |         $selectionStart = clone $start; | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ declare(strict_types=1); | |||||||
| 
 | 
 | ||||||
| namespace FireflyIII\Http\Controllers\Rule; | namespace FireflyIII\Http\Controllers\Rule; | ||||||
| 
 | 
 | ||||||
|  | use Carbon\Carbon; | ||||||
| use FireflyIII\Exceptions\FireflyException; | use FireflyIII\Exceptions\FireflyException; | ||||||
| use FireflyIII\Http\Controllers\Controller; | use FireflyIII\Http\Controllers\Controller; | ||||||
| use FireflyIII\Http\Requests\SelectTransactionsRequest; | use FireflyIII\Http\Requests\SelectTransactionsRequest; | ||||||
| @@ -56,7 +57,7 @@ class SelectController extends Controller | |||||||
| 
 | 
 | ||||||
|         $this->middleware( |         $this->middleware( | ||||||
|             static function ($request, $next) { |             static function ($request, $next) { | ||||||
|                 app('view')->share('title', (string) trans('firefly.rules')); |                 app('view')->share('title', (string)trans('firefly.rules')); | ||||||
|                 app('view')->share('mainTitleIcon', 'fa-random'); |                 app('view')->share('mainTitleIcon', 'fa-random'); | ||||||
| 
 | 
 | ||||||
|                 return $next($request); |                 return $next($request); | ||||||
| @@ -73,11 +74,20 @@ class SelectController extends Controller | |||||||
|         /** @var User $user */ |         /** @var User $user */ | ||||||
|         $user          = auth()->user(); |         $user          = auth()->user(); | ||||||
|         $accounts      = implode(',', $request->get('accounts')); |         $accounts      = implode(',', $request->get('accounts')); | ||||||
| 
 |  | ||||||
|         // create new rule engine:
 |         // create new rule engine:
 | ||||||
|         $newRuleEngine = app(RuleEngineInterface::class); |         $newRuleEngine = app(RuleEngineInterface::class); | ||||||
|         $newRuleEngine->setUser($user); |         $newRuleEngine->setUser($user); | ||||||
| 
 | 
 | ||||||
|  |         // add date operators.
 | ||||||
|  |         if (null !== $request->get('start')) { | ||||||
|  |             $startDate = new Carbon($request->get('start')); | ||||||
|  |             $newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]); | ||||||
|  |         } | ||||||
|  |         if (null !== $request->get('end')) { | ||||||
|  |             $endDate = new Carbon($request->get('end')); | ||||||
|  |             $newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // add extra operators:
 |         // add extra operators:
 | ||||||
|         $newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]); |         $newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]); | ||||||
| 
 | 
 | ||||||
| @@ -102,7 +112,7 @@ class SelectController extends Controller | |||||||
|             return redirect(route('rules.index')); |             return redirect(route('rules.index')); | ||||||
|         } |         } | ||||||
|         // does the user have shared accounts?
 |         // does the user have shared accounts?
 | ||||||
|         $subTitle = (string) trans('firefly.apply_rule_selection', ['title' => $rule->title]); |         $subTitle = (string)trans('firefly.apply_rule_selection', ['title' => $rule->title]); | ||||||
| 
 | 
 | ||||||
|         return view('rules.rule.select-transactions', compact('rule', 'subTitle')); |         return view('rules.rule.select-transactions', compact('rule', 'subTitle')); | ||||||
|     } |     } | ||||||
| @@ -127,7 +137,7 @@ class SelectController extends Controller | |||||||
| 
 | 
 | ||||||
|         // warn if nothing.
 |         // warn if nothing.
 | ||||||
|         if (0 === count($textTriggers)) { |         if (0 === count($textTriggers)) { | ||||||
|             return response()->json(['html' => '', 'warning' => (string) trans('firefly.warning_no_valid_triggers')]); |             return response()->json(['html' => '', 'warning' => (string)trans('firefly.warning_no_valid_triggers')]); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         foreach ($textTriggers as $textTrigger) { |         foreach ($textTriggers as $textTrigger) { | ||||||
| @@ -160,7 +170,7 @@ class SelectController extends Controller | |||||||
|         // Warn the user if only a subset of transactions is returned
 |         // Warn the user if only a subset of transactions is returned
 | ||||||
|         $warning            = ''; |         $warning            = ''; | ||||||
|         if (0 === count($collection)) { |         if (0 === count($collection)) { | ||||||
|             $warning = (string) trans('firefly.warning_no_matching_transactions'); |             $warning = (string)trans('firefly.warning_no_matching_transactions'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Return json response
 |         // Return json response
 | ||||||
| @@ -190,7 +200,7 @@ class SelectController extends Controller | |||||||
|         $triggers      = $rule->ruleTriggers; |         $triggers      = $rule->ruleTriggers; | ||||||
| 
 | 
 | ||||||
|         if (0 === count($triggers)) { |         if (0 === count($triggers)) { | ||||||
|             return response()->json(['html' => '', 'warning' => (string) trans('firefly.warning_no_valid_triggers')]); |             return response()->json(['html' => '', 'warning' => (string)trans('firefly.warning_no_valid_triggers')]); | ||||||
|         } |         } | ||||||
|         // create new rule engine:
 |         // create new rule engine:
 | ||||||
|         $newRuleEngine = app(RuleEngineInterface::class); |         $newRuleEngine = app(RuleEngineInterface::class); | ||||||
| @@ -202,7 +212,7 @@ class SelectController extends Controller | |||||||
| 
 | 
 | ||||||
|         $warning       = ''; |         $warning       = ''; | ||||||
|         if (0 === count($collection)) { |         if (0 === count($collection)) { | ||||||
|             $warning = (string) trans('firefly.warning_no_matching_transactions'); |             $warning = (string)trans('firefly.warning_no_matching_transactions'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Return json response
 |         // Return json response
 | ||||||
|   | |||||||
| @@ -24,6 +24,7 @@ declare(strict_types=1); | |||||||
| 
 | 
 | ||||||
| namespace FireflyIII\Http\Controllers\RuleGroup; | namespace FireflyIII\Http\Controllers\RuleGroup; | ||||||
| 
 | 
 | ||||||
|  | use Carbon\Carbon; | ||||||
| use Exception; | use Exception; | ||||||
| use FireflyIII\Http\Controllers\Controller; | use FireflyIII\Http\Controllers\Controller; | ||||||
| use FireflyIII\Http\Requests\SelectTransactionsRequest; | use FireflyIII\Http\Requests\SelectTransactionsRequest; | ||||||
| @@ -73,6 +74,16 @@ class ExecutionController extends Controller | |||||||
|         $newRuleEngine = app(RuleEngineInterface::class); |         $newRuleEngine = app(RuleEngineInterface::class); | ||||||
|         $newRuleEngine->setUser($user); |         $newRuleEngine->setUser($user); | ||||||
| 
 | 
 | ||||||
|  |         // add date operators.
 | ||||||
|  |         if (null !== $request->get('start')) { | ||||||
|  |             $startDate = new Carbon($request->get('start')); | ||||||
|  |             $newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]); | ||||||
|  |         } | ||||||
|  |         if (null !== $request->get('end')) { | ||||||
|  |             $endDate = new Carbon($request->get('end')); | ||||||
|  |             $newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         // add extra operators:
 |         // add extra operators:
 | ||||||
|         $newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]); |         $newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]); | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ use Illuminate\Http\JsonResponse; | |||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Illuminate\Support\Facades\Artisan; | use Illuminate\Support\Facades\Artisan; | ||||||
| use Illuminate\Support\Facades\Cache; | use Illuminate\Support\Facades\Cache; | ||||||
|  | use Illuminate\Support\Facades\Log; | ||||||
| use Illuminate\View\View; | use Illuminate\View\View; | ||||||
| use Laravel\Passport\Passport; | use Laravel\Passport\Passport; | ||||||
| use phpseclib3\Crypt\RSA; | use phpseclib3\Crypt\RSA; | ||||||
| @@ -81,8 +82,10 @@ class InstallController extends Controller | |||||||
|     public function index() |     public function index() | ||||||
|     { |     { | ||||||
|         app('view')->share('FF_VERSION', config('firefly.version')); |         app('view')->share('FF_VERSION', config('firefly.version')); | ||||||
|  | 
 | ||||||
|         // index will set FF3 version.
 |         // index will set FF3 version.
 | ||||||
|         FireflyConfig::set('ff3_version', (string) config('firefly.version')); |         FireflyConfig::set('ff3_version', (string) config('firefly.version')); | ||||||
|  |         FireflyConfig::set('ff3_build_time', (int) config('firefly.build_time')); | ||||||
| 
 | 
 | ||||||
|         return view('install.index'); |         return view('install.index'); | ||||||
|     } |     } | ||||||
| @@ -98,18 +101,18 @@ class InstallController extends Controller | |||||||
|             'errorMessage'   => null, |             'errorMessage'   => null, | ||||||
|         ]; |         ]; | ||||||
| 
 | 
 | ||||||
|         app('log')->debug(sprintf('Will now run commands. Request index is %d', $requestIndex)); |         Log::debug(sprintf('Will now run commands. Request index is %d', $requestIndex)); | ||||||
|         $indexes      = array_keys($this->upgradeCommands); |         $indexes      = array_keys($this->upgradeCommands); | ||||||
|         if (array_key_exists($requestIndex, $indexes)) { |         if (array_key_exists($requestIndex, $indexes)) { | ||||||
|             $command                    = $indexes[$requestIndex]; |             $command                    = $indexes[$requestIndex]; | ||||||
|             $parameters                 = $this->upgradeCommands[$command]; |             $parameters                 = $this->upgradeCommands[$command]; | ||||||
|             app('log')->debug(sprintf('Will now execute command "%s" with parameters', $command), $parameters); |             Log::debug(sprintf('Will now execute command "%s" with parameters', $command), $parameters); | ||||||
| 
 | 
 | ||||||
|             try { |             try { | ||||||
|                 $result = $this->executeCommand($command, $parameters); |                 $result = $this->executeCommand($command, $parameters); | ||||||
|             } catch (FireflyException $e) { |             } catch (FireflyException $e) { | ||||||
|                 app('log')->error($e->getMessage()); |                 Log::error($e->getMessage()); | ||||||
|                 app('log')->error($e->getTraceAsString()); |                 Log::error($e->getTraceAsString()); | ||||||
|                 if (str_contains($e->getMessage(), 'open_basedir restriction in effect')) { |                 if (str_contains($e->getMessage(), 'open_basedir restriction in effect')) { | ||||||
|                     $this->lastError = self::BASEDIR_ERROR; |                     $this->lastError = self::BASEDIR_ERROR; | ||||||
|                 } |                 } | ||||||
| @@ -134,7 +137,7 @@ class InstallController extends Controller | |||||||
|      */ |      */ | ||||||
|     private function executeCommand(string $command, array $args): bool |     private function executeCommand(string $command, array $args): bool | ||||||
|     { |     { | ||||||
|         app('log')->debug(sprintf('Will now call command %s with args.', $command), $args); |         Log::debug(sprintf('Will now call command %s with args.', $command), $args); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             if ('generate-keys' === $command) { |             if ('generate-keys' === $command) { | ||||||
| @@ -142,7 +145,7 @@ class InstallController extends Controller | |||||||
|             } |             } | ||||||
|             if ('generate-keys' !== $command) { |             if ('generate-keys' !== $command) { | ||||||
|                 Artisan::call($command, $args); |                 Artisan::call($command, $args); | ||||||
|                 app('log')->debug(Artisan::output()); |                 Log::debug(Artisan::output()); | ||||||
|             } |             } | ||||||
|         } catch (Exception $e) { // intentional generic exception
 |         } catch (Exception $e) { // intentional generic exception
 | ||||||
|             throw new FireflyException($e->getMessage(), 0, $e); |             throw new FireflyException($e->getMessage(), 0, $e); | ||||||
|   | |||||||
| @@ -37,6 +37,7 @@ use FireflyIII\Models\TransactionJournal; | |||||||
| use FireflyIII\Models\TransactionType; | use FireflyIII\Models\TransactionType; | ||||||
| use FireflyIII\Repositories\Account\AccountRepositoryInterface; | use FireflyIII\Repositories\Account\AccountRepositoryInterface; | ||||||
| use FireflyIII\Services\Internal\Update\JournalUpdateService; | use FireflyIII\Services\Internal\Update\JournalUpdateService; | ||||||
|  | use FireflyIII\Support\Facades\Amount; | ||||||
| use FireflyIII\Support\Facades\Steam; | use FireflyIII\Support\Facades\Steam; | ||||||
| use FireflyIII\Support\Http\Controllers\ModelInformation; | use FireflyIII\Support\Http\Controllers\ModelInformation; | ||||||
| use FireflyIII\Transformers\TransactionGroupTransformer; | use FireflyIII\Transformers\TransactionGroupTransformer; | ||||||
| @@ -45,6 +46,7 @@ use Illuminate\Contracts\View\Factory; | |||||||
| use Illuminate\Http\RedirectResponse; | use Illuminate\Http\RedirectResponse; | ||||||
| use Illuminate\Http\Request; | use Illuminate\Http\Request; | ||||||
| use Illuminate\Routing\Redirector; | use Illuminate\Routing\Redirector; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
| use Illuminate\Support\Facades\Log; | use Illuminate\Support\Facades\Log; | ||||||
| use Illuminate\View\View; | use Illuminate\View\View; | ||||||
| 
 | 
 | ||||||
| @@ -228,11 +230,13 @@ class ConvertController extends Controller | |||||||
|         foreach ($accountList as $account) { |         foreach ($accountList as $account) { | ||||||
|             $date                        = today()->endOfDay(); |             $date                        = today()->endOfDay(); | ||||||
|             Log::debug(sprintf('getLiabilities: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); |             Log::debug(sprintf('getLiabilities: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); | ||||||
|             $balance                     = Steam::finalAccountBalance($account, $date)['balance']; |             // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
 | ||||||
|  |             // $balance                     = Steam::finalAccountBalance($account, $date)['balance'];
 | ||||||
|  |             $balance                     = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0'; | ||||||
|             $currency                    = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency; |             $currency                    = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency; | ||||||
|             $role                        = 'l_'.$account->accountType->type; |             $role                        = sprintf('l_%s', $account->accountType->type); | ||||||
|             $key                         = (string) trans('firefly.opt_group_'.$role); |             $key                         = (string) trans(sprintf('firefly.opt_group_%s', $role)); | ||||||
|             $grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')'; |             $grouped[$key][$account->id] = sprintf('%s (%s)', $account->name, Amount::formatAnything($currency, $balance, false)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return $grouped; |         return $grouped; | ||||||
| @@ -252,15 +256,18 @@ class ConvertController extends Controller | |||||||
|         foreach ($accountList as $account) { |         foreach ($accountList as $account) { | ||||||
|             $date                        = today()->endOfDay(); |             $date                        = today()->endOfDay(); | ||||||
|             Log::debug(sprintf('getAssetAccounts: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); |             Log::debug(sprintf('getAssetAccounts: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); | ||||||
|             $balance                     = Steam::finalAccountBalance($account, $date)['balance']; |             // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
 | ||||||
|  |             // $balance                     = Steam::finalAccountBalance($account, $date)['balance'];
 | ||||||
|  |             $balance                     = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0'; | ||||||
|  | 
 | ||||||
|             $currency                    = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency; |             $currency                    = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency; | ||||||
|             $role                        = (string) $this->accountRepository->getMetaValue($account, 'account_role'); |             $role                        = (string) $this->accountRepository->getMetaValue($account, 'account_role'); | ||||||
|             if ('' === $role) { |             if ('' === $role) { | ||||||
|                 $role = 'no_account_type'; |                 $role = 'no_account_type'; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             $key                         = (string) trans('firefly.opt_group_'.$role); |             $key                         = (string) trans(sprintf('firefly.opt_group_%s', $role)); | ||||||
|             $grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')'; |             $grouped[$key][$account->id] = sprintf('%s (%s)', $account->name, Amount::formatAnything($currency, $balance, false)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return $grouped; |         return $grouped; | ||||||
|   | |||||||
| @@ -31,6 +31,8 @@ use Illuminate\Queue\InteractsWithQueue; | |||||||
| use Illuminate\Queue\SerializesModels; | use Illuminate\Queue\SerializesModels; | ||||||
| use Illuminate\Support\Facades\Log; | use Illuminate\Support\Facades\Log; | ||||||
| use Illuminate\Support\Facades\Mail; | use Illuminate\Support\Facades\Mail; | ||||||
|  | use Safe\Exceptions\FilesystemException; | ||||||
|  | use Safe\Exceptions\JsonException; | ||||||
| use Symfony\Component\Mailer\Exception\TransportException; | use Symfony\Component\Mailer\Exception\TransportException; | ||||||
| 
 | 
 | ||||||
| use function Safe\file_get_contents; | use function Safe\file_get_contents; | ||||||
| @@ -125,11 +127,25 @@ class MailError extends Job implements ShouldQueue | |||||||
| 
 | 
 | ||||||
|         if (!file_exists($file)) { |         if (!file_exists($file)) { | ||||||
|             Log::debug(sprintf('Wrote new file in "%s"', $file)); |             Log::debug(sprintf('Wrote new file in "%s"', $file)); | ||||||
|             file_put_contents($file, json_encode($limits, JSON_PRETTY_PRINT)); | 
 | ||||||
|  |             try { | ||||||
|  |                 file_put_contents($file, json_encode($limits, JSON_PRETTY_PRINT)); | ||||||
|  |             } catch (FilesystemException $e) { | ||||||
|  |                 Log::warning(sprintf('[a] Could not write file "%s": %s', $file, $e->getMessage())); | ||||||
|  |             } catch (JsonException $e) { | ||||||
|  |                 Log::warning(sprintf('[b] Could not parse file "%s": %s', $file, $e->getMessage())); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         if (file_exists($file)) { |         if (file_exists($file)) { | ||||||
|             Log::debug(sprintf('Read file in "%s"', $file)); |             Log::debug(sprintf('Read file in "%s"', $file)); | ||||||
|             $limits = json_decode(file_get_contents($file), true); | 
 | ||||||
|  |             try { | ||||||
|  |                 $limits = json_decode(file_get_contents($file), true); | ||||||
|  |             } catch (FilesystemException $e) { | ||||||
|  |                 Log::warning(sprintf('[c] Could not read file "%s": %s', $file, $e->getMessage())); | ||||||
|  |             } catch (JsonException $e) { | ||||||
|  |                 Log::warning(sprintf('[d] Could not parse file "%s": %s', $file, $e->getMessage())); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|         // limit reached?
 |         // limit reached?
 | ||||||
|         foreach ($types as $type => $info) { |         foreach ($types as $type => $info) { | ||||||
| @@ -157,7 +173,14 @@ class MailError extends Job implements ShouldQueue | |||||||
|             } |             } | ||||||
|             ++$limits[$type]['sent']; |             ++$limits[$type]['sent']; | ||||||
|         } |         } | ||||||
|         file_put_contents($file, json_encode($limits, JSON_PRETTY_PRINT)); | 
 | ||||||
|  |         try { | ||||||
|  |             file_put_contents($file, json_encode($limits, JSON_PRETTY_PRINT)); | ||||||
|  |         } catch (FilesystemException $e) { | ||||||
|  |             Log::warning(sprintf('[c] Could not write file "%s": %s', $file, $e->getMessage())); | ||||||
|  |         } catch (JsonException $e) { | ||||||
|  |             Log::warning(sprintf('[c] Could not parse file "%s": %s', $file, $e->getMessage())); | ||||||
|  |         } | ||||||
|         Log::debug('No limits reached, return FALSE.'); |         Log::debug('No limits reached, return FALSE.'); | ||||||
| 
 | 
 | ||||||
|         return false; |         return false; | ||||||
|   | |||||||
| @@ -50,10 +50,11 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface | |||||||
|         $yesterday       = clone $start; |         $yesterday       = clone $start; | ||||||
|         $yesterday->subDay()->endOfDay(); // exactly up until $start but NOT including.
 |         $yesterday->subDay()->endOfDay(); // exactly up until $start but NOT including.
 | ||||||
|         $end->endOfDay();                 // needs to be end of day to be correct.
 |         $end->endOfDay();                 // needs to be end of day to be correct.
 | ||||||
|         Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $yesterday->format('Y-m-d H:i:s'))); |         Log::debug(sprintf('getAccountReport: accountsBalancesInRange("%s", "%s")', $yesterday->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); | ||||||
|         Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); |         [ | ||||||
|         $startSet        = Steam::accountsBalancesOptimized($accounts, $yesterday); |             $startSet, | ||||||
|         $endSet          = Steam::accountsBalancesOptimized($accounts, $end); |             $endSet, | ||||||
|  |         ]                = Steam::accountsBalancesInRange($accounts, $yesterday, $end); | ||||||
|         Log::debug('Start of accountreport'); |         Log::debug('Start of accountreport'); | ||||||
| 
 | 
 | ||||||
|         /** @var AccountRepositoryInterface $repository */ |         /** @var AccountRepositoryInterface $repository */ | ||||||
|   | |||||||
| @@ -375,7 +375,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte | |||||||
|     { |     { | ||||||
|         Log::debug(sprintf('leftOnAccount("%s","%s","%s")', $piggyBank->name, $account->name, $date->format('Y-m-d H:i:s'))); |         Log::debug(sprintf('leftOnAccount("%s","%s","%s")', $piggyBank->name, $account->name, $date->format('Y-m-d H:i:s'))); | ||||||
|         Log::debug(sprintf('leftOnAccount: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); |         Log::debug(sprintf('leftOnAccount: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); | ||||||
|         $balance = Steam::finalAccountBalance($account, $date)['balance']; |         // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
 | ||||||
|  |         $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance']; | ||||||
|  |         // $balance = Steam::finalAccountBalance($account, $date)['balance'];
 | ||||||
| 
 | 
 | ||||||
|         Log::debug(sprintf('Balance is: %s', $balance)); |         Log::debug(sprintf('Balance is: %s', $balance)); | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -139,7 +139,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte | |||||||
|         /** @var Rule $entry */ |         /** @var Rule $entry */ | ||||||
|         foreach ($set as $entry) { |         foreach ($set as $entry) { | ||||||
|             if ($entry->order !== $count) { |             if ($entry->order !== $count) { | ||||||
|                 app('log')->debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count)); |                 Log::debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count)); | ||||||
|                 $entry->order = $count; |                 $entry->order = $count; | ||||||
|                 $entry->save(); |                 $entry->save(); | ||||||
|             } |             } | ||||||
| @@ -167,7 +167,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte | |||||||
|             if ($action->order !== $index) { |             if ($action->order !== $index) { | ||||||
|                 $action->order = $index; |                 $action->order = $index; | ||||||
|                 $action->save(); |                 $action->save(); | ||||||
|                 app('log')->debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index)); |                 Log::debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index)); | ||||||
|             } |             } | ||||||
|             ++$index; |             ++$index; | ||||||
|         } |         } | ||||||
| @@ -189,7 +189,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte | |||||||
|             if ($order !== $index) { |             if ($order !== $index) { | ||||||
|                 $trigger->order = $index; |                 $trigger->order = $index; | ||||||
|                 $trigger->save(); |                 $trigger->save(); | ||||||
|                 app('log')->debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index)); |                 Log::debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index)); | ||||||
|             } |             } | ||||||
|             ++$index; |             ++$index; | ||||||
|         } |         } | ||||||
| @@ -235,6 +235,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte | |||||||
|     public function getActiveStoreRules(RuleGroup $group): Collection |     public function getActiveStoreRules(RuleGroup $group): Collection | ||||||
|     { |     { | ||||||
|         return $group->rules() |         return $group->rules() | ||||||
|  |             ->orderBy('rules.order', 'ASC') | ||||||
|             ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') |             ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') | ||||||
|             ->where('rule_triggers.trigger_type', 'user_action') |             ->where('rule_triggers.trigger_type', 'user_action') | ||||||
|             ->where('rule_triggers.trigger_value', 'store-journal') |             ->where('rule_triggers.trigger_value', 'store-journal') | ||||||
| @@ -262,6 +263,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte | |||||||
|                 [ // @phpstan-ignore-line
 |                 [ // @phpstan-ignore-line
 | ||||||
|                     'rules'              => static function (HasMany $query): void { |                     'rules'              => static function (HasMany $query): void { | ||||||
|                         $query->orderBy('order', 'ASC'); |                         $query->orderBy('order', 'ASC'); | ||||||
|  |                         $query->where('rules.active', true); | ||||||
|                     }, |                     }, | ||||||
|                     'rules.ruleTriggers' => static function (HasMany $query): void { |                     'rules.ruleTriggers' => static function (HasMany $query): void { | ||||||
|                         $query->orderBy('order', 'ASC'); |                         $query->orderBy('order', 'ASC'); | ||||||
| @@ -275,23 +277,23 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte | |||||||
|         if (null === $filter) { |         if (null === $filter) { | ||||||
|             return $groups; |             return $groups; | ||||||
|         } |         } | ||||||
|         app('log')->debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter)); |         Log::debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter)); | ||||||
| 
 | 
 | ||||||
|         return $groups->map( |         return $groups->map( | ||||||
|             static function (RuleGroup $group) use ($filter) { // @phpstan-ignore-line
 |             static function (RuleGroup $group) use ($filter) { // @phpstan-ignore-line
 | ||||||
|                 app('log')->debug(sprintf('Now filtering group #%d', $group->id)); |                 Log::debug(sprintf('Now filtering group #%d', $group->id)); | ||||||
|                 // filter the rules in the rule group:
 |                 // filter the rules in the rule group:
 | ||||||
|                 $group->rules = $group->rules->filter( |                 $group->rules = $group->rules->filter( | ||||||
|                     static function (Rule $rule) use ($filter) { |                     static function (Rule $rule) use ($filter) { | ||||||
|                         app('log')->debug(sprintf('Now filtering rule #%d', $rule->id)); |                         Log::debug(sprintf('Now filtering rule #%d', $rule->id)); | ||||||
|                         foreach ($rule->ruleTriggers as $trigger) { |                         foreach ($rule->ruleTriggers as $trigger) { | ||||||
|                             if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) { |                             if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) { | ||||||
|                                 app('log')->debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter)); |                                 Log::debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter)); | ||||||
| 
 | 
 | ||||||
|                                 return true; |                                 return true; | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                         app('log')->debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter)); |                         Log::debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter)); | ||||||
| 
 | 
 | ||||||
|                         return false; |                         return false; | ||||||
|                     } |                     } | ||||||
| @@ -318,6 +320,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte | |||||||
|                 [ // @phpstan-ignore-line
 |                 [ // @phpstan-ignore-line
 | ||||||
|                     'rules'              => static function (HasMany $query): void { |                     'rules'              => static function (HasMany $query): void { | ||||||
|                         $query->orderBy('order', 'ASC'); |                         $query->orderBy('order', 'ASC'); | ||||||
|  |                         $query->where('rules.active', true); | ||||||
|                     }, |                     }, | ||||||
|                     'rules.ruleTriggers' => static function (HasMany $query): void { |                     'rules.ruleTriggers' => static function (HasMany $query): void { | ||||||
|                         $query->orderBy('order', 'ASC'); |                         $query->orderBy('order', 'ASC'); | ||||||
| @@ -331,23 +334,23 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte | |||||||
|         if (null === $filter) { |         if (null === $filter) { | ||||||
|             return $groups; |             return $groups; | ||||||
|         } |         } | ||||||
|         app('log')->debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter)); |         Log::debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter)); | ||||||
| 
 | 
 | ||||||
|         return $groups->map( |         return $groups->map( | ||||||
|             static function (RuleGroup $group) use ($filter) { // @phpstan-ignore-line
 |             static function (RuleGroup $group) use ($filter) { // @phpstan-ignore-line
 | ||||||
|                 app('log')->debug(sprintf('Now filtering group #%d', $group->id)); |                 Log::debug(sprintf('Now filtering group #%d', $group->id)); | ||||||
|                 // filter the rules in the rule group:
 |                 // filter the rules in the rule group:
 | ||||||
|                 $group->rules = $group->rules->filter( |                 $group->rules = $group->rules->filter( | ||||||
|                     static function (Rule $rule) use ($filter) { |                     static function (Rule $rule) use ($filter) { | ||||||
|                         app('log')->debug(sprintf('Now filtering rule #%d', $rule->id)); |                         Log::debug(sprintf('Now filtering rule #%d', $rule->id)); | ||||||
|                         foreach ($rule->ruleTriggers as $trigger) { |                         foreach ($rule->ruleTriggers as $trigger) { | ||||||
|                             if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) { |                             if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) { | ||||||
|                                 app('log')->debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter)); |                                 Log::debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter)); | ||||||
| 
 | 
 | ||||||
|                                 return true; |                                 return true; | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                         app('log')->debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter)); |                         Log::debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter)); | ||||||
| 
 | 
 | ||||||
|                         return false; |                         return false; | ||||||
|                     } |                     } | ||||||
| @@ -414,7 +417,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte | |||||||
|                 ->decrement('order') |                 ->decrement('order') | ||||||
|             ; |             ; | ||||||
|             $ruleGroup->order = $newOrder; |             $ruleGroup->order = $newOrder; | ||||||
|             app('log')->debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); |             Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); | ||||||
|             $ruleGroup->save(); |             $ruleGroup->save(); | ||||||
| 
 | 
 | ||||||
|             return; |             return; | ||||||
| @@ -425,7 +428,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte | |||||||
|             ->increment('order') |             ->increment('order') | ||||||
|         ; |         ; | ||||||
|         $ruleGroup->order = $newOrder; |         $ruleGroup->order = $newOrder; | ||||||
|         app('log')->debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); |         Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); | ||||||
|         $ruleGroup->save(); |         $ruleGroup->save(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -38,6 +38,11 @@ class IsValidSortInstruction implements ValidationRule | |||||||
| 
 | 
 | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |         if ('' === $value) { | ||||||
|  |             // don't validate.
 | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|         $validParameters = config(sprintf('firefly.allowed_sort_parameters.%s', $shortClass)); |         $validParameters = config(sprintf('firefly.allowed_sort_parameters.%s', $shortClass)); | ||||||
|         if (!is_array($validParameters)) { |         if (!is_array($validParameters)) { | ||||||
|             $fail('validation.no_sort_instructions')->translate(['object' => $shortClass]); |             $fail('validation.no_sort_instructions')->translate(['object' => $shortClass]); | ||||||
|   | |||||||
| @@ -88,6 +88,7 @@ trait JournalServiceTrait | |||||||
| 
 | 
 | ||||||
|         // the account that Firefly III creates must be "creatable", aka select the one we can create from the list just in case
 |         // the account that Firefly III creates must be "creatable", aka select the one we can create from the list just in case
 | ||||||
|         $creatableType = $this->getCreatableType($expectedTypes[$transactionType]); |         $creatableType = $this->getCreatableType($expectedTypes[$transactionType]); | ||||||
|  |         Log::debug(sprintf('Creatable type is "%s"', $creatableType), $expectedTypes[$transactionType]); | ||||||
| 
 | 
 | ||||||
|         // if the result is NULL but the ID is set, an account could exist of the wrong type.
 |         // if the result is NULL but the ID is set, an account could exist of the wrong type.
 | ||||||
|         // that data can be used to create a new account of the right type.
 |         // that data can be used to create a new account of the right type.
 | ||||||
| @@ -227,9 +228,11 @@ trait JournalServiceTrait | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // find by preferred type.
 |         // find by preferred type.
 | ||||||
|  |         Log::debug('Find by preferred type.'); | ||||||
|         $result = $this->accountRepository->findByName($data['name'], [$types[0]]); |         $result = $this->accountRepository->findByName($data['name'], [$types[0]]); | ||||||
| 
 | 
 | ||||||
|         // or any expected type.
 |         // or any expected type.
 | ||||||
|  |         Log::debug('Find by any expected type.'); | ||||||
|         $result ??= $this->accountRepository->findByName($data['name'], $types); |         $result ??= $this->accountRepository->findByName($data['name'], $types); | ||||||
| 
 | 
 | ||||||
|         if (null !== $result) { |         if (null !== $result) { | ||||||
|   | |||||||
| @@ -163,6 +163,7 @@ class Amount | |||||||
|      */ |      */ | ||||||
|     public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string |     public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string | ||||||
|     { |     { | ||||||
|  |         $amount  = Steam::anonymous() ? '0' : $amount; | ||||||
|         $locale  = Steam::getLocale(); |         $locale  = Steam::getLocale(); | ||||||
|         $rounded = Steam::bcround($amount, $decimalPlaces); |         $rounded = Steam::bcround($amount, $decimalPlaces); | ||||||
|         $coloured ??= true; |         $coloured ??= true; | ||||||
|   | |||||||
| @@ -49,14 +49,14 @@ class RemoteUserGuard implements Guard | |||||||
|     { |     { | ||||||
|         /** @var null|Request $request */ |         /** @var null|Request $request */ | ||||||
|         $request           = $app->get('request'); |         $request           = $app->get('request'); | ||||||
|         Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $request?->getMethod(), $request?->getRequestUri())); |         // Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $request?->getMethod(), $request?->getRequestUri()));
 | ||||||
|         $this->application = $app; |         $this->application = $app; | ||||||
|         $this->user        = null; |         $this->user        = null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function authenticate(): void |     public function authenticate(): void | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('Now at %s', __METHOD__)); |         // Log::debug(sprintf('Now at %s', __METHOD__));
 | ||||||
|         if ($this->user instanceof User) { |         if ($this->user instanceof User) { | ||||||
|             Log::debug(sprintf('%s is found: #%d, "%s".', $this->user::class, $this->user->id, $this->user->email)); |             Log::debug(sprintf('%s is found: #%d, "%s".', $this->user::class, $this->user->id, $this->user->email)); | ||||||
| 
 | 
 | ||||||
| @@ -104,21 +104,21 @@ class RemoteUserGuard implements Guard | |||||||
| 
 | 
 | ||||||
|     public function check(): bool |     public function check(): bool | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('Now at %s', __METHOD__)); |         // Log::debug(sprintf('Now at %s', __METHOD__));
 | ||||||
| 
 | 
 | ||||||
|         return $this->user() instanceof User; |         return $this->user() instanceof User; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function guest(): bool |     public function guest(): bool | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('Now at %s', __METHOD__)); |         // Log::debug(sprintf('Now at %s', __METHOD__));
 | ||||||
| 
 | 
 | ||||||
|         return !$this->check(); |         return !$this->check(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function hasUser(): bool |     public function hasUser(): bool | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('Now at %s', __METHOD__)); |         // Log::debug(sprintf('Now at %s', __METHOD__));
 | ||||||
| 
 | 
 | ||||||
|         throw new FireflyException('Did not implement RemoteUserGuard::hasUser()'); |         throw new FireflyException('Did not implement RemoteUserGuard::hasUser()'); | ||||||
|     } |     } | ||||||
| @@ -128,14 +128,14 @@ class RemoteUserGuard implements Guard | |||||||
|      */ |      */ | ||||||
|     public function id(): int|string|null |     public function id(): int|string|null | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('Now at %s', __METHOD__)); |         // Log::debug(sprintf('Now at %s', __METHOD__));
 | ||||||
| 
 | 
 | ||||||
|         return $this->user?->id; |         return $this->user?->id; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function setUser(Authenticatable|User|null $user): void // @phpstan-ignore-line
 |     public function setUser(Authenticatable|User|null $user): void // @phpstan-ignore-line
 | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('Now at %s', __METHOD__)); |         // Log::debug(sprintf('Now at %s', __METHOD__));
 | ||||||
|         if ($user instanceof User) { |         if ($user instanceof User) { | ||||||
|             $this->user = $user; |             $this->user = $user; | ||||||
| 
 | 
 | ||||||
| @@ -146,7 +146,7 @@ class RemoteUserGuard implements Guard | |||||||
| 
 | 
 | ||||||
|     public function user(): ?User |     public function user(): ?User | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('Now at %s', __METHOD__)); |         // Log::debug(sprintf('Now at %s', __METHOD__));
 | ||||||
|         $user = $this->user; |         $user = $this->user; | ||||||
|         if (!$user instanceof User) { |         if (!$user instanceof User) { | ||||||
|             Log::debug('User is NULL'); |             Log::debug('User is NULL'); | ||||||
| @@ -164,14 +164,14 @@ class RemoteUserGuard implements Guard | |||||||
|      */ |      */ | ||||||
|     public function validate(array $credentials = []): bool |     public function validate(array $credentials = []): bool | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('Now at %s', __METHOD__)); |         // Log::debug(sprintf('Now at %s', __METHOD__));
 | ||||||
| 
 | 
 | ||||||
|         throw new FireflyException('Did not implement RemoteUserGuard::validate()'); |         throw new FireflyException('Did not implement RemoteUserGuard::validate()'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function viaRemember(): bool |     public function viaRemember(): bool | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('Now at %s', __METHOD__)); |         // Log::debug(sprintf('Now at %s', __METHOD__));
 | ||||||
| 
 | 
 | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -634,10 +634,10 @@ trait PeriodOverview | |||||||
|             $currencySymbol                = $journal['currency_symbol']; |             $currencySymbol                = $journal['currency_symbol']; | ||||||
|             $currencyDecimalPlaces         = $journal['currency_decimal_places']; |             $currencyDecimalPlaces         = $journal['currency_decimal_places']; | ||||||
|             $foreignCurrencyId             = $journal['foreign_currency_id']; |             $foreignCurrencyId             = $journal['foreign_currency_id']; | ||||||
|             $amount                        = $journal['amount'] ?? '0'; |             $amount                        = (string) ($journal['amount'] ?? '0'); | ||||||
| 
 | 
 | ||||||
|             if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId !== $this->primaryCurrency->id) { |             if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId !== $this->primaryCurrency->id) { | ||||||
|                 $amount                = $journal['pc_amount'] ?? '0'; |                 $amount                = (string)  ($journal['pc_amount'] ?? '0'); | ||||||
|                 $currencyId            = $this->primaryCurrency->id; |                 $currencyId            = $this->primaryCurrency->id; | ||||||
|                 $currencyCode          = $this->primaryCurrency->code; |                 $currencyCode          = $this->primaryCurrency->code; | ||||||
|                 $currencyName          = $this->primaryCurrency->name; |                 $currencyName          = $this->primaryCurrency->name; | ||||||
| @@ -650,7 +650,7 @@ trait PeriodOverview | |||||||
|                 $currencyName          = $journal['foreign_currency_name']; |                 $currencyName          = $journal['foreign_currency_name']; | ||||||
|                 $currencySymbol        = $journal['foreign_currency_symbol']; |                 $currencySymbol        = $journal['foreign_currency_symbol']; | ||||||
|                 $currencyDecimalPlaces = $journal['foreign_currency_decimal_places']; |                 $currencyDecimalPlaces = $journal['foreign_currency_decimal_places']; | ||||||
|                 $amount                = $journal['foreign_amount'] ?? '0'; |                 $amount                = (string) ($journal['foreign_amount'] ?? '0'); | ||||||
|             } |             } | ||||||
|             $return[$currencyId] ??= [ |             $return[$currencyId] ??= [ | ||||||
|                 'amount'                  => '0', |                 'amount'                  => '0', | ||||||
|   | |||||||
| @@ -289,8 +289,10 @@ class AccountEnrichment implements EnrichmentInterface | |||||||
|     { |     { | ||||||
|         $this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary); |         $this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary); | ||||||
|         if ($this->start instanceof Carbon && $this->end instanceof Carbon) { |         if ($this->start instanceof Carbon && $this->end instanceof Carbon) { | ||||||
|             $this->startBalances = Steam::accountsBalancesOptimized($this->collection, $this->start, $this->primaryCurrency, $this->convertToPrimary); |             [ | ||||||
|             $this->endBalances   = Steam::accountsBalancesOptimized($this->collection, $this->end, $this->primaryCurrency, $this->convertToPrimary); |                 $this->startBalances, | ||||||
|  |                 $this->endBalances, | ||||||
|  |             ] = Steam::accountsBalancesInRange($this->collection, $this->start, $this->end, $this->primaryCurrency, $this->convertToPrimary); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -154,6 +154,9 @@ class BudgetLimitEnrichment implements EnrichmentInterface | |||||||
|         $this->start       = $this->collection->min('start_date') ?? Carbon::now()->startOfMonth(); |         $this->start       = $this->collection->min('start_date') ?? Carbon::now()->startOfMonth(); | ||||||
|         $this->end         = $this->collection->max('end_date') ?? Carbon::now()->endOfMonth(); |         $this->end         = $this->collection->max('end_date') ?? Carbon::now()->endOfMonth(); | ||||||
| 
 | 
 | ||||||
|  |         // #11096 make sure that the max end date is also at the end of the day,
 | ||||||
|  |         $this->end->endOfDay(); | ||||||
|  | 
 | ||||||
|         /** @var BudgetLimit $limit */ |         /** @var BudgetLimit $limit */ | ||||||
|         foreach ($this->collection as $limit) { |         foreach ($this->collection as $limit) { | ||||||
|             $id          = (int)$limit->id; |             $id          = (int)$limit->id; | ||||||
|   | |||||||
| @@ -420,6 +420,7 @@ class Navigation | |||||||
|             'week'     => (string)trans('config.week_in_year_js'), |             'week'     => (string)trans('config.week_in_year_js'), | ||||||
|             'weekly'   => (string)trans('config.week_in_year_js'), |             'weekly'   => (string)trans('config.week_in_year_js'), | ||||||
|             '1M'       => (string)trans('config.month_js'), |             '1M'       => (string)trans('config.month_js'), | ||||||
|  |             'MTD'      => (string)trans('config.month_js'), | ||||||
|             'month'    => (string)trans('config.month_js'), |             'month'    => (string)trans('config.month_js'), | ||||||
|             'monthly'  => (string)trans('config.month_js'), |             'monthly'  => (string)trans('config.month_js'), | ||||||
|             '1Y'       => (string)trans('config.year_js'), |             '1Y'       => (string)trans('config.year_js'), | ||||||
| @@ -427,6 +428,11 @@ class Navigation | |||||||
|             'year'     => (string)trans('config.year_js'), |             'year'     => (string)trans('config.year_js'), | ||||||
|             'yearly'   => (string)trans('config.year_js'), |             'yearly'   => (string)trans('config.year_js'), | ||||||
|             '6M'       => (string)trans('config.half_year_js'), |             '6M'       => (string)trans('config.half_year_js'), | ||||||
|  |             'last7'    => (string)trans('config.specific_day_js'), | ||||||
|  |             'last30'   => (string)trans('config.month_js'), | ||||||
|  |             'last90'   => (string)trans('config.month_js'), | ||||||
|  |             'last365'  => (string)trans('config.year_js'), | ||||||
|  |             'QTD'      => (string)trans('config.month_js'), | ||||||
|         ]; |         ]; | ||||||
| 
 | 
 | ||||||
|         if (array_key_exists($repeatFrequency, $formatMap)) { |         if (array_key_exists($repeatFrequency, $formatMap)) { | ||||||
|   | |||||||
| @@ -88,7 +88,7 @@ trait UserGroupTrait | |||||||
|     public function setUserGroup(UserGroup $userGroup): void |     public function setUserGroup(UserGroup $userGroup): void | ||||||
|     { |     { | ||||||
|         if (null === $this->user) { |         if (null === $this->user) { | ||||||
|             Log::warning(sprintf('User is not set in repository %s', static::class)); |             Log::warning(sprintf('User is not set in repository %s. This does not have to be a problem.', static::class)); | ||||||
|         } |         } | ||||||
|         $this->userGroup = $userGroup; |         $this->userGroup = $userGroup; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -402,21 +402,21 @@ trait ConvertsDataTypes | |||||||
|      */ |      */ | ||||||
|     protected function getCarbonDate(string $field): ?Carbon |     protected function getCarbonDate(string $field): ?Carbon | ||||||
|     { |     { | ||||||
|         $result = null; |         $data = (string)$this->get($field); | ||||||
|  |         Log::debug(sprintf('Date string is "%s"', $data)); | ||||||
| 
 | 
 | ||||||
|         Log::debug(sprintf('Date string is "%s"', (string)$this->get($field))); |         if ('' === $data) { | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             $result = '' !== (string)$this->get($field) ? new Carbon((string)$this->get($field), config('app.timezone')) : null; |             return new Carbon($data, config('app.timezone')); | ||||||
|         } catch (InvalidFormatException) { |         } catch (InvalidFormatException) { | ||||||
|             // @ignoreException
 |             // @ignoreException
 | ||||||
|             Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field))); |             Log::debug(sprintf('Exception when parsing date "%s".', $data)); | ||||||
|         } |  | ||||||
|         if (!$result instanceof Carbon) { |  | ||||||
|             Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field))); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return $result; |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -49,9 +49,9 @@ use function Safe\preg_replace; | |||||||
|  */ |  */ | ||||||
| class Steam | class Steam | ||||||
| { | { | ||||||
|     public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array |     public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s"', $accounts->count(), $date->toIso8601String())); |         Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s" (inclusive: %s)', $accounts->count(), $date->toIso8601String(), var_export($inclusive, true))); | ||||||
|         $result      = []; |         $result      = []; | ||||||
|         $convertToPrimary ??= Amount::convertToPrimary(); |         $convertToPrimary ??= Amount::convertToPrimary(); | ||||||
|         $primary          ??= Amount::getPrimaryCurrency(); |         $primary          ??= Amount::getPrimaryCurrency(); | ||||||
| @@ -61,14 +61,15 @@ class Steam | |||||||
|         $arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray()) |         $arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray()) | ||||||
|             ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') |             ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||||
|             ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') |             ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') | ||||||
|             ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) |             ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s')) | ||||||
|             ->groupBy(['transactions.account_id', 'transaction_currencies.code']) |             ->groupBy(['transactions.account_id', 'transaction_currencies.code']) | ||||||
|             ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray() |             ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray() | ||||||
|         ; |         ; | ||||||
| 
 | 
 | ||||||
|  |         Log::debug('Array of sums: ', $arrayOfSums); | ||||||
|  | 
 | ||||||
|         /** @var Account $account */ |         /** @var Account $account */ | ||||||
|         foreach ($accounts as $account) { |         foreach ($accounts as $account) { | ||||||
|             // this array is PER account, so we wait a bit before we change code here.
 |  | ||||||
|             $return               = [ |             $return               = [ | ||||||
|                 'pc_balance' => '0', |                 'pc_balance' => '0', | ||||||
|                 'balance'    => '0', // this key is overwritten right away, but I must remember it is always created.
 |                 'balance'    => '0', // this key is overwritten right away, but I must remember it is always created.
 | ||||||
| @@ -76,19 +77,19 @@ class Steam | |||||||
|             $currency             = $currencies[$account->id]; |             $currency             = $currencies[$account->id]; | ||||||
| 
 | 
 | ||||||
|             // second array
 |             // second array
 | ||||||
|             $accountSum           = array_filter($arrayOfSums, fn ($entry) => $entry['account_id'] === $account->id); |             $accountSums          = array_filter($arrayOfSums, fn ($entry) => $entry['account_id'] === $account->id); | ||||||
|             if (0 === count($accountSum)) { |             if (0 === count($accountSums)) { | ||||||
|                 $result[$account->id] = $return; |                 $result[$account->id] = $return; | ||||||
| 
 | 
 | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             $accountSum           = array_values($accountSum)[0]; |             $sumsByCode           = []; | ||||||
|             $sumOfAmount          = (string)$accountSum['sum_of_amount']; |             foreach ($accountSums as $accountSum) { | ||||||
|             $sumOfAmount          = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount); |                 // $accountSum  = array_values($accountSum)[0];
 | ||||||
|             $sumsByCode           = [ |                 $sumOfAmount                     = (string)$accountSum['sum_of_amount']; | ||||||
|                 $accountSum['code'] => $sumOfAmount, |                 $sumOfAmount                     = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount); | ||||||
|             ]; |                 $sumsByCode[$accountSum['code']] = $sumOfAmount; | ||||||
| 
 |             } | ||||||
|             // Log::debug('All balances are (joined)', $others);
 |             // Log::debug('All balances are (joined)', $others);
 | ||||||
|             // if there is no request to convert, take this as "balance" and "pc_balance".
 |             // if there is no request to convert, take this as "balance" and "pc_balance".
 | ||||||
|             $return['balance']    = $sumsByCode[$currency->code] ?? '0'; |             $return['balance']    = $sumsByCode[$currency->code] ?? '0'; | ||||||
| @@ -96,6 +97,7 @@ class Steam | |||||||
|                 unset($return['pc_balance']); |                 unset($return['pc_balance']); | ||||||
|                 // Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
 |                 // Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
 | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             // if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
 |             // if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
 | ||||||
|             if ($convertToPrimary) { |             if ($convertToPrimary) { | ||||||
|                 $return['pc_balance'] = $this->convertAllBalances($sumsByCode, $primary, $date); |                 $return['pc_balance'] = $this->convertAllBalances($sumsByCode, $primary, $date); | ||||||
| @@ -119,12 +121,23 @@ class Steam | |||||||
|             } |             } | ||||||
|             $final                = array_merge($return, $sumsByCode); |             $final                = array_merge($return, $sumsByCode); | ||||||
|             $result[$account->id] = $final; |             $result[$account->id] = $final; | ||||||
|             // Log::debug('Final balance is', $final);
 |             Log::debug(sprintf('Final balance for account #%d is', $account->id), $final); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return $result; |         return $result; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Calls accountsBalancesOptimized for the given accounts and makes sure that inclusive is set to false, so it properly gets the balance of a range. | ||||||
|  |      */ | ||||||
|  |     public function accountsBalancesInRange(Collection $accounts, Carbon $start, Carbon $end, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             $this->accountsBalancesOptimized($accounts, $start, $primary, $convertToPrimary, inclusive: false), | ||||||
|  |             $this->accountsBalancesOptimized($accounts, $end, $primary, $convertToPrimary), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers |      * https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers | ||||||
|      */ |      */ | ||||||
| @@ -276,7 +289,9 @@ class Steam | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns smaller than or equal to, so be careful with END OF DAY. |      * @deprecated | ||||||
|  |      * By default this method returns "smaller than or equal to", so be careful with END OF DAY. | ||||||
|  |      * If you need end of day balance, use "inclusive = false". | ||||||
|      * |      * | ||||||
|      * Returns the balance of an account at exact moment given. Array with at least one value. |      * Returns the balance of an account at exact moment given. Array with at least one value. | ||||||
|      * Always returns: |      * Always returns: | ||||||
| @@ -288,18 +303,17 @@ class Steam | |||||||
|      * --> "pc_balance": balance in the user's primary currency, with all amounts converted to the primary currency. |      * --> "pc_balance": balance in the user's primary currency, with all amounts converted to the primary currency. | ||||||
|      * "EUR": balance in EUR (or whatever currencies the account has balance in) |      * "EUR": balance in EUR (or whatever currencies the account has balance in) | ||||||
|      */ |      */ | ||||||
|     public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array |     public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array | ||||||
|     { |     { | ||||||
| 
 | 
 | ||||||
|         $cache             = new CacheProperties(); |         $cache             = new CacheProperties(); | ||||||
|         $cache->addProperty($account->id); |         $cache->addProperty($account->id); | ||||||
|         $cache->addProperty($date); |         $cache->addProperty($date); | ||||||
|         if ($cache->has()) { |         if ($cache->has()) { | ||||||
|             Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s'))); |             Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s, inclusive:%s)', $account->id, $date->format('Y-m-d H:i:s'), var_export($inclusive, true))); | ||||||
| 
 |  | ||||||
|             // return $cache->get();
 |             // return $cache->get();
 | ||||||
|         } |         } | ||||||
|         // Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
 |         Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s'))); | ||||||
|         if (null === $convertToPrimary) { |         if (null === $convertToPrimary) { | ||||||
|             $convertToPrimary = Amount::convertToPrimary($account->user); |             $convertToPrimary = Amount::convertToPrimary($account->user); | ||||||
|         } |         } | ||||||
| @@ -312,7 +326,6 @@ class Steam | |||||||
|             $accountCurrency = $account->meta['currency']; |             $accountCurrency = $account->meta['currency']; | ||||||
|         } |         } | ||||||
|         if (!$currencyPresent) { |         if (!$currencyPresent) { | ||||||
| 
 |  | ||||||
|             $accountCurrency = $this->getAccountCurrency($account); |             $accountCurrency = $this->getAccountCurrency($account); | ||||||
|         } |         } | ||||||
|         $hasCurrency       = null !== $accountCurrency; |         $hasCurrency       = null !== $accountCurrency; | ||||||
| @@ -325,11 +338,11 @@ class Steam | |||||||
|         $array             = $account->transactions() |         $array             = $account->transactions() | ||||||
|             ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') |             ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') | ||||||
|             ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') |             ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') | ||||||
|             ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) |             ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s')) | ||||||
|             ->get(['transaction_currencies.code', 'transactions.amount'])->toArray() |             ->get(['transaction_currencies.code', 'transactions.amount'])->toArray() | ||||||
|         ; |         ; | ||||||
|         $others            = $this->groupAndSumTransactions($array, 'code', 'amount'); |         $others            = $this->groupAndSumTransactions($array, 'code', 'amount'); | ||||||
|         // Log::debug('All balances are (joined)', $others);
 |         Log::debug('All balances are (joined)', $others); | ||||||
|         // if there is no request to convert, take this as "balance" and "pc_balance".
 |         // if there is no request to convert, take this as "balance" and "pc_balance".
 | ||||||
|         $return['balance'] = $others[$currency->code] ?? '0'; |         $return['balance'] = $others[$currency->code] ?? '0'; | ||||||
|         if (!$convertToPrimary) { |         if (!$convertToPrimary) { | ||||||
| @@ -338,7 +351,7 @@ class Steam | |||||||
|         } |         } | ||||||
|         // if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
 |         // if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
 | ||||||
|         if ($convertToPrimary) { |         if ($convertToPrimary) { | ||||||
|             $return['pc_balance'] = $this->convertAllBalances($others, $primary, $date); // todo sum all and convert.
 |             $return['pc_balance'] = $this->convertAllBalances($others, $primary, $date); | ||||||
|             // Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance']));
 |             // Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance']));
 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -358,18 +371,21 @@ class Steam | |||||||
|             // Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
 |             // Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
 | ||||||
|         } |         } | ||||||
|         $final             = array_merge($return, $others); |         $final             = array_merge($return, $others); | ||||||
|         // Log::debug('Final balance is', $final);
 |         Log::debug('Final balance is', $final); | ||||||
|         $cache->store($final); |         $cache->store($final); | ||||||
| 
 | 
 | ||||||
|         return $final; |         return $final; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Returns the balance for the given account in the range, with daily precision. | ||||||
|  |      */ | ||||||
|     public function finalAccountBalanceInRange(Account $account, Carbon $start, Carbon $end, bool $convertToPrimary): array |     public function finalAccountBalanceInRange(Account $account, Carbon $start, Carbon $end, bool $convertToPrimary): array | ||||||
|     { |     { | ||||||
|         // expand period.
 |         // expand period.
 | ||||||
|         $start->startOfDay(); |         $start->startOfDay(); | ||||||
|         $end->endOfDay(); |         $end->endOfDay(); | ||||||
|         Log::debug(sprintf('finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); |         Log::debug(sprintf('called finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); | ||||||
| 
 | 
 | ||||||
|         // set up cache
 |         // set up cache
 | ||||||
|         $cache                = new CacheProperties(); |         $cache                = new CacheProperties(); | ||||||
| @@ -379,28 +395,21 @@ class Steam | |||||||
|         $cache->addProperty($convertToPrimary); |         $cache->addProperty($convertToPrimary); | ||||||
|         $cache->addProperty($end); |         $cache->addProperty($end); | ||||||
|         if ($cache->has()) { |         if ($cache->has()) { | ||||||
|             return $cache->get(); |             Log::debug('Return cached finalAccountBalanceInRange'); | ||||||
|  |             // return $cache->get();
 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $balances             = []; |         $balances             = []; | ||||||
|         $formatted            = $start->format('Y-m-d'); |         $formatted            = $start->format('Y-m-d'); | ||||||
|         /* |  | ||||||
|          * To make sure the start balance is correct, we need to get the balance at the exact end of the previous day. |  | ||||||
|          * Since we just did "startOfDay" we can do subDay()->endOfDay() to get the correct moment. |  | ||||||
|          * THAT will be the start balance. |  | ||||||
|          */ |  | ||||||
|         $request              = clone $start; |  | ||||||
|         $request->subDay()->endOfDay(); |  | ||||||
|         Log::debug('Get first balance to start.'); |         Log::debug('Get first balance to start.'); | ||||||
|         Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String())); |         // 2025-10-08 replaced finalAccountBalance with accountsBalancesOptimized:
 | ||||||
|         $startBalance         = $this->finalAccountBalance($account, $request); |  | ||||||
|         $primaryCurrency      = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup); |         $primaryCurrency      = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup); | ||||||
|  |         $startBalance         = $this->accountsBalancesOptimized(new Collection()->push($account), $start, $primaryCurrency, $convertToPrimary, false)[$account->id]; | ||||||
|         $accountCurrency      = $this->getAccountCurrency($account); |         $accountCurrency      = $this->getAccountCurrency($account); | ||||||
|         $hasCurrency          = $accountCurrency instanceof TransactionCurrency; |         $hasCurrency          = $accountCurrency instanceof TransactionCurrency; | ||||||
|         $currency             = $accountCurrency ?? $primaryCurrency; |         $currency             = $accountCurrency ?? $primaryCurrency; | ||||||
|         Log::debug(sprintf('Currency is %s', $currency->code)); |         Log::debug(sprintf('Currency is %s', $currency->code)); | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|         // set start balances:
 |         // set start balances:
 | ||||||
|         $startBalance[$currency->code] ??= '0'; |         $startBalance[$currency->code] ??= '0'; | ||||||
|         if ($hasCurrency) { |         if ($hasCurrency) { | ||||||
| @@ -542,7 +551,7 @@ class Steam | |||||||
|         try { |         try { | ||||||
|             $hostName = gethostbyaddr($ipAddress); |             $hostName = gethostbyaddr($ipAddress); | ||||||
|         } catch (Exception $e) { |         } catch (Exception $e) { | ||||||
|             app('log')->error($e->getMessage()); |             Log::error($e->getMessage()); | ||||||
|             $hostName = $ipAddress; |             $hostName = $ipAddress; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @@ -618,6 +627,19 @@ class Steam | |||||||
|         return $locale; |         return $locale; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function anonymous(): bool // get preference
 | ||||||
|  |     { | ||||||
|  |         $singleton = PreferencesSingleton::getInstance(); | ||||||
|  |         $cached    = $singleton->getPreference('anonymous'); | ||||||
|  |         if (null !== $cached) { | ||||||
|  |             return $cached; | ||||||
|  |         } | ||||||
|  |         $anonymous = app('preferences')->get('anonymous', config('firefly.default_preferences.anonymous', false))->data; | ||||||
|  |         $singleton->setPreference('anonymous', $anonymous); | ||||||
|  | 
 | ||||||
|  |         return $anonymous; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function getLocaleArray(string $locale): array |     public function getLocaleArray(string $locale): array | ||||||
|     { |     { | ||||||
|         return [ |         return [ | ||||||
| @@ -757,7 +779,7 @@ class Steam | |||||||
|                 $current = $converter->convert($currency, $primary, $date, $amount); |                 $current = $converter->convert($currency, $primary, $date, $amount); | ||||||
|                 Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $primary->code, $current)); |                 Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $primary->code, $current)); | ||||||
|             } |             } | ||||||
|             $total      = bcadd((string) $current, $total); |             $total      = bcadd((string)$current, $total); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return $total; |         return $total; | ||||||
|   | |||||||
| @@ -67,32 +67,16 @@ trait IsOldVersion | |||||||
|     protected function isOldVersionInstalled(): bool |     protected function isOldVersionInstalled(): bool | ||||||
|     { |     { | ||||||
|         // version compare thing.
 |         // version compare thing.
 | ||||||
|         $configVersion = (string)config('firefly.version'); |         $configBuildTime = (int)config('firefly.build_time'); | ||||||
|         $dbVersion     = (string)FireflyConfig::getFresh('ff3_version', '1.0')->data; |         $dbBuildTime     = (int)FireflyConfig::getFresh('ff3_build_time', 123)->data; | ||||||
|         $compare       = 0; |         $configTime      = Carbon::createFromTimestamp($configBuildTime, config('app.timezone')); | ||||||
|         // compare develop to develop
 |         $dbTime          = Carbon::createFromTimestamp($dbBuildTime, config('app.timezone')); | ||||||
|         if (str_starts_with($configVersion, 'develop') && str_starts_with($dbVersion, 'develop')) { |         if ($dbBuildTime < $configBuildTime) { | ||||||
|             $compare = $this->compareDevelopVersions($configVersion, $dbVersion); |             Log::warning(sprintf('Your database was last managed by an older version of Firefly III (I see %s, I expect %s). Redirect to migrate routine.', $dbTime->format('Y-m-d H:i:s'), $configTime->format('Y-m-d H:i:s'))); | ||||||
|         } |  | ||||||
|         // user has develop installed, goes to normal version.
 |  | ||||||
|         if (!str_starts_with($configVersion, 'develop') && str_starts_with($dbVersion, 'develop')) { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // user has normal, goes to develop version.
 |  | ||||||
|         if (str_starts_with($configVersion, 'develop') && !str_starts_with($dbVersion, 'develop')) { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // compare normal with normal.
 |  | ||||||
|         if (!str_starts_with($configVersion, 'develop') && !str_starts_with($dbVersion, 'develop')) { |  | ||||||
|             $compare = version_compare($configVersion, $dbVersion); |  | ||||||
|         } |  | ||||||
|         if (-1 === $compare) { |  | ||||||
|             Log::warning(sprintf('The current configured Firefly III version (%s) is older than the required version (%s). Redirect to migrate routine.', $dbVersion, $configVersion)); |  | ||||||
| 
 | 
 | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|  |         Log::debug(sprintf('Your database is up to date (I see %s, I expect %s).', $dbTime->format('Y-m-d H:i:s'), $configTime->format('Y-m-d H:i:s'))); | ||||||
| 
 | 
 | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ declare(strict_types=1); | |||||||
| namespace FireflyIII\Support\System; | namespace FireflyIII\Support\System; | ||||||
| 
 | 
 | ||||||
| use FireflyIII\Exceptions\FireflyException; | use FireflyIII\Exceptions\FireflyException; | ||||||
|  | use FireflyIII\Support\Facades\FireflyConfig; | ||||||
| use Illuminate\Contracts\Encryption\DecryptException; | use Illuminate\Contracts\Encryption\DecryptException; | ||||||
| use Illuminate\Support\Facades\Artisan; | use Illuminate\Support\Facades\Artisan; | ||||||
| use Illuminate\Support\Facades\Crypt; | use Illuminate\Support\Facades\Crypt; | ||||||
| @@ -63,10 +64,10 @@ class OAuthKeys | |||||||
|         $privateKey = ''; |         $privateKey = ''; | ||||||
|         $publicKey  = ''; |         $publicKey  = ''; | ||||||
|         // better check if keys are in the database:
 |         // better check if keys are in the database:
 | ||||||
|         if (app('fireflyconfig')->has(self::PRIVATE_KEY) && app('fireflyconfig')->has(self::PUBLIC_KEY)) { |         if (FireflyConfig::has(self::PRIVATE_KEY) && FireflyConfig::has(self::PUBLIC_KEY)) { | ||||||
|             try { |             try { | ||||||
|                 $privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data; |                 $privateKey = (string)FireflyConfig::get(self::PRIVATE_KEY)?->data; | ||||||
|                 $publicKey  = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data; |                 $publicKey  = (string)FireflyConfig::get(self::PUBLIC_KEY)?->data; | ||||||
|             } catch (ContainerExceptionInterface|FireflyException|NotFoundExceptionInterface $e) { |             } catch (ContainerExceptionInterface|FireflyException|NotFoundExceptionInterface $e) { | ||||||
|                 app('log')->error(sprintf('Could not validate keysInDatabase(): %s', $e->getMessage())); |                 app('log')->error(sprintf('Could not validate keysInDatabase(): %s', $e->getMessage())); | ||||||
|                 app('log')->error($e->getTraceAsString()); |                 app('log')->error($e->getTraceAsString()); | ||||||
| @@ -87,8 +88,8 @@ class OAuthKeys | |||||||
|      */ |      */ | ||||||
|     public static function restoreKeysFromDB(): bool |     public static function restoreKeysFromDB(): bool | ||||||
|     { |     { | ||||||
|         $privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data; |         $privateKey = (string)FireflyConfig::get(self::PRIVATE_KEY)?->data; | ||||||
|         $publicKey  = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data; |         $publicKey  = (string)FireflyConfig::get(self::PUBLIC_KEY)?->data; | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             $privateContent = Crypt::decrypt($privateKey); |             $privateContent = Crypt::decrypt($privateKey); | ||||||
| @@ -98,8 +99,8 @@ class OAuthKeys | |||||||
|             app('log')->error($e->getMessage()); |             app('log')->error($e->getMessage()); | ||||||
| 
 | 
 | ||||||
|             // delete config vars from DB:
 |             // delete config vars from DB:
 | ||||||
|             app('fireflyconfig')->delete(self::PRIVATE_KEY); |             FireflyConfig::delete(self::PRIVATE_KEY); | ||||||
|             app('fireflyconfig')->delete(self::PUBLIC_KEY); |             FireflyConfig::delete(self::PUBLIC_KEY); | ||||||
| 
 | 
 | ||||||
|             return false; |             return false; | ||||||
|         } |         } | ||||||
| @@ -115,8 +116,8 @@ class OAuthKeys | |||||||
|     { |     { | ||||||
|         $private = storage_path('oauth-private.key'); |         $private = storage_path('oauth-private.key'); | ||||||
|         $public  = storage_path('oauth-public.key'); |         $public  = storage_path('oauth-public.key'); | ||||||
|         app('fireflyconfig')->set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private))); |         FireflyConfig::set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private))); | ||||||
|         app('fireflyconfig')->set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public))); |         FireflyConfig::set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static function verifyKeysRoutine(): void |     public static function verifyKeysRoutine(): void | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ use FireflyIII\Repositories\User\UserRepositoryInterface; | |||||||
| use FireflyIII\Support\Facades\Amount; | use FireflyIII\Support\Facades\Amount; | ||||||
| use FireflyIII\Support\Facades\Steam; | use FireflyIII\Support\Facades\Steam; | ||||||
| use FireflyIII\Support\Search\OperatorQuerySearch; | use FireflyIII\Support\Search\OperatorQuerySearch; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
| use Illuminate\Support\Facades\Log; | use Illuminate\Support\Facades\Log; | ||||||
| use Illuminate\Support\Facades\Route; | use Illuminate\Support\Facades\Route; | ||||||
| use League\CommonMark\GithubFlavoredMarkdownConverter; | use League\CommonMark\GithubFlavoredMarkdownConverter; | ||||||
| @@ -155,9 +156,12 @@ class General extends AbstractExtension | |||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 /** @var Carbon $date */ |                 /** @var Carbon $date */ | ||||||
|                 $date             = session('end', today(config('app.timezone'))->endOfMonth()); |                 $date             = now(); | ||||||
|                 Log::debug(sprintf('twig balance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); |                 Log::debug(sprintf('twig balance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); | ||||||
|                 $info             = Steam::finalAccountBalance($account, $date); | 
 | ||||||
|  |                 // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized.
 | ||||||
|  |                 $info             = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]; | ||||||
|  |                 // $info             = Steam::finalAccountBalance($account, $date);
 | ||||||
|                 $currency         = Steam::getAccountCurrency($account); |                 $currency         = Steam::getAccountCurrency($account); | ||||||
|                 $primary          = Amount::getPrimaryCurrency(); |                 $primary          = Amount::getPrimaryCurrency(); | ||||||
|                 $convertToPrimary = Amount::convertToPrimary(); |                 $convertToPrimary = Amount::convertToPrimary(); | ||||||
|   | |||||||
| @@ -59,9 +59,7 @@ class UpdatePiggyBank implements ActionInterface | |||||||
| 
 | 
 | ||||||
|         $piggyBank   = $this->findPiggyBank($user, $actionValue); |         $piggyBank   = $this->findPiggyBank($user, $actionValue); | ||||||
|         if (!$piggyBank instanceof PiggyBank) { |         if (!$piggyBank instanceof PiggyBank) { | ||||||
|             Log::info( |             Log::info(sprintf('No piggy bank named "%s", cant execute action #%d of rule #%d', $actionValue, $this->action->id, $this->action->rule_id)); | ||||||
|                 sprintf('No piggy bank named "%s", cant execute action #%d of rule #%d', $actionValue, $this->action->id, $this->action->rule_id) |  | ||||||
|             ); |  | ||||||
|             event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_piggy', ['name' => $actionValue]))); |             event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_piggy', ['name' => $actionValue]))); | ||||||
| 
 | 
 | ||||||
|             return false; |             return false; | ||||||
|   | |||||||
| @@ -506,9 +506,23 @@ class SearchRuleEngine implements RuleEngineInterface | |||||||
|      */ |      */ | ||||||
|     private function fireGroup(RuleGroup $group): void |     private function fireGroup(RuleGroup $group): void | ||||||
|     { |     { | ||||||
|         Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $group->rules->count())); |         $rules = []; | ||||||
| 
 |         if ($group->relationLoaded('rules')) { | ||||||
|         $rules = $group->rules()->orderBy('order', 'ASC')->get(); |             Log::debug('Group rules have been pre-loaded, do not reload them.'); | ||||||
|  |             $rules = $group->rules; | ||||||
|  |         } | ||||||
|  |         if (!$group->relationLoaded('rules')) { | ||||||
|  |             Log::debug('Group rules have NOT been pre-loaded, load them NOW.'); | ||||||
|  |             $rules = $group->rules() | ||||||
|  |                 ->orderBy('rules.order', 'ASC') | ||||||
|  | //                         ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
 | ||||||
|  | //                         ->where('rule_triggers.trigger_type', 'user_action')
 | ||||||
|  | //                         ->where('rule_triggers.trigger_value', 'store-journal')
 | ||||||
|  |                 ->where('rules.active', true) | ||||||
|  |                 ->get(['rules.*']) | ||||||
|  |             ; | ||||||
|  |         } | ||||||
|  |         Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $rules->count())); | ||||||
| 
 | 
 | ||||||
|         /** @var Rule $rule */ |         /** @var Rule $rule */ | ||||||
|         foreach ($rules as $rule) { |         foreach ($rules as $rule) { | ||||||
|   | |||||||
| @@ -34,11 +34,17 @@ abstract class AbstractTransformer extends TransformerAbstract | |||||||
| { | { | ||||||
|     protected ParameterBag $parameters; |     protected ParameterBag $parameters; | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @deprecated | ||||||
|  |      */ | ||||||
|     final public function getParameters(): ParameterBag |     final public function getParameters(): ParameterBag | ||||||
|     { |     { | ||||||
|         return $this->parameters; |         return $this->parameters; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * @deprecated | ||||||
|  |      */ | ||||||
|     final public function setParameters(ParameterBag $parameters): void |     final public function setParameters(ParameterBag $parameters): void | ||||||
|     { |     { | ||||||
|         $this->parameters = $parameters; |         $this->parameters = $parameters; | ||||||
|   | |||||||
| @@ -30,7 +30,6 @@ use FireflyIII\Models\Account; | |||||||
| use FireflyIII\Models\TransactionCurrency; | use FireflyIII\Models\TransactionCurrency; | ||||||
| use FireflyIII\Repositories\Account\AccountRepositoryInterface; | use FireflyIII\Repositories\Account\AccountRepositoryInterface; | ||||||
| use FireflyIII\Support\Facades\Amount; | use FireflyIII\Support\Facades\Amount; | ||||||
| use Symfony\Component\HttpFoundation\ParameterBag; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Class AccountTransformer |  * Class AccountTransformer | ||||||
| @@ -46,7 +45,6 @@ class AccountTransformer extends AbstractTransformer | |||||||
|      */ |      */ | ||||||
|     public function __construct() |     public function __construct() | ||||||
|     { |     { | ||||||
|         $this->parameters       = new ParameterBag(); |  | ||||||
|         $this->repository       = app(AccountRepositoryInterface::class); |         $this->repository       = app(AccountRepositoryInterface::class); | ||||||
|         $this->convertToPrimary = Amount::convertToPrimary(); |         $this->convertToPrimary = Amount::convertToPrimary(); | ||||||
|         $this->primary          = Amount::getPrimaryCurrency(); |         $this->primary          = Amount::getPrimaryCurrency(); | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ Everything from v6.4.1, plus: | |||||||
| 
 | 
 | ||||||
| - [Issue 11015](https://github.com/firefly-iii/firefly-iii/issues/11015) (Call to a member function subSecond() on null when viewing accounts) reported by @sirgio145 | - [Issue 11015](https://github.com/firefly-iii/firefly-iii/issues/11015) (Call to a member function subSecond() on null when viewing accounts) reported by @sirgio145 | ||||||
| - [Issue 11016](https://github.com/firefly-iii/firefly-iii/issues/11016) (Undefined array key 1 when viewing subscriptions) reported by @anuneo | - [Issue 11016](https://github.com/firefly-iii/firefly-iii/issues/11016) (Undefined array key 1 when viewing subscriptions) reported by @anuneo | ||||||
|  | - [Issue 11018](https://github.com/firefly-iii/firefly-iii/issues/11018) (/v1/accounts balance_difference last day is not accounted for) reported by @ctrl-f5 | ||||||
| 
 | 
 | ||||||
| ## 6.4.1 - 2025-10-07 | ## 6.4.1 - 2025-10-07 | ||||||
| 
 | 
 | ||||||
|   | |||||||
							
								
								
									
										188
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										188
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							| @@ -1878,16 +1878,16 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "laravel/framework", |             "name": "laravel/framework", | ||||||
|             "version": "v12.32.5", |             "version": "v12.35.1", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/laravel/framework.git", |                 "url": "https://github.com/laravel/framework.git", | ||||||
|                 "reference": "77b2740391cd2a825ba59d6fada45e9b8b9bcc5a" |                 "reference": "d6d6e3cb68238e2fb25b440f222442adef5a8a15" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/laravel/framework/zipball/77b2740391cd2a825ba59d6fada45e9b8b9bcc5a", |                 "url": "https://api.github.com/repos/laravel/framework/zipball/d6d6e3cb68238e2fb25b440f222442adef5a8a15", | ||||||
|                 "reference": "77b2740391cd2a825ba59d6fada45e9b8b9bcc5a", |                 "reference": "d6d6e3cb68238e2fb25b440f222442adef5a8a15", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
| @@ -1999,7 +1999,7 @@ | |||||||
|                 "league/flysystem-sftp-v3": "^3.25.1", |                 "league/flysystem-sftp-v3": "^3.25.1", | ||||||
|                 "mockery/mockery": "^1.6.10", |                 "mockery/mockery": "^1.6.10", | ||||||
|                 "opis/json-schema": "^2.4.1", |                 "opis/json-schema": "^2.4.1", | ||||||
|                 "orchestra/testbench-core": "^10.6.5", |                 "orchestra/testbench-core": "^10.7.0", | ||||||
|                 "pda/pheanstalk": "^5.0.6|^7.0.0", |                 "pda/pheanstalk": "^5.0.6|^7.0.0", | ||||||
|                 "php-http/discovery": "^1.15", |                 "php-http/discovery": "^1.15", | ||||||
|                 "phpstan/phpstan": "^2.0", |                 "phpstan/phpstan": "^2.0", | ||||||
| @@ -2093,7 +2093,7 @@ | |||||||
|                 "issues": "https://github.com/laravel/framework/issues", |                 "issues": "https://github.com/laravel/framework/issues", | ||||||
|                 "source": "https://github.com/laravel/framework" |                 "source": "https://github.com/laravel/framework" | ||||||
|             }, |             }, | ||||||
|             "time": "2025-09-30T17:39:22+00:00" |             "time": "2025-10-23T15:25:03+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "laravel/passport", |             "name": "laravel/passport", | ||||||
| @@ -2296,16 +2296,16 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "laravel/serializable-closure", |             "name": "laravel/serializable-closure", | ||||||
|             "version": "v2.0.5", |             "version": "v2.0.6", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/laravel/serializable-closure.git", |                 "url": "https://github.com/laravel/serializable-closure.git", | ||||||
|                 "reference": "3832547db6e0e2f8bb03d4093857b378c66eceed" |                 "reference": "038ce42edee619599a1debb7e81d7b3759492819" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3832547db6e0e2f8bb03d4093857b378c66eceed", |                 "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819", | ||||||
|                 "reference": "3832547db6e0e2f8bb03d4093857b378c66eceed", |                 "reference": "038ce42edee619599a1debb7e81d7b3759492819", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
| @@ -2353,7 +2353,7 @@ | |||||||
|                 "issues": "https://github.com/laravel/serializable-closure/issues", |                 "issues": "https://github.com/laravel/serializable-closure/issues", | ||||||
|                 "source": "https://github.com/laravel/serializable-closure" |                 "source": "https://github.com/laravel/serializable-closure" | ||||||
|             }, |             }, | ||||||
|             "time": "2025-09-22T17:29:40+00:00" |             "time": "2025-10-09T13:42:30+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "laravel/slack-notification-channel", |             "name": "laravel/slack-notification-channel", | ||||||
| @@ -2485,34 +2485,34 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "lcobucci/clock", |             "name": "lcobucci/clock", | ||||||
|             "version": "3.3.1", |             "version": "3.4.0", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/lcobucci/clock.git", |                 "url": "https://github.com/lcobucci/clock.git", | ||||||
|                 "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b" |                 "reference": "f91d84f65cb3e974988bbe872b5da8ca132a155f" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/lcobucci/clock/zipball/db3713a61addfffd615b79bf0bc22f0ccc61b86b", |                 "url": "https://api.github.com/repos/lcobucci/clock/zipball/f91d84f65cb3e974988bbe872b5da8ca132a155f", | ||||||
|                 "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b", |                 "reference": "f91d84f65cb3e974988bbe872b5da8ca132a155f", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
|                 "php": "~8.2.0 || ~8.3.0 || ~8.4.0", |                 "php": "~8.3.0 || ~8.4.0", | ||||||
|                 "psr/clock": "^1.0" |                 "psr/clock": "^1.0" | ||||||
|             }, |             }, | ||||||
|             "provide": { |             "provide": { | ||||||
|                 "psr/clock-implementation": "1.0" |                 "psr/clock-implementation": "1.0" | ||||||
|             }, |             }, | ||||||
|             "require-dev": { |             "require-dev": { | ||||||
|                 "infection/infection": "^0.29", |                 "infection/infection": "^0.31", | ||||||
|                 "lcobucci/coding-standard": "^11.1.0", |                 "lcobucci/coding-standard": "^11.1.0", | ||||||
|                 "phpstan/extension-installer": "^1.3.1", |                 "phpstan/extension-installer": "^1.3.1", | ||||||
|                 "phpstan/phpstan": "^1.10.25", |                 "phpstan/phpstan": "^2.0.0", | ||||||
|                 "phpstan/phpstan-deprecation-rules": "^1.1.3", |                 "phpstan/phpstan-deprecation-rules": "^2.0.0", | ||||||
|                 "phpstan/phpstan-phpunit": "^1.3.13", |                 "phpstan/phpstan-phpunit": "^2.0.0", | ||||||
|                 "phpstan/phpstan-strict-rules": "^1.5.1", |                 "phpstan/phpstan-strict-rules": "^2.0.0", | ||||||
|                 "phpunit/phpunit": "^11.3.6" |                 "phpunit/phpunit": "^12.0.0" | ||||||
|             }, |             }, | ||||||
|             "type": "library", |             "type": "library", | ||||||
|             "autoload": { |             "autoload": { | ||||||
| @@ -2533,7 +2533,7 @@ | |||||||
|             "description": "Yet another clock abstraction", |             "description": "Yet another clock abstraction", | ||||||
|             "support": { |             "support": { | ||||||
|                 "issues": "https://github.com/lcobucci/clock/issues", |                 "issues": "https://github.com/lcobucci/clock/issues", | ||||||
|                 "source": "https://github.com/lcobucci/clock/tree/3.3.1" |                 "source": "https://github.com/lcobucci/clock/tree/3.4.0" | ||||||
|             }, |             }, | ||||||
|             "funding": [ |             "funding": [ | ||||||
|                 { |                 { | ||||||
| @@ -2545,26 +2545,26 @@ | |||||||
|                     "type": "patreon" |                     "type": "patreon" | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             "time": "2024-09-24T20:45:14+00:00" |             "time": "2025-10-08T18:00:48+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "lcobucci/jwt", |             "name": "lcobucci/jwt", | ||||||
|             "version": "5.5.0", |             "version": "5.6.0", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/lcobucci/jwt.git", |                 "url": "https://github.com/lcobucci/jwt.git", | ||||||
|                 "reference": "a835af59b030d3f2967725697cf88300f579088e" |                 "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a835af59b030d3f2967725697cf88300f579088e", |                 "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", | ||||||
|                 "reference": "a835af59b030d3f2967725697cf88300f579088e", |                 "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
|                 "ext-openssl": "*", |                 "ext-openssl": "*", | ||||||
|                 "ext-sodium": "*", |                 "ext-sodium": "*", | ||||||
|                 "php": "~8.2.0 || ~8.3.0 || ~8.4.0", |                 "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", | ||||||
|                 "psr/clock": "^1.0" |                 "psr/clock": "^1.0" | ||||||
|             }, |             }, | ||||||
|             "require-dev": { |             "require-dev": { | ||||||
| @@ -2606,7 +2606,7 @@ | |||||||
|             ], |             ], | ||||||
|             "support": { |             "support": { | ||||||
|                 "issues": "https://github.com/lcobucci/jwt/issues", |                 "issues": "https://github.com/lcobucci/jwt/issues", | ||||||
|                 "source": "https://github.com/lcobucci/jwt/tree/5.5.0" |                 "source": "https://github.com/lcobucci/jwt/tree/5.6.0" | ||||||
|             }, |             }, | ||||||
|             "funding": [ |             "funding": [ | ||||||
|                 { |                 { | ||||||
| @@ -2618,7 +2618,7 @@ | |||||||
|                     "type": "patreon" |                     "type": "patreon" | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             "time": "2025-01-26T21:29:45+00:00" |             "time": "2025-10-17T11:30:53+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "league/commonmark", |             "name": "league/commonmark", | ||||||
| @@ -2811,16 +2811,16 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "league/csv", |             "name": "league/csv", | ||||||
|             "version": "9.26.0", |             "version": "9.27.1", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/thephpleague/csv.git", |                 "url": "https://github.com/thephpleague/csv.git", | ||||||
|                 "reference": "7fce732754d043f3938899e5183e2d0f3d31b571" |                 "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/thephpleague/csv/zipball/7fce732754d043f3938899e5183e2d0f3d31b571", |                 "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797", | ||||||
|                 "reference": "7fce732754d043f3938899e5183e2d0f3d31b571", |                 "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
| @@ -2898,7 +2898,7 @@ | |||||||
|                     "type": "github" |                     "type": "github" | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             "time": "2025-10-01T11:24:54+00:00" |             "time": "2025-10-25T08:35:20+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "league/event", |             "name": "league/event", | ||||||
| @@ -2956,16 +2956,16 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "league/flysystem", |             "name": "league/flysystem", | ||||||
|             "version": "3.30.0", |             "version": "3.30.1", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/thephpleague/flysystem.git", |                 "url": "https://github.com/thephpleague/flysystem.git", | ||||||
|                 "reference": "2203e3151755d874bb2943649dae1eb8533ac93e" |                 "reference": "c139fd65c1f796b926f4aec0df37f6caa959a8da" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2203e3151755d874bb2943649dae1eb8533ac93e", |                 "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/c139fd65c1f796b926f4aec0df37f6caa959a8da", | ||||||
|                 "reference": "2203e3151755d874bb2943649dae1eb8533ac93e", |                 "reference": "c139fd65c1f796b926f4aec0df37f6caa959a8da", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
| @@ -3033,9 +3033,9 @@ | |||||||
|             ], |             ], | ||||||
|             "support": { |             "support": { | ||||||
|                 "issues": "https://github.com/thephpleague/flysystem/issues", |                 "issues": "https://github.com/thephpleague/flysystem/issues", | ||||||
|                 "source": "https://github.com/thephpleague/flysystem/tree/3.30.0" |                 "source": "https://github.com/thephpleague/flysystem/tree/3.30.1" | ||||||
|             }, |             }, | ||||||
|             "time": "2025-06-25T13:29:59+00:00" |             "time": "2025-10-20T15:35:26+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "league/flysystem-local", |             "name": "league/flysystem-local", | ||||||
| @@ -4069,31 +4069,31 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "nunomaduro/termwind", |             "name": "nunomaduro/termwind", | ||||||
|             "version": "v2.3.1", |             "version": "v2.3.2", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/nunomaduro/termwind.git", |                 "url": "https://github.com/nunomaduro/termwind.git", | ||||||
|                 "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" |                 "reference": "eb61920a53057a7debd718a5b89c2178032b52c0" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", |                 "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/eb61920a53057a7debd718a5b89c2178032b52c0", | ||||||
|                 "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", |                 "reference": "eb61920a53057a7debd718a5b89c2178032b52c0", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
|                 "ext-mbstring": "*", |                 "ext-mbstring": "*", | ||||||
|                 "php": "^8.2", |                 "php": "^8.2", | ||||||
|                 "symfony/console": "^7.2.6" |                 "symfony/console": "^7.3.4" | ||||||
|             }, |             }, | ||||||
|             "require-dev": { |             "require-dev": { | ||||||
|                 "illuminate/console": "^11.44.7", |                 "illuminate/console": "^11.46.1", | ||||||
|                 "laravel/pint": "^1.22.0", |                 "laravel/pint": "^1.25.1", | ||||||
|                 "mockery/mockery": "^1.6.12", |                 "mockery/mockery": "^1.6.12", | ||||||
|                 "pestphp/pest": "^2.36.0 || ^3.8.2", |                 "pestphp/pest": "^2.36.0 || ^3.8.4", | ||||||
|                 "phpstan/phpstan": "^1.12.25", |                 "phpstan/phpstan": "^1.12.32", | ||||||
|                 "phpstan/phpstan-strict-rules": "^1.6.2", |                 "phpstan/phpstan-strict-rules": "^1.6.2", | ||||||
|                 "symfony/var-dumper": "^7.2.6", |                 "symfony/var-dumper": "^7.3.4", | ||||||
|                 "thecodingmachine/phpstan-strict-rules": "^1.0.0" |                 "thecodingmachine/phpstan-strict-rules": "^1.0.0" | ||||||
|             }, |             }, | ||||||
|             "type": "library", |             "type": "library", | ||||||
| @@ -4136,7 +4136,7 @@ | |||||||
|             ], |             ], | ||||||
|             "support": { |             "support": { | ||||||
|                 "issues": "https://github.com/nunomaduro/termwind/issues", |                 "issues": "https://github.com/nunomaduro/termwind/issues", | ||||||
|                 "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" |                 "source": "https://github.com/nunomaduro/termwind/tree/v2.3.2" | ||||||
|             }, |             }, | ||||||
|             "funding": [ |             "funding": [ | ||||||
|                 { |                 { | ||||||
| @@ -4152,7 +4152,7 @@ | |||||||
|                     "type": "github" |                     "type": "github" | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             "time": "2025-05-08T08:14:37+00:00" |             "time": "2025-10-18T11:10:27+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "nyholm/psr7", |             "name": "nyholm/psr7", | ||||||
| @@ -10052,28 +10052,28 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "webmozart/assert", |             "name": "webmozart/assert", | ||||||
|             "version": "1.11.0", |             "version": "1.12.0", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/webmozarts/assert.git", |                 "url": "https://github.com/webmozarts/assert.git", | ||||||
|                 "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" |                 "reference": "541057574806f942c94662b817a50f63f7345360" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", |                 "url": "https://api.github.com/repos/webmozarts/assert/zipball/541057574806f942c94662b817a50f63f7345360", | ||||||
|                 "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", |                 "reference": "541057574806f942c94662b817a50f63f7345360", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
|                 "ext-ctype": "*", |                 "ext-ctype": "*", | ||||||
|  |                 "ext-date": "*", | ||||||
|  |                 "ext-filter": "*", | ||||||
|                 "php": "^7.2 || ^8.0" |                 "php": "^7.2 || ^8.0" | ||||||
|             }, |             }, | ||||||
|             "conflict": { |             "suggest": { | ||||||
|                 "phpstan/phpstan": "<0.12.20", |                 "ext-intl": "", | ||||||
|                 "vimeo/psalm": "<4.6.1 || 4.6.2" |                 "ext-simplexml": "", | ||||||
|             }, |                 "ext-spl": "" | ||||||
|             "require-dev": { |  | ||||||
|                 "phpunit/phpunit": "^8.5.13" |  | ||||||
|             }, |             }, | ||||||
|             "type": "library", |             "type": "library", | ||||||
|             "extra": { |             "extra": { | ||||||
| @@ -10104,9 +10104,9 @@ | |||||||
|             ], |             ], | ||||||
|             "support": { |             "support": { | ||||||
|                 "issues": "https://github.com/webmozarts/assert/issues", |                 "issues": "https://github.com/webmozarts/assert/issues", | ||||||
|                 "source": "https://github.com/webmozarts/assert/tree/1.11.0" |                 "source": "https://github.com/webmozarts/assert/tree/1.12.0" | ||||||
|             }, |             }, | ||||||
|             "time": "2022-06-03T18:03:27+00:00" |             "time": "2025-10-20T12:43:39+00:00" | ||||||
|         } |         } | ||||||
|     ], |     ], | ||||||
|     "packages-dev": [ |     "packages-dev": [ | ||||||
| @@ -10549,16 +10549,16 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "driftingly/rector-laravel", |             "name": "driftingly/rector-laravel", | ||||||
|             "version": "2.0.7", |             "version": "2.1.1", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/driftingly/rector-laravel.git", |                 "url": "https://github.com/driftingly/rector-laravel.git", | ||||||
|                 "reference": "625dc02cee08d47ecf0ac86de2f02a55026cf34e" |                 "reference": "abc336cbf06f53d90ab74cecfd319379fc55d408" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/625dc02cee08d47ecf0ac86de2f02a55026cf34e", |                 "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/abc336cbf06f53d90ab74cecfd319379fc55d408", | ||||||
|                 "reference": "625dc02cee08d47ecf0ac86de2f02a55026cf34e", |                 "reference": "abc336cbf06f53d90ab74cecfd319379fc55d408", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
| @@ -10578,9 +10578,9 @@ | |||||||
|             "description": "Rector upgrades rules for Laravel Framework", |             "description": "Rector upgrades rules for Laravel Framework", | ||||||
|             "support": { |             "support": { | ||||||
|                 "issues": "https://github.com/driftingly/rector-laravel/issues", |                 "issues": "https://github.com/driftingly/rector-laravel/issues", | ||||||
|                 "source": "https://github.com/driftingly/rector-laravel/tree/2.0.7" |                 "source": "https://github.com/driftingly/rector-laravel/tree/2.1.1" | ||||||
|             }, |             }, | ||||||
|             "time": "2025-08-19T20:49:47+00:00" |             "time": "2025-10-23T13:53:44+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "fakerphp/faker", |             "name": "fakerphp/faker", | ||||||
| @@ -11036,16 +11036,16 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "nikic/php-parser", |             "name": "nikic/php-parser", | ||||||
|             "version": "v5.6.1", |             "version": "v5.6.2", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/nikic/PHP-Parser.git", |                 "url": "https://github.com/nikic/PHP-Parser.git", | ||||||
|                 "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" |                 "reference": "3a454ca033b9e06b63282ce19562e892747449bb" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", |                 "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", | ||||||
|                 "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", |                 "reference": "3a454ca033b9e06b63282ce19562e892747449bb", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
| @@ -11088,9 +11088,9 @@ | |||||||
|             ], |             ], | ||||||
|             "support": { |             "support": { | ||||||
|                 "issues": "https://github.com/nikic/PHP-Parser/issues", |                 "issues": "https://github.com/nikic/PHP-Parser/issues", | ||||||
|                 "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" |                 "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" | ||||||
|             }, |             }, | ||||||
|             "time": "2025-08-13T20:13:15+00:00" |             "time": "2025-10-21T19:32:17+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "phar-io/manifest", |             "name": "phar-io/manifest", | ||||||
| @@ -11333,11 +11333,11 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "phpstan/phpstan", |             "name": "phpstan/phpstan", | ||||||
|             "version": "2.1.30", |             "version": "2.1.31", | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a4a7f159927983dd4f7c8020ed227d80b7f39d7d", |                 "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", | ||||||
|                 "reference": "a4a7f159927983dd4f7c8020ed227d80b7f39d7d", |                 "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
| @@ -11382,7 +11382,7 @@ | |||||||
|                     "type": "github" |                     "type": "github" | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             "time": "2025-10-02T16:07:52+00:00" |             "time": "2025-10-10T14:14:11+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "phpstan/phpstan-deprecation-rules", |             "name": "phpstan/phpstan-deprecation-rules", | ||||||
| @@ -11815,16 +11815,16 @@ | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "phpunit/phpunit", |             "name": "phpunit/phpunit", | ||||||
|             "version": "12.4.0", |             "version": "12.4.1", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/sebastianbergmann/phpunit.git", |                 "url": "https://github.com/sebastianbergmann/phpunit.git", | ||||||
|                 "reference": "f62aab5794e36ccd26860db2d1bbf89ac19028d9" |                 "reference": "fc5413a2e6d240d2f6d9317bdf7f0a24e73de194" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f62aab5794e36ccd26860db2d1bbf89ac19028d9", |                 "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc5413a2e6d240d2f6d9317bdf7f0a24e73de194", | ||||||
|                 "reference": "f62aab5794e36ccd26860db2d1bbf89ac19028d9", |                 "reference": "fc5413a2e6d240d2f6d9317bdf7f0a24e73de194", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
| @@ -11892,7 +11892,7 @@ | |||||||
|             "support": { |             "support": { | ||||||
|                 "issues": "https://github.com/sebastianbergmann/phpunit/issues", |                 "issues": "https://github.com/sebastianbergmann/phpunit/issues", | ||||||
|                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy", |                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy", | ||||||
|                 "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.0" |                 "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.1" | ||||||
|             }, |             }, | ||||||
|             "funding": [ |             "funding": [ | ||||||
|                 { |                 { | ||||||
| @@ -11916,20 +11916,20 @@ | |||||||
|                     "type": "tidelift" |                     "type": "tidelift" | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             "time": "2025-10-03T04:28:03+00:00" |             "time": "2025-10-09T14:08:29+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "rector/rector", |             "name": "rector/rector", | ||||||
|             "version": "2.2.1", |             "version": "2.2.5", | ||||||
|             "source": { |             "source": { | ||||||
|                 "type": "git", |                 "type": "git", | ||||||
|                 "url": "https://github.com/rectorphp/rector.git", |                 "url": "https://github.com/rectorphp/rector.git", | ||||||
|                 "reference": "e1aaf3061e9ae9342ed0824865e3a3360defddeb" |                 "reference": "fb9418af7777dfb1c87a536dc58398b5b07c74b9" | ||||||
|             }, |             }, | ||||||
|             "dist": { |             "dist": { | ||||||
|                 "type": "zip", |                 "type": "zip", | ||||||
|                 "url": "https://api.github.com/repos/rectorphp/rector/zipball/e1aaf3061e9ae9342ed0824865e3a3360defddeb", |                 "url": "https://api.github.com/repos/rectorphp/rector/zipball/fb9418af7777dfb1c87a536dc58398b5b07c74b9", | ||||||
|                 "reference": "e1aaf3061e9ae9342ed0824865e3a3360defddeb", |                 "reference": "fb9418af7777dfb1c87a536dc58398b5b07c74b9", | ||||||
|                 "shasum": "" |                 "shasum": "" | ||||||
|             }, |             }, | ||||||
|             "require": { |             "require": { | ||||||
| @@ -11968,7 +11968,7 @@ | |||||||
|             ], |             ], | ||||||
|             "support": { |             "support": { | ||||||
|                 "issues": "https://github.com/rectorphp/rector/issues", |                 "issues": "https://github.com/rectorphp/rector/issues", | ||||||
|                 "source": "https://github.com/rectorphp/rector/tree/2.2.1" |                 "source": "https://github.com/rectorphp/rector/tree/2.2.5" | ||||||
|             }, |             }, | ||||||
|             "funding": [ |             "funding": [ | ||||||
|                 { |                 { | ||||||
| @@ -11976,7 +11976,7 @@ | |||||||
|                     "type": "github" |                     "type": "github" | ||||||
|                 } |                 } | ||||||
|             ], |             ], | ||||||
|             "time": "2025-10-06T21:25:14+00:00" |             "time": "2025-10-23T11:22:37+00:00" | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|             "name": "sebastian/cli-parser", |             "name": "sebastian/cli-parser", | ||||||
|   | |||||||
| @@ -78,8 +78,8 @@ return [ | |||||||
|         'running_balance_column' => env('USE_RUNNING_BALANCE', false), |         'running_balance_column' => env('USE_RUNNING_BALANCE', false), | ||||||
|         // see cer.php for exchange rates feature flag.
 |         // see cer.php for exchange rates feature flag.
 | ||||||
|     ], |     ], | ||||||
|     'version'                      => 'develop/2025-10-07', |     'version'                      => 'develop/2025-10-26', | ||||||
|     'build_time'                   => 1759823774, |     'build_time'                   => 1761469088, | ||||||
|     'api_version'                  => '2.1.0', // field is no longer used.
 |     'api_version'                  => '2.1.0', // field is no longer used.
 | ||||||
|     'db_version'                   => 28, // field is no longer used.
 |     'db_version'                   => 28, // field is no longer used.
 | ||||||
| 
 | 
 | ||||||
| @@ -182,6 +182,7 @@ return [ | |||||||
|     'darkMode'                     => 'browser', |     'darkMode'                     => 'browser', | ||||||
|     'list_length'                  => 10, // to be removed if v1 is cancelled.
 |     'list_length'                  => 10, // to be removed if v1 is cancelled.
 | ||||||
|     'default_preferences'          => [ |     'default_preferences'          => [ | ||||||
|  |         'anonymous'          => false, | ||||||
|         'frontpageAccounts'  => [], |         'frontpageAccounts'  => [], | ||||||
|         'listPageSize'       => 50, |         'listPageSize'       => 50, | ||||||
|         'currencyPreference' => 'EUR', |         'currencyPreference' => 'EUR', | ||||||
| @@ -225,6 +226,8 @@ return [ | |||||||
|         // plain files
 |         // plain files
 | ||||||
|         'text/plain', |         'text/plain', | ||||||
|         'text/html', |         'text/html', | ||||||
|  |         'text/xml', | ||||||
|  |         'application/xml', | ||||||
| 
 | 
 | ||||||
|         // images
 |         // images
 | ||||||
|         'image/jpeg', |         'image/jpeg', | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								database/factories/AccountFactory.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								database/factories/AccountFactory.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | <?php | ||||||
|  | declare(strict_types=1); | ||||||
|  | 
 | ||||||
|  | namespace Database\Factories; | ||||||
|  | 
 | ||||||
|  | use FireflyIII\Enums\AccountTypeEnum; | ||||||
|  | use FireflyIII\Models\AccountType; | ||||||
|  | use Illuminate\Database\Eloquent\Factories\Factory; | ||||||
|  | 
 | ||||||
|  | class AccountFactory extends Factory | ||||||
|  | { | ||||||
|  |     public function definition(): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             'name' => $this->faker->name(), | ||||||
|  |             'active' => true, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function withType(AccountTypeEnum $type): static | ||||||
|  |     { | ||||||
|  |         return $this->for(AccountType::where('type', $type->value)->first()); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -11,34 +11,36 @@ return new class extends Migration | |||||||
|      */ |      */ | ||||||
|     public function up(): void |     public function up(): void | ||||||
|     { |     { | ||||||
|         Schema::create('period_statistics', function (Blueprint $table) { |         if (!Schema::hasTable('period_statistics')) { | ||||||
|             $table->id(); |             Schema::create('period_statistics', function (Blueprint $table) { | ||||||
|             $table->timestamps(); |                 $table->id(); | ||||||
|  |                 $table->timestamps(); | ||||||
| 
 | 
 | ||||||
|             // reference to user group id.
 |                 // reference to user group id.
 | ||||||
|             $table->bigInteger('user_group_id', false, true); |                 $table->bigInteger('user_group_id', false, true); | ||||||
| 
 | 
 | ||||||
|             $table->integer('primary_statable_id', false, true)->nullable(); |                 $table->integer('primary_statable_id', false, true)->nullable(); | ||||||
|             $table->string('primary_statable_type', 255)->nullable(); |                 $table->string('primary_statable_type', 255)->nullable(); | ||||||
| 
 | 
 | ||||||
|             $table->integer('secondary_statable_id', false, true)->nullable(); |                 $table->integer('secondary_statable_id', false, true)->nullable(); | ||||||
|             $table->string('secondary_statable_type', 255)->nullable(); |                 $table->string('secondary_statable_type', 255)->nullable(); | ||||||
| 
 | 
 | ||||||
|             $table->integer('tertiary_statable_id', false, true)->nullable(); |                 $table->integer('tertiary_statable_id', false, true)->nullable(); | ||||||
|             $table->string('tertiary_statable_type', 255)->nullable(); |                 $table->string('tertiary_statable_type', 255)->nullable(); | ||||||
| 
 | 
 | ||||||
|             $table->integer('transaction_currency_id', false, true); |                 $table->integer('transaction_currency_id', false, true); | ||||||
|             $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); |                 $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); | ||||||
| 
 | 
 | ||||||
|             $table->dateTime('start')->nullable(); |                 $table->dateTime('start')->nullable(); | ||||||
|             $table->string('start_tz', 50)->nullable(); |                 $table->string('start_tz', 50)->nullable(); | ||||||
|             $table->dateTime('end')->nullable(); |                 $table->dateTime('end')->nullable(); | ||||||
|             $table->string('end_tz', 50)->nullable(); |                 $table->string('end_tz', 50)->nullable(); | ||||||
|             $table->string('type',255); |                 $table->string('type', 255); | ||||||
|             $table->integer('count', false, true)->default(0); |                 $table->integer('count', false, true)->default(0); | ||||||
|             $table->decimal('amount', 32, 12); |                 $table->decimal('amount', 32, 12); | ||||||
|             $table->foreign('user_group_id')->references('id')->on('user_groups')->onDelete('cascade'); |                 $table->foreign('user_group_id')->references('id')->on('user_groups')->onDelete('cascade'); | ||||||
|         }); |             }); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -53,6 +53,10 @@ class TransactionCurrencySeeder extends Seeder | |||||||
|         $currencies[] = ['code' => 'BRL', 'name' => 'Brazilian real', 'symbol' => 'R$', 'decimal_places' => 2]; |         $currencies[] = ['code' => 'BRL', 'name' => 'Brazilian real', 'symbol' => 'R$', 'decimal_places' => 2]; | ||||||
|         $currencies[] = ['code' => 'CAD', 'name' => 'Canadian dollar', 'symbol' => 'C$', 'decimal_places' => 2]; |         $currencies[] = ['code' => 'CAD', 'name' => 'Canadian dollar', 'symbol' => 'C$', 'decimal_places' => 2]; | ||||||
|         $currencies[] = ['code' => 'MXN', 'name' => 'Mexican peso', 'symbol' => 'MX$', 'decimal_places' => 2]; |         $currencies[] = ['code' => 'MXN', 'name' => 'Mexican peso', 'symbol' => 'MX$', 'decimal_places' => 2]; | ||||||
|  |         $currencies[] = ['code' => 'PEN', 'name' => 'Peruvian Sol', 'symbol' => 'S/', 'decimal_places' => 2]; | ||||||
|  |         $currencies[] = ['code' => 'ARS', 'name' => 'Argentinian Peso', 'symbol' => '$', 'decimal_places' => 2]; | ||||||
|  |         $currencies[] = ['code' => 'COP', 'name' => 'Colombian Peso', 'symbol' => '$', 'decimal_places' => 2]; | ||||||
|  |         $currencies[] = ['code' => 'CLP', 'name' => 'Chilean Peso', 'symbol' => '$', 'decimal_places' => 2]; | ||||||
| 
 | 
 | ||||||
|         // oceanian currencies
 |         // oceanian currencies
 | ||||||
|         $currencies[] = ['code' => 'IDR', 'name' => 'Indonesian rupiah', 'symbol' => 'Rp', 'decimal_places' => 2]; |         $currencies[] = ['code' => 'IDR', 'name' => 'Indonesian rupiah', 'symbol' => 'Rp', 'decimal_places' => 2]; | ||||||
|   | |||||||
							
								
								
									
										752
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										752
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -42,13 +42,11 @@ function formatLabel(str, maxwidth) { | |||||||
|             if (concat.length > maxwidth) { |             if (concat.length > maxwidth) { | ||||||
|                 sections.push(temp); |                 sections.push(temp); | ||||||
|                 temp = ""; |                 temp = ""; | ||||||
|             } |             } else { | ||||||
|             else { |  | ||||||
|                 if (index === (words.length - 1)) { |                 if (index === (words.length - 1)) { | ||||||
|                     sections.push(concat); |                     sections.push(concat); | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } else { | ||||||
|                 else { |  | ||||||
|                     temp = concat; |                     temp = concat; | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
| @@ -62,8 +60,7 @@ function formatLabel(str, maxwidth) { | |||||||
|  |  | ||||||
|         if (item.length < maxwidth) { |         if (item.length < maxwidth) { | ||||||
|             temp = item; |             temp = item; | ||||||
|         } |         } else { | ||||||
|         else { |  | ||||||
|             sections.push(item); |             sections.push(item); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -98,9 +95,11 @@ var defaultChartOptions = { | |||||||
|             ticks: { |             ticks: { | ||||||
|                 callback: function (tickValue) { |                 callback: function (tickValue) { | ||||||
|                     "use strict"; |                     "use strict"; | ||||||
|  |                     if (anonymous) { | ||||||
|  |                         return accounting.formatMoney(0); | ||||||
|  |                     } | ||||||
|                     // use first symbol or null: |                     // use first symbol or null: | ||||||
|                     return accounting.formatMoney(tickValue); |                     return accounting.formatMoney(tickValue); | ||||||
|  |  | ||||||
|                 }, |                 }, | ||||||
|                 beginAtZero: true |                 beginAtZero: true | ||||||
|             } |             } | ||||||
| @@ -112,8 +111,11 @@ var defaultChartOptions = { | |||||||
|         callbacks: { |         callbacks: { | ||||||
|             label: function (tooltipItem, data) { |             label: function (tooltipItem, data) { | ||||||
|                 "use strict"; |                 "use strict"; | ||||||
|                 return data.datasets[tooltipItem.datasetIndex].label + ': ' + |                 var string = accounting.formatMoney(tooltipItem.yLabel, data.datasets[tooltipItem.datasetIndex].currency_symbol); | ||||||
|                        accounting.formatMoney(tooltipItem.yLabel, data.datasets[tooltipItem.datasetIndex].currency_symbol); |                 if (anonymous) { | ||||||
|  |                     string = accounting.formatMoney(0); | ||||||
|  |                 } | ||||||
|  |                 return data.datasets[tooltipItem.datasetIndex].label + ': ' + string; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -125,7 +127,11 @@ var pieOptionsWithCurrency = { | |||||||
|             label: function (tooltipItem, data) { |             label: function (tooltipItem, data) { | ||||||
|                 "use strict"; |                 "use strict"; | ||||||
|                 var value = data.datasets[0].data[tooltipItem.index]; |                 var value = data.datasets[0].data[tooltipItem.index]; | ||||||
|                 return data.labels[tooltipItem.index] + ': ' + accounting.formatMoney(value, data.datasets[tooltipItem.datasetIndex].currency_symbol[tooltipItem.index]); |                 var string = accounting.formatMoney(value, data.datasets[tooltipItem.datasetIndex].currency_symbol[tooltipItem.index]); | ||||||
|  |                 if (anonymous) { | ||||||
|  |                     string = accounting.formatMoney(0); | ||||||
|  |                 } | ||||||
|  |                 return data.labels[tooltipItem.index] + ': ' + string; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
| @@ -139,7 +145,11 @@ var defaultPieOptions = { | |||||||
|             label: function (tooltipItem, data) { |             label: function (tooltipItem, data) { | ||||||
|                 "use strict"; |                 "use strict"; | ||||||
|                 var value = data.datasets[0].data[tooltipItem.index]; |                 var value = data.datasets[0].data[tooltipItem.index]; | ||||||
|                 return data.labels[tooltipItem.index] + ': ' + accounting.formatMoney(value); |                 var string = accounting.formatMoney(value); | ||||||
|  |                 if (anonymous) { | ||||||
|  |                     string = accounting.formatMoney(0); | ||||||
|  |                 } | ||||||
|  |                 return data.labels[tooltipItem.index] + ': ' + string; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
| @@ -153,7 +163,11 @@ var neutralDefaultPieOptions = { | |||||||
|             label: function (tooltipItem, data) { |             label: function (tooltipItem, data) { | ||||||
|                 "use strict"; |                 "use strict"; | ||||||
|                 var value = data.datasets[0].data[tooltipItem.index]; |                 var value = data.datasets[0].data[tooltipItem.index]; | ||||||
|                 return data.labels[tooltipItem.index] + ': ' + accounting.formatMoney(value, '¤'); |                 var string = accounting.formatMoney(value, '¤'); | ||||||
|  |                 if(anonymous) { | ||||||
|  |                     string = accounting.formatMoney(0); | ||||||
|  |                 } | ||||||
|  |                 return data.labels[tooltipItem.index] + ': ' + string; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -47,7 +47,6 @@ function parseToLocalDates() { | |||||||
| $(function () { | $(function () { | ||||||
|     "use strict"; |     "use strict"; | ||||||
|  |  | ||||||
|  |  | ||||||
|     configAccounting(currencySymbol); |     configAccounting(currencySymbol); | ||||||
|  |  | ||||||
|     // on submit of logout button: |     // on submit of logout button: | ||||||
|   | |||||||
| @@ -17,13 +17,42 @@ | |||||||
|  * You should have received a copy of the GNU Affero General Public License |  * 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/>. |  * along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  */ |  */ | ||||||
| /** global: token, helpPageTitle */ | /** global: token, helpPageTitle, anonymous */ | ||||||
| $(function () { | $(function () { | ||||||
|     "use strict"; |     "use strict"; | ||||||
|     $('#help').click(showHelp); |     $('#help').click(showHelp); | ||||||
|  |     $('#anonymous').click(changeAnonymity) | ||||||
|  |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | function submitAnonymity(value) { | ||||||
|  |     $.ajax({ | ||||||
|  |         url: 'api/v1/preferences/anonymous', | ||||||
|  |         data: JSON.stringify({data: value}), | ||||||
|  |         type: 'PUT', | ||||||
|  |         headers: { | ||||||
|  |             'Content-Type': 'application/json', | ||||||
|  |             'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'), | ||||||
|  |         }, | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function changeAnonymity(e) { | ||||||
|  |     if (anonymous) { | ||||||
|  |         console.log('Will DISABLE.'); | ||||||
|  |         submitAnonymity(false); | ||||||
|  |         alert(anonymous_warning_off_txt); | ||||||
|  |         window.location.reload(true); | ||||||
|  |     } | ||||||
|  |     if (!anonymous) { | ||||||
|  |         console.log('Will ENABLE.'); | ||||||
|  |         submitAnonymity(true); | ||||||
|  |         alert(anonymous_warning_on_txt); | ||||||
|  |         window.location.reload(true); | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
| function showHelp(e) { | function showHelp(e) { | ||||||
|     "use strict"; |     "use strict"; | ||||||
|     var target = $(e.target); |     var target = $(e.target); | ||||||
|   | |||||||
| @@ -44,20 +44,29 @@ | |||||||
|                     <div class="box-body no-padding"> |                     <div class="box-body no-padding"> | ||||||
|                         <nav v-if="totalPages > 1"> |                         <nav v-if="totalPages > 1"> | ||||||
|                             <ul class="pagination"> |                             <ul class="pagination"> | ||||||
|                                 <li v-if="1 === this.page" class="page-item disabled" aria-disabled="true" :aria-label="$t('pagination.previous')"> |                                 <li v-if="1 === this.page" class="page-item disabled" aria-disabled="true" | ||||||
|  |                                     :aria-label="$t('pagination.previous')"> | ||||||
|                                     <span class="page-link" aria-hidden="true">‹</span> |                                     <span class="page-link" aria-hidden="true">‹</span> | ||||||
|                                 </li> |                                 </li> | ||||||
|                                 <li class="page-item" v-if="1 !== this.page"> |                                 <li class="page-item" v-if="1 !== this.page"> | ||||||
|                                     <a class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page-1)" rel="prev" :aria-label="$t('pagination.next')">‹</a> |                                     <a class="page-link" | ||||||
|  |                                        :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page-1)" | ||||||
|  |                                        rel="prev" :aria-label="$t('pagination.next')">‹</a> | ||||||
|                                 </li> |                                 </li> | ||||||
|                                 <li v-for="item in this.totalPages" :class="item === page ? 'page-item active' : 'page-item'" aria-current="page"> |                                 <li v-for="item in this.totalPages" | ||||||
|  |                                     :class="item === page ? 'page-item active' : 'page-item'" aria-current="page"> | ||||||
|                                     <span v-if="item === page" class="page-link" v-text="item"></span> |                                     <span v-if="item === page" class="page-link" v-text="item"></span> | ||||||
|                                     <a v-if="item !== page" class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + item" v-text="item"></a> |                                     <a v-if="item !== page" class="page-link" | ||||||
|  |                                        :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + item" | ||||||
|  |                                        v-text="item"></a> | ||||||
|                                 </li> |                                 </li> | ||||||
|                                 <li v-if="totalPages !== page" class="page-item"> |                                 <li v-if="totalPages !== page" class="page-item"> | ||||||
|                                     <a class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page+1)" rel="next" :aria-label="$t('pagination.next')">›</a> |                                     <a class="page-link" | ||||||
|  |                                        :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page+1)" | ||||||
|  |                                        rel="next" :aria-label="$t('pagination.next')">›</a> | ||||||
|                                 </li> |                                 </li> | ||||||
|                                 <li v-if="totalPages === page" class="page-item disabled" aria-disabled="true" :aria-label="$t('pagination.next')"> |                                 <li v-if="totalPages === page" class="page-item disabled" aria-disabled="true" | ||||||
|  |                                     :aria-label="$t('pagination.next')"> | ||||||
|                                     <span class="page-link" aria-hidden="true">›</span> |                                     <span class="page-link" aria-hidden="true">›</span> | ||||||
|                                 </li> |                                 </li> | ||||||
|                             </ul> |                             </ul> | ||||||
| @@ -97,9 +106,11 @@ | |||||||
|                                     > |                                     > | ||||||
|                                 </td> |                                 </td> | ||||||
|                                 <td> |                                 <td> | ||||||
|  |                                     <!-- (<span v-text="rate.rate_id"></span>) --> | ||||||
|                                     <input type="number" class="form-control" min="0" step="any" v-model="rate.rate"> |                                     <input type="number" class="form-control" min="0" step="any" v-model="rate.rate"> | ||||||
|                                 </td> |                                 </td> | ||||||
|                                 <td> |                                 <td> | ||||||
|  |                                     <!-- (<span v-text="rate.inverse_id"></span>) --> | ||||||
|                                     <input type="number" class="form-control" min="0" step="any" v-model="rate.inverse"> |                                     <input type="number" class="form-control" min="0" step="any" v-model="rate.inverse"> | ||||||
|                                 </td> |                                 </td> | ||||||
|                                 <td> |                                 <td> | ||||||
| @@ -122,20 +133,29 @@ | |||||||
|  |  | ||||||
|                         <nav v-if="totalPages > 1"> |                         <nav v-if="totalPages > 1"> | ||||||
|                             <ul class="pagination"> |                             <ul class="pagination"> | ||||||
|                                 <li v-if="1 === this.page" class="page-item disabled" aria-disabled="true" :aria-label="$t('pagination.previous')"> |                                 <li v-if="1 === this.page" class="page-item disabled" aria-disabled="true" | ||||||
|  |                                     :aria-label="$t('pagination.previous')"> | ||||||
|                                     <span class="page-link" aria-hidden="true">‹</span> |                                     <span class="page-link" aria-hidden="true">‹</span> | ||||||
|                                 </li> |                                 </li> | ||||||
|                                 <li class="page-item" v-if="1 !== this.page"> |                                 <li class="page-item" v-if="1 !== this.page"> | ||||||
|                                     <a class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page-1)" rel="prev" :aria-label="$t('pagination.next')">‹</a> |                                     <a class="page-link" | ||||||
|  |                                        :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page-1)" | ||||||
|  |                                        rel="prev" :aria-label="$t('pagination.next')">‹</a> | ||||||
|                                 </li> |                                 </li> | ||||||
|                                 <li v-for="item in this.totalPages" :class="item === page ? 'page-item active' : 'page-item'" aria-current="page"> |                                 <li v-for="item in this.totalPages" | ||||||
|  |                                     :class="item === page ? 'page-item active' : 'page-item'" aria-current="page"> | ||||||
|                                     <span v-if="item === page" class="page-link" v-text="item"></span> |                                     <span v-if="item === page" class="page-link" v-text="item"></span> | ||||||
|                                     <a v-if="item !== page" class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + item" v-text="item"></a> |                                     <a v-if="item !== page" class="page-link" | ||||||
|  |                                        :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + item" | ||||||
|  |                                        v-text="item"></a> | ||||||
|                                 </li> |                                 </li> | ||||||
|                                 <li v-if="totalPages !== page" class="page-item"> |                                 <li v-if="totalPages !== page" class="page-item"> | ||||||
|                                     <a class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page+1)" rel="next" :aria-label="$t('pagination.next')">›</a> |                                     <a class="page-link" | ||||||
|  |                                        :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page+1)" | ||||||
|  |                                        rel="next" :aria-label="$t('pagination.next')">›</a> | ||||||
|                                 </li> |                                 </li> | ||||||
|                                 <li v-if="totalPages === page" class="page-item disabled" aria-disabled="true" :aria-label="$t('pagination.next')"> |                                 <li v-if="totalPages === page" class="page-item disabled" aria-disabled="true" | ||||||
|  |                                     :aria-label="$t('pagination.next')"> | ||||||
|                                     <span class="page-link" aria-hidden="true">›</span> |                                     <span class="page-link" aria-hidden="true">›</span> | ||||||
|                                 </li> |                                 </li> | ||||||
|                             </ul> |                             </ul> | ||||||
| @@ -160,7 +180,8 @@ | |||||||
|                                 <label for="ffInput_date" class="col-sm-4 control-label" |                                 <label for="ffInput_date" class="col-sm-4 control-label" | ||||||
|                                        v-text="$t('form.date')"></label> |                                        v-text="$t('form.date')"></label> | ||||||
|                                 <div class="col-sm-8"> |                                 <div class="col-sm-8"> | ||||||
|                                     <input class="form-control" type="date" name="date" id="ffInput_date" :disabled="posting" |                                     <input class="form-control" type="date" name="date" id="ffInput_date" | ||||||
|  |                                            :disabled="posting" | ||||||
|                                            autocomplete="off" spellcheck="false" v-model="newDate"> |                                            autocomplete="off" spellcheck="false" v-model="newDate"> | ||||||
|                                 </div> |                                 </div> | ||||||
|                             </div> |                             </div> | ||||||
| @@ -168,16 +189,19 @@ | |||||||
|                                 <label for="ffInput_rate" class="col-sm-4 control-label" |                                 <label for="ffInput_rate" class="col-sm-4 control-label" | ||||||
|                                        v-text="$t('form.rate')"></label> |                                        v-text="$t('form.rate')"></label> | ||||||
|                                 <div class="col-sm-8"> |                                 <div class="col-sm-8"> | ||||||
|                                     <input class="form-control" type="number" name="rate" id="ffInput_rate" :disabled="posting" |                                     <input class="form-control" type="number" name="rate" id="ffInput_rate" | ||||||
|  |                                            :disabled="posting" | ||||||
|                                            autocomplete="off" spellcheck="false" v-model="newRate" step="any"> |                                            autocomplete="off" spellcheck="false" v-model="newRate" step="any"> | ||||||
|                                     <p class="help-block" v-text="$t('firefly.help_rate_form', {from: from_code, to: to_code})"> |                                     <p class="help-block" | ||||||
|  |                                        v-text="$t('firefly.help_rate_form', {from: from_code, to: to_code})"> | ||||||
|  |  | ||||||
|                                     </p> |                                     </p> | ||||||
|                                 </div> |                                 </div> | ||||||
|                             </div> |                             </div> | ||||||
|                         </div> |                         </div> | ||||||
|                         <div class="box-footer"> |                         <div class="box-footer"> | ||||||
|                             <button type="submit" class="nodisablebutton btn pull-right btn-success" v-text="$t('firefly.save_new_rate')"></button> |                             <button type="submit" class="nodisablebutton btn pull-right btn-success" | ||||||
|  |                                     v-text="$t('firefly.save_new_rate')"></button> | ||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|  |  | ||||||
| @@ -217,9 +241,9 @@ export default { | |||||||
|     mounted() { |     mounted() { | ||||||
|         // get from and to code from URL |         // get from and to code from URL | ||||||
|         this.newDate = format(new Date, 'yyyy-MM-dd'); |         this.newDate = format(new Date, 'yyyy-MM-dd'); | ||||||
|         let parts = window.location.href.split('/'); |         let parts = window.location.pathname.split('/'); | ||||||
|         this.from_code = parts[parts.length - 2]; |         this.from_code = parts[parts.length - 2].toUpperCase(); | ||||||
|         this.to_code = parts[parts.length - 1]; |         this.to_code = parts[parts.length - 1].toUpperCase(); | ||||||
|  |  | ||||||
|         const params = new Proxy(new URLSearchParams(window.location.search), { |         const params = new Proxy(new URLSearchParams(window.location.search), { | ||||||
|             get: (searchParams, prop) => searchParams.get(prop), |             get: (searchParams, prop) => searchParams.get(prop), | ||||||
| @@ -232,8 +256,8 @@ export default { | |||||||
|         this.downloadRates(this.page); |         this.downloadRates(this.page); | ||||||
|     }, |     }, | ||||||
|     methods: { |     methods: { | ||||||
|         submitRate: function(e) { |         submitRate: function (e) { | ||||||
|             if(e) e.preventDefault(); |             if (e) e.preventDefault(); | ||||||
|             this.posting = true; |             this.posting = true; | ||||||
|  |  | ||||||
|             axios.post("./api/v1/exchange-rates", { |             axios.post("./api/v1/exchange-rates", { | ||||||
| @@ -260,33 +284,74 @@ export default { | |||||||
|             if (0 === parts.length) { |             if (0 === parts.length) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |             console.log('These are the parts', parts); | ||||||
|             if ('' !== this.rates[index].rate) { |             if ('' !== this.rates[index].rate) { | ||||||
|  |                 //console.log('[a] Rate info is', this.rates[index]); | ||||||
|                 this.updating = true; |                 this.updating = true; | ||||||
|                 axios.put("./api/v1/exchange-rates/" + this.rates[index].rate_id, {rate: this.rates[index].rate}) |                 if (0 === parseInt(this.rates[index].rate_id)) { | ||||||
|                     .then(() => { |                     console.log('[a] POST, not PUT.'); | ||||||
|                         this.updating = false; |                     axios.post('./api/v1/exchange-rates', | ||||||
|                     }); |                         { | ||||||
|  |                             from: this.from_code, | ||||||
|  |                             to: this.to_code, | ||||||
|  |                             rate: this.rates[index].rate, | ||||||
|  |                             date: this.rates[index].date_field | ||||||
|  |                         }) | ||||||
|  |                         .then(() => { | ||||||
|  |                             this.updating = false; | ||||||
|  |                         }); | ||||||
|  |                 } | ||||||
|  |                 if (0 !== parseInt(this.rates[index].rate_id)) { | ||||||
|  |                     console.log('[a] PUT, not POST.'); | ||||||
|  |                     axios.put('./api/v1/exchange-rates/' + this.rates[index].rate_id, {rate: this.rates[index].rate}) | ||||||
|  |                         .then(() => { | ||||||
|  |                             this.updating = false; | ||||||
|  |                         }); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             if ('' !== this.rates[index].inverse) { |             if ('' !== this.rates[index].inverse) { | ||||||
|  |                 //console.log('[b] Rate info is', this.rates[index]); | ||||||
|                 this.updating = true; |                 this.updating = true; | ||||||
|                 axios.put("./api/v1/exchange-rates/" + this.rates[index].inverse_id, {rate: this.rates[index].inverse}) |                 if (0 === parseInt(this.rates[index].inverse_id)) { | ||||||
|                     .then(() => { |                     console.log('[b] POST, not PUT.'); | ||||||
|                         this.updating = false; |                     // post, not put | ||||||
|                     }); |                     axios.post('./api/v1/exchange-rates', | ||||||
|  |                         { | ||||||
|  |                             // remember, this is in reverse. | ||||||
|  |                             from: this.to_code, | ||||||
|  |                             to: this.from_code, | ||||||
|  |                             rate: this.rates[index].inverse, | ||||||
|  |                             date: this.rates[index].date_field | ||||||
|  |                         }) | ||||||
|  |                         .then(() => { | ||||||
|  |                             this.updating = false; | ||||||
|  |                         }); | ||||||
|  |                 } | ||||||
|  |                 if (0 !== parseInt(this.rates[index].inverse_id)) { | ||||||
|  |                     console.log('[b] PUT, not POST.'); | ||||||
|  |                     axios.put('./api/v1/exchange-rates/' + this.rates[index].inverse_id, {rate: this.rates[index].inverse}) | ||||||
|  |                         .then(() => { | ||||||
|  |                             this.updating = false; | ||||||
|  |                         }); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         }, |         }, | ||||||
|         deleteRate: function (index) { |         deleteRate: function (index) { | ||||||
|             // console.log(this.rates[index].key); |             // console.log(this.rates[index].key); | ||||||
|             let parts = this.spliceKey(this.rates[index].key); |             let parts = this.spliceKey(this.rates[index].key); | ||||||
|  |  | ||||||
|             if (0 === parts.length) { |             if (0 === parts.length) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             // console.log(parts); |             let rateId = parseInt(this.rates[index].rate_id); | ||||||
|  |             let inverseId = parseInt(this.rates[index].inverse_id); | ||||||
|             // delete A to B |             // delete A to B | ||||||
|             axios.delete("./api/v1/exchange-rates/" + parts.from + '/' + parts.to + '/' + format(parts.date, 'yyyy-MM-dd')); |             if (rateId > 0) { | ||||||
|             // delete B to A. |                 axios.delete('./api/v1/exchange-rates/' + rateId); | ||||||
|             axios.delete("./api/v1/exchange-rates/" + parts.to + '/' + parts.from + '/' + format(parts.date, 'yyyy-MM-dd')); |             } | ||||||
|  |             if (inverseId > 0) { | ||||||
|  |                 axios.delete('./api/v1/exchange-rates/' + inverseId); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             this.rates.splice(index, 1); |             this.rates.splice(index, 1); | ||||||
|         }, |         }, | ||||||
| @@ -307,6 +372,7 @@ export default { | |||||||
|             }; |             }; | ||||||
|         }, |         }, | ||||||
|         downloadCurrencies: function () { |         downloadCurrencies: function () { | ||||||
|  |             console.log('Now downloading currencies.'); | ||||||
|             this.loading = true; |             this.loading = true; | ||||||
|             axios.get("./api/v1/currencies/" + this.from_code).then((response) => { |             axios.get("./api/v1/currencies/" + this.from_code).then((response) => { | ||||||
|                 this.from = { |                 this.from = { | ||||||
| @@ -327,31 +393,36 @@ export default { | |||||||
|         downloadRates: function (page) { |         downloadRates: function (page) { | ||||||
|             this.tempRates = {}; |             this.tempRates = {}; | ||||||
|             this.loading = true; |             this.loading = true; | ||||||
|             axios.get("./api/v1/exchange-rates/" + this.from_code + '/' + this.to_code + '?page=' + page).then((response) => { |             console.log('Now downloading rates.', page); | ||||||
|  |             axios.get('./api/v1/exchange-rates/' + this.from_code + '/' + this.to_code + '?page=' + page).then((response) => { | ||||||
|                 for (let i in response.data.data) { |                 for (let i in response.data.data) { | ||||||
|                     if (response.data.data.hasOwnProperty(i)) { |                     if (response.data.data.hasOwnProperty(i)) { | ||||||
|  |                         console.log('Downloaded entry #' + i); | ||||||
|                         let current = response.data.data[i]; |                         let current = response.data.data[i]; | ||||||
|                         let date = new Date(current.attributes.date); |                         let date = new Date(current.attributes.date); | ||||||
|                         let from_code = current.attributes.from_currency_code; |                         let from_code = current.attributes.from_currency_code.toUpperCase(); | ||||||
|                         let to_code = current.attributes.to_currency_code; |                         let to_code = current.attributes.to_currency_code.toUpperCase(); | ||||||
|                         let rate = current.attributes.rate; |                         let rate = current.attributes.rate; | ||||||
|                         let inverse = ''; |                         let inverse = ''; | ||||||
|                         let rate_id = current.id; |                         let rate_id = current.id; | ||||||
|                         let inverse_id = '0'; |                         let inverse_id = '0'; | ||||||
|                         let key = from_code + '_' + to_code + '_' + format(date, 'yyyy-MM-dd'); |                         let key = from_code + '_' + to_code + '_' + format(date, 'yyyy-MM-dd'); | ||||||
|                         // console.log('Key is now "' + key + '"'); |                         console.log('Key is now "' + key + '"'); | ||||||
|  |  | ||||||
|                         // perhaps the returned rate is actually the inverse rate. |                         // perhaps the returned rate is actually the inverse rate. | ||||||
|                         if (from_code === this.to_code && to_code === this.from_code) { |                         if (from_code === this.to_code && to_code === this.from_code) { | ||||||
|                             // console.log('Inverse rate found!'); |                             // console.log('Inverse rate found!'); | ||||||
|                             key = to_code + '_' + from_code + '_' + format(date, 'yyyy-MM-dd'); |                             key = to_code + '_' + from_code + '_' + format(date, 'yyyy-MM-dd'); | ||||||
|                             rate = ''; |                             rate = ''; | ||||||
|  |                             // new: set rate id to zero. | ||||||
|  |                             rate_id = '0'; | ||||||
|                             inverse = current.attributes.rate; |                             inverse = current.attributes.rate; | ||||||
|                             inverse_id = current.id; |                             inverse_id = current.id; | ||||||
|                             // console.log('Key updated to "' + key + '"'); |                             console.log('Key updated to "' + key + '"'); | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|                         if (!this.tempRates.hasOwnProperty(key)) { |                         if (!this.tempRates.hasOwnProperty(key)) { | ||||||
|  |                             console.log('New entry stored'); | ||||||
|                             this.tempRates[key] = { |                             this.tempRates[key] = { | ||||||
|                                 key: key, |                                 key: key, | ||||||
|                                 date: date, |                                 date: date, | ||||||
| @@ -374,6 +445,7 @@ export default { | |||||||
|                             this.tempRates[key].rate = rate; |                             this.tempRates[key].rate = rate; | ||||||
|                             this.tempRates[key].rate_id = rate_id; |                             this.tempRates[key].rate_id = rate_id; | ||||||
|                         } |                         } | ||||||
|  |                         console.log('Found exchange rate #' + this.tempRates[key].rate_id + ' with inverse #' + this.tempRates[key].inverse_id); | ||||||
|  |  | ||||||
|  |  | ||||||
|                     } |                     } | ||||||
|   | |||||||
| @@ -35,5 +35,5 @@ return [ | |||||||
|     'currencies-index'       => 'Firefly III supports multiple currencies. Although it defaults to the Euro it can be set to the US Dollar and many other currencies. As you can see a small selection of currencies has been included but you can add your own if you wish to. Changing the default currency will not change the currency of existing transactions however: Firefly III supports the use of multiple currencies at the same time.', |     'currencies-index'       => 'Firefly III supports multiple currencies. Although it defaults to the Euro it can be set to the US Dollar and many other currencies. As you can see a small selection of currencies has been included but you can add your own if you wish to. Changing the default currency will not change the currency of existing transactions however: Firefly III supports the use of multiple currencies at the same time.', | ||||||
|     'transactions-index'     => 'These expenses, deposits and transfers are not particularly imaginative. They have been generated automatically.', |     'transactions-index'     => 'These expenses, deposits and transfers are not particularly imaginative. They have been generated automatically.', | ||||||
|     'piggy-banks-index'      => 'As you can see, there are three piggy banks. Use the plus and minus buttons to influence the amount of money in each piggy bank. Click the name of the piggy bank to see the administration for each piggy bank.', |     'piggy-banks-index'      => 'As you can see, there are three piggy banks. Use the plus and minus buttons to influence the amount of money in each piggy bank. Click the name of the piggy bank to see the administration for each piggy bank.', | ||||||
|     'profile-index'          => 'Keep in mind that the demo site resets every four hours. Your access may be revoked at any time. This happens automatically and is not a bug.', |     'profile-index'          => 'Keep in mind that the demo site resets every four hours. Standard user agents (curl, Postman, wget) are blocked. Your access may be revoked at any time. This happens automatically and is not a bug.', | ||||||
| ]; | ]; | ||||||
|   | |||||||
| @@ -235,6 +235,8 @@ return [ | |||||||
|     'advanced_options_explain'                  => 'Some pages in Firefly III have advanced options hidden behind this button. This page doesn\'t have anything fancy here, but do check out the others!', |     'advanced_options_explain'                  => 'Some pages in Firefly III have advanced options hidden behind this button. This page doesn\'t have anything fancy here, but do check out the others!', | ||||||
|     'here_be_dragons'                           => 'Hic sunt dracones', |     'here_be_dragons'                           => 'Hic sunt dracones', | ||||||
|     'bad_date_transaction'                      => 'Firefly III has detected you have transactions from before the year 1970. Please correct these transactions at your earliest convenience.', |     'bad_date_transaction'                      => 'Firefly III has detected you have transactions from before the year 1970. Please correct these transactions at your earliest convenience.', | ||||||
|  |     'anonymous_warning_on'                      => 'For your privacy, all amounts are now displayed as "zero". Warning: text input boxes may still show the original amounts!', | ||||||
|  |     'anonymous_warning_off'                     => 'Amounts will be visible again. Please be mindful of your surroundings.', | ||||||
| 
 | 
 | ||||||
|     // Webhooks
 |     // Webhooks
 | ||||||
|     'webhooks'                                  => 'Webhooks', |     'webhooks'                                  => 'Webhooks', | ||||||
|   | |||||||
| @@ -32,6 +32,7 @@ return [ | |||||||
|     'webhook_account_info'           => 'Cannot deliver account information for budget related webhooks.', |     'webhook_account_info'           => 'Cannot deliver account information for budget related webhooks.', | ||||||
|     'webhook_transaction_info'       => 'Cannot deliver transaction information for budget related webhooks.', |     'webhook_transaction_info'       => 'Cannot deliver transaction information for budget related webhooks.', | ||||||
|     'invalid_account_type'           => 'A piggy bank can only be linked to asset accounts and liabilities', |     'invalid_account_type'           => 'A piggy bank can only be linked to asset accounts and liabilities', | ||||||
|  |     'unique_currency_code' => 'This currency code is already in use', | ||||||
|     'invalid_account_currency'       => 'This account does not use the currency you have selected', |     'invalid_account_currency'       => 'This account does not use the currency you have selected', | ||||||
|     'current_amount_too_much'        => 'The combined amount in "current_amount" cannot exceed the "target_amount".', |     'current_amount_too_much'        => 'The combined amount in "current_amount" cannot exceed the "target_amount".', | ||||||
|     'filter_must_be_in'              => 'Filter ":filter" must be one of: :values', |     'filter_must_be_in'              => 'Filter ":filter" must be one of: :values', | ||||||
|   | |||||||
| @@ -90,15 +90,15 @@ | |||||||
|         var iAmOwed = '{{ 'i_am_owed_amount'|_|escape('js') }}'; |         var iAmOwed = '{{ 'i_am_owed_amount'|_|escape('js') }}'; | ||||||
|         var iOwe = '{{ 'i_owe_amount'|_|escape('js') }}'; |         var iOwe = '{{ 'i_owe_amount'|_|escape('js') }}'; | ||||||
|     </script> |     </script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/modernizr-custom.js?v={{ FF_VERSION }}" |     <script type="text/javascript" src="v1/js/lib/modernizr-custom.js?v={{ FF_BUILD_TIME }}" | ||||||
|             nonce="{{ JS_NONCE }}"></script> |             nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/jquery-ui.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/accounts/create.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/accounts/create.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block styles %} | {% block styles %} | ||||||
|     <link href="v1/css/jquery-ui/jquery-ui.structure.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" |     <link href="v1/css/jquery-ui/jquery-ui.structure.min.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" | ||||||
|           media="all" nonce="{{ JS_NONCE }}"> |           media="all" nonce="{{ JS_NONCE }}"> | ||||||
|     <link href="v1/css/jquery-ui/jquery-ui.theme.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" |     <link href="v1/css/jquery-ui/jquery-ui.theme.min.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" | ||||||
|           media="all" nonce="{{ JS_NONCE }}"> |           media="all" nonce="{{ JS_NONCE }}"> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -122,15 +122,15 @@ | |||||||
|         var iAmOwed = '{{ 'i_am_owed_amount'|_|escape('js') }}'; |         var iAmOwed = '{{ 'i_am_owed_amount'|_|escape('js') }}'; | ||||||
|         var iOwe = '{{ 'i_owe_amount'|_|escape('js') }}'; |         var iOwe = '{{ 'i_owe_amount'|_|escape('js') }}'; | ||||||
|     </script> |     </script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/modernizr-custom.js?v={{ FF_VERSION }}" |     <script type="text/javascript" src="v1/js/lib/modernizr-custom.js?v={{ FF_BUILD_TIME }}" | ||||||
|             nonce="{{ JS_NONCE }}"></script> |             nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/jquery-ui.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/accounts/edit.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/accounts/edit.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block styles %} | {% block styles %} | ||||||
|     <link href="v1/css/jquery-ui/jquery-ui.structure.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" |     <link href="v1/css/jquery-ui/jquery-ui.structure.min.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" | ||||||
|           media="all" nonce="{{ JS_NONCE }}"> |           media="all" nonce="{{ JS_NONCE }}"> | ||||||
|     <link href="v1/css/jquery-ui/jquery-ui.theme.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" |     <link href="v1/css/jquery-ui/jquery-ui.theme.min.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" | ||||||
|           media="all" nonce="{{ JS_NONCE }}"> |           media="all" nonce="{{ JS_NONCE }}"> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -90,6 +90,6 @@ | |||||||
|     <script type="text/javascript" nonce="{{ JS_NONCE }}"> |     <script type="text/javascript" nonce="{{ JS_NONCE }}"> | ||||||
|         var objectType = '{{ objectType|escape }}'; |         var objectType = '{{ objectType|escape }}'; | ||||||
|     </script> |     </script> | ||||||
|     <script src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> |     <script src="v1/js/lib/jquery-ui.min.js?v={{ FF_BUILD_TIME }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" nonce="{{ JS_NONCE }}" src="v1/js/ff/accounts/index.js?v={{ FF_VERSION }}"></script> |     <script type="text/javascript" nonce="{{ JS_NONCE }}" src="v1/js/ff/accounts/index.js?v={{ FF_BUILD_TIME }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -133,5 +133,5 @@ | |||||||
|         var indexUrl = '{{ indexUrl }}'; |         var indexUrl = '{{ indexUrl }}'; | ||||||
|         var selectRangeAndBalance = '{{ 'select_range_and_balance'|_|escape('js') }}'; |         var selectRangeAndBalance = '{{ 'select_range_and_balance'|_|escape('js') }}'; | ||||||
|     </script> |     </script> | ||||||
|     <script src="v1/js/ff/accounts/reconcile.js?v={{ FF_VERSION }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> |     <script src="v1/js/ff/accounts/reconcile.js?v={{ FF_BUILD_TIME }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -219,25 +219,25 @@ | |||||||
|  |  | ||||||
|     </script> |     </script> | ||||||
|     {% if location %} |     {% if location %} | ||||||
|         <script src="v1/lib/leaflet/leaflet.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |         <script src="v1/lib/leaflet/leaflet.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     {% endif %} |     {% endif %} | ||||||
|     <script type="text/javascript" src="v1/js/lib/Chart.bundle.min.js?v={{ FF_VERSION }}" |     <script type="text/javascript" src="v1/js/lib/Chart.bundle.min.js?v={{ FF_BUILD_TIME }}" | ||||||
|             nonce="{{ JS_NONCE }}"></script> |             nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/chartjs-plugin-annotation.min.js?v={{ FF_VERSION }}" |     <script type="text/javascript" src="v1/js/lib/chartjs-plugin-annotation.min.js?v={{ FF_BUILD_TIME }}" | ||||||
|             nonce="{{ JS_NONCE }}"></script> |             nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/charts.defaults.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/charts.defaults.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/charts.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/charts.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|  |  | ||||||
|     <script src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> |     <script src="v1/js/lib/jquery-ui.min.js?v={{ FF_BUILD_TIME }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script src="v1/js/lib/jquery.color-2.1.2.min.js?v={{ FF_VERSION }}" type="text/javascript" |     <script src="v1/js/lib/jquery.color-2.1.2.min.js?v={{ FF_BUILD_TIME }}" type="text/javascript" | ||||||
|             nonce="{{ JS_NONCE }}"></script> |             nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script src="v1/js/ff/accounts/show.js?v={{ FF_VERSION }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> |     <script src="v1/js/ff/accounts/show.js?v={{ FF_BUILD_TIME }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> | ||||||
|     {# required for groups.twig #} |     {# required for groups.twig #} | ||||||
|     <script type="text/javascript" src="v1/js/ff/list/groups.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/list/groups.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block styles %} | {% block styles %} | ||||||
|     {% if location %} |     {% if location %} | ||||||
|         <link rel="stylesheet" href="v1/lib/leaflet/leaflet.css?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"> |         <link rel="stylesheet" href="v1/lib/leaflet/leaflet.css?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"> | ||||||
|     {% endif %} |     {% endif %} | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -68,10 +68,10 @@ | |||||||
|     </div> |     </div> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     <script type="text/javascript" src="v1/js/lib/bootstrap-sortable.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/bootstrap-sortable.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block styles %} | {% block styles %} | ||||||
|     <link rel="stylesheet" href="v1/css/bootstrap-sortable.css?v={{ FF_VERSION }}" type="text/css" media="all" nonce="{{ JS_NONCE }}"> |     <link rel="stylesheet" href="v1/css/bootstrap-sortable.css?v={{ FF_BUILD_TIME }}" type="text/css" media="all" nonce="{{ JS_NONCE }}"> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -68,17 +68,17 @@ | |||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block styles %} | {% block styles %} | ||||||
|     <link href="v1/css/bootstrap-tagsinput.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all" nonce="{{ JS_NONCE }}"> |     <link href="v1/css/bootstrap-tagsinput.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" media="all" nonce="{{ JS_NONCE }}"> | ||||||
|     <link href="v1/css/jquery-ui/jquery-ui.structure.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all" nonce="{{ JS_NONCE }}"> |     <link href="v1/css/jquery-ui/jquery-ui.structure.min.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" media="all" nonce="{{ JS_NONCE }}"> | ||||||
|     <link href="v1/css/jquery-ui/jquery-ui.theme.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all" nonce="{{ JS_NONCE }}"> |     <link href="v1/css/jquery-ui/jquery-ui.theme.min.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" media="all" nonce="{{ JS_NONCE }}"> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     <script type="text/javascript" src="v1/js/lib/bootstrap-tagsinput.min.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/bootstrap-tagsinput.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/modernizr-custom.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/modernizr-custom.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/jquery-ui.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/bills/create.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/bills/create.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|  |  | ||||||
|     {# auto complete for object groups #} |     {# auto complete for object groups #} | ||||||
|     <script type="text/javascript" src="v1/js/lib/typeahead/typeahead.bundle.min.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/typeahead/typeahead.bundle.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/object-groups/create-edit.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/object-groups/create-edit.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -74,24 +74,24 @@ | |||||||
|  |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block styles %} | {% block styles %} | ||||||
|     <link href="v1/css/bootstrap-tagsinput.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all" |     <link href="v1/css/bootstrap-tagsinput.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" media="all" | ||||||
|           nonce="{{ JS_NONCE }}"> |           nonce="{{ JS_NONCE }}"> | ||||||
|     <link href="v1/css/jquery-ui/jquery-ui.structure.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" |     <link href="v1/css/jquery-ui/jquery-ui.structure.min.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" | ||||||
|           media="all" nonce="{{ JS_NONCE }}"> |           media="all" nonce="{{ JS_NONCE }}"> | ||||||
|     <link href="v1/css/jquery-ui/jquery-ui.theme.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" |     <link href="v1/css/jquery-ui/jquery-ui.theme.min.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" | ||||||
|           media="all" nonce="{{ JS_NONCE }}"> |           media="all" nonce="{{ JS_NONCE }}"> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     <script type="text/javascript" src="v1/js/lib/bootstrap-tagsinput.min.js?v={{ FF_VERSION }}" |     <script type="text/javascript" src="v1/js/lib/bootstrap-tagsinput.min.js?v={{ FF_BUILD_TIME }}" | ||||||
|             nonce="{{ JS_NONCE }}"></script> |             nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/modernizr-custom.js?v={{ FF_VERSION }}" |     <script type="text/javascript" src="v1/js/lib/modernizr-custom.js?v={{ FF_BUILD_TIME }}" | ||||||
|             nonce="{{ JS_NONCE }}"></script> |             nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/jquery-ui.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/accounts/edit.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/accounts/edit.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|  |  | ||||||
|     {# auto complete for object groups #} |     {# auto complete for object groups #} | ||||||
|     <script type="text/javascript" src="v1/js/lib/typeahead/typeahead.bundle.min.js?v={{ FF_VERSION }}" |     <script type="text/javascript" src="v1/js/lib/typeahead/typeahead.bundle.min.js?v={{ FF_BUILD_TIME }}" | ||||||
|             nonce="{{ JS_NONCE }}"></script> |             nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/object-groups/create-edit.js?v={{ FF_VERSION }}" |     <script type="text/javascript" src="v1/js/ff/object-groups/create-edit.js?v={{ FF_BUILD_TIME }}" | ||||||
|             nonce="{{ JS_NONCE }}"></script> |             nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -41,6 +41,6 @@ | |||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     <script src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> |     <script src="v1/js/lib/jquery-ui.min.js?v={{ FF_BUILD_TIME }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/bills/index.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/bills/index.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -188,10 +188,10 @@ | |||||||
|         var billCurrencySymbol = "{{ convertToPrimary ? primaryCurrency.symbol : object.data.currency.symbol }}"; |         var billCurrencySymbol = "{{ convertToPrimary ? primaryCurrency.symbol : object.data.currency.symbol }}"; | ||||||
|         var billUrl = '{{ route('chart.bill.single', [object.data.id]) }}'; |         var billUrl = '{{ route('chart.bill.single', [object.data.id]) }}'; | ||||||
|     </script> |     </script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/Chart.bundle.min.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/Chart.bundle.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/charts.defaults.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/charts.defaults.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/charts.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/charts.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/bills/show.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/bills/show.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     {# required for groups.twig #} |     {# required for groups.twig #} | ||||||
|     <script type="text/javascript" src="v1/js/ff/list/groups.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/list/groups.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -60,5 +60,5 @@ | |||||||
|  |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     <script type="text/javascript" src="v1/js/ff/budgets/create.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/budgets/create.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -61,5 +61,5 @@ | |||||||
|  |  | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     <script type="text/javascript" src="v1/js/ff/budgets/edit.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/budgets/edit.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -267,9 +267,14 @@ | |||||||
|                                             <div class="input-group"> |                                             <div class="input-group"> | ||||||
|                                                 <div class="input-group-addon">{{ primaryCurrency.symbol }}</div> |                                                 <div class="input-group-addon">{{ primaryCurrency.symbol }}</div> | ||||||
|                                                 <input type="hidden" name="balance_currency_id" value="{{ primaryCurrency.id }}"/> |                                                 <input type="hidden" name="balance_currency_id" value="{{ primaryCurrency.id }}"/> | ||||||
|  |                                                 {% if not anonymous %} | ||||||
|                                                 <input class="form-control budget_amount" data-original="0" data-id="{{ budget.id }}" |                                                 <input class="form-control budget_amount" data-original="0" data-id="{{ budget.id }}" | ||||||
|                                                        data-currency="{{ primaryCurrency.id }}" data-limit="0" value="0" autocomplete="off" min="0" name="amount" |                                                        data-currency="{{ primaryCurrency.id }}" data-limit="0" value="0" autocomplete="off" min="0" name="amount" | ||||||
|                                                        type="number"> |                                                        type="number"> | ||||||
|  |                                                 {% endif %} | ||||||
|  |                                                 {% if anonymous %} | ||||||
|  |                                                 --- | ||||||
|  |                                                 {% endif %} | ||||||
|                                             </div> |                                             </div> | ||||||
|                                             <span class="text-danger budget_warning" data-id="{{ budget.id }}" data-budgetLimit="{{ budgetLimit.id }}" |                                             <span class="text-danger budget_warning" data-id="{{ budget.id }}" data-budgetLimit="{{ budgetLimit.id }}" | ||||||
|                                                   style="display:none;"></span> |                                                   style="display:none;"></span> | ||||||
| @@ -283,10 +288,15 @@ | |||||||
|                                                 {% endif %} |                                                 {% endif %} | ||||||
|                                                 <div class="input-group bl_entry" data-budget-limit-id="{{ budgetLimit.id }}"> |                                                 <div class="input-group bl_entry" data-budget-limit-id="{{ budgetLimit.id }}"> | ||||||
|                                                     <div class="input-group-addon">{{ budgetLimit.currency_symbol }}</div> |                                                     <div class="input-group-addon">{{ budgetLimit.currency_symbol }}</div> | ||||||
|  |                                                     {% if not anonymous %} | ||||||
|                                                     <input class="form-control budget_amount" data-original="{{ budgetLimit.amount }}" |                                                     <input class="form-control budget_amount" data-original="{{ budgetLimit.amount }}" | ||||||
|                                                            data-id="{{ budget.id }}" data-limit="{{ budgetLimit.id }}" value="{{ budgetLimit.amount }}" |                                                            data-id="{{ budget.id }}" data-limit="{{ budgetLimit.id }}" value="{{ budgetLimit.amount }}" | ||||||
|                                                            autocomplete="off" |                                                            autocomplete="off" | ||||||
|                                                            min="0" name="amount" type="number"> |                                                            min="0" name="amount" type="number"> | ||||||
|  |                                                     {% endif %} | ||||||
|  |                                                     {% if anonymous %} | ||||||
|  |                                                         --- | ||||||
|  |                                                     {% endif %} | ||||||
|                                                     <div class="input-group-btn"> |                                                     <div class="input-group-btn"> | ||||||
|                                                         <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" |                                                         <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" | ||||||
|                                                                 aria-expanded="false"><span class="caret"></span></button> |                                                                 aria-expanded="false"><span class="caret"></span></button> | ||||||
| @@ -455,11 +465,11 @@ | |||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block styles %} | {% block styles %} | ||||||
|     <link href="v1/css/bootstrap-sortable.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all" nonce="{{ JS_NONCE }}"> |     <link href="v1/css/bootstrap-sortable.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" media="all" nonce="{{ JS_NONCE }}"> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     <script src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> |     <script src="v1/js/lib/jquery-ui.min.js?v={{ FF_BUILD_TIME }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" nonce="{{ JS_NONCE }}"> |     <script type="text/javascript" nonce="{{ JS_NONCE }}"> | ||||||
|  |  | ||||||
|         // index route. |         // index route. | ||||||
| @@ -478,6 +488,6 @@ | |||||||
|         var periodStart = "{{ start.format('Y-m-d') }}"; |         var periodStart = "{{ start.format('Y-m-d') }}"; | ||||||
|         var periodEnd = "{{ end.format('Y-m-d') }}"; |         var periodEnd = "{{ end.format('Y-m-d') }}"; | ||||||
|     </script> |     </script> | ||||||
|     <script type="text/javascript" src="v1/js/lib/bootstrap-sortable.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/bootstrap-sortable.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/budgets/index.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/budgets/index.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -60,5 +60,5 @@ | |||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     {# required for groups.twig #} |     {# required for groups.twig #} | ||||||
|     <script type="text/javascript" src="v1/js/ff/list/groups.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/list/groups.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -230,10 +230,10 @@ | |||||||
|         {% endif %} |         {% endif %} | ||||||
|     </script> |     </script> | ||||||
|  |  | ||||||
|     <script type="text/javascript" src="v1/js/lib/Chart.bundle.min.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/Chart.bundle.min.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/charts.defaults.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/charts.defaults.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/charts.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/charts.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/budgets/show.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/budgets/show.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     {# required for groups.twig #} |     {# required for groups.twig #} | ||||||
|     <script type="text/javascript" src="v1/js/ff/list/groups.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/list/groups.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -53,5 +53,5 @@ | |||||||
|     </form> |     </form> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     <script type="text/javascript" src="v1/js/ff/budgets/create.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/budgets/create.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -59,5 +59,5 @@ | |||||||
|     </form> |     </form> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     <script type="text/javascript" src="v1/js/ff/categories/edit.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/categories/edit.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
| @@ -40,10 +40,10 @@ | |||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block styles %} | {% block styles %} | ||||||
|     <link href="v1/css/bootstrap-sortable.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all" nonce="{{ JS_NONCE }}"> |     <link href="v1/css/bootstrap-sortable.css?v={{ FF_BUILD_TIME }}" type="text/css" rel="stylesheet" media="all" nonce="{{ JS_NONCE }}"> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  |  | ||||||
| {% block scripts %} | {% block scripts %} | ||||||
|     <script type="text/javascript" src="v1/js/lib/bootstrap-sortable.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/lib/bootstrap-sortable.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
|     <script type="text/javascript" src="v1/js/ff/categories/index.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script> |     <script type="text/javascript" src="v1/js/ff/categories/index.js?v={{ FF_BUILD_TIME }}" nonce="{{ JS_NONCE }}"></script> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user