diff --git a/app/Factory/PiggyBankFactory.php b/app/Factory/PiggyBankFactory.php index 068cd0841a..5c308c1d08 100644 --- a/app/Factory/PiggyBankFactory.php +++ b/app/Factory/PiggyBankFactory.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Factory; +use FireflyIII\Events\Model\PiggyBank\ChangedAmount; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\TransactionCurrency; @@ -237,20 +238,46 @@ class PiggyBankFactory } } - /** @var array $info */ foreach ($accounts as $info) { $account = $this->accountRepository->find((int) ($info['account_id'] ?? 0)); if (null === $account) { + Log::debug(sprintf('Account #%d not found, skipping.', (int) ($info['account_id'] ?? 0))); + continue; } if (array_key_exists('current_amount', $info) && null !== $info['current_amount']) { + // an amount is set, first check out if there is a difference with the previous amount. + $previous = $toBeLinked[$account->id]['current_amount'] ?? '0'; + $diff = bcsub($info['current_amount'], $previous); + + // create event for difference. + if (0 !== bccomp($diff, '0')) { + Log::debug(sprintf('[a] Will save event for difference %s (previous value was %s)', $diff, $previous)); + event(new ChangedAmount($piggyBank, $diff, null, null)); + } + $toBeLinked[$account->id] = ['current_amount' => $info['current_amount']]; Log::debug(sprintf('[a] Will link account #%d with amount %s', $account->id, $info['current_amount'])); } if (array_key_exists('current_amount', $info) && null === $info['current_amount']) { + // an amount is set, first check out if there is a difference with the previous amount. + $previous = $toBeLinked[$account->id]['current_amount'] ?? '0'; + $diff = bcsub('0', $previous); + + // create event for difference. + if (0 !== bccomp($diff, '0')) { + Log::debug(sprintf('[b] Will save event for difference %s (previous value was %s)', $diff, $previous)); + event(new ChangedAmount($piggyBank, $diff, null, null)); + } + + // no amount set, use previous amount or go to ZERO. $toBeLinked[$account->id] = ['current_amount' => $toBeLinked[$account->id]['current_amount'] ?? '0']; Log::debug(sprintf('[b] Will link account #%d with amount %s', $account->id, $toBeLinked[$account->id]['current_amount'] ?? '0')); + + // create event: + Log::debug('linkToAccountIds: Trigger change for positive amount [b].'); + event(new ChangedAmount($piggyBank, $toBeLinked[$account->id]['current_amount'], null, null)); } if (!array_key_exists('current_amount', $info)) { $toBeLinked[$account->id] ??= []; @@ -258,6 +285,11 @@ class PiggyBankFactory } } Log::debug(sprintf('Link information: %s', json_encode($toBeLinked))); - $piggyBank->accounts()->sync($toBeLinked); + if (0 !== count($toBeLinked)) { + $piggyBank->accounts()->sync($toBeLinked); + } + if (0 === count($toBeLinked)) { + Log::warning('No accounts to link to piggy bank, will not change whatever is there now.'); + } } } diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index e0b54071b3..80c803cc68 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -82,13 +82,16 @@ class AccountController extends Controller */ public function expenseAccounts(): JsonResponse { - Log::debug('RevenueAccounts'); + Log::debug('ExpenseAccounts'); /** @var Carbon $start */ $start = clone session('start', today(config('app.timezone'))->startOfMonth()); /** @var Carbon $end */ $end = clone session('end', today(config('app.timezone'))->endOfMonth()); + $start->startOfDay(); + $end->endOfDay(); + $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); @@ -97,7 +100,6 @@ class AccountController extends Controller if ($cache->has()) { return response()->json($cache->get()); } - $start->subDay(); // prep some vars: $currencies = []; @@ -557,6 +559,10 @@ class AccountController extends Controller /** @var Carbon $end */ $end = clone session('end', today(config('app.timezone'))->endOfMonth()); + + $start->startOfDay(); + $end->endOfDay(); + $cache = new CacheProperties(); $cache->addProperty($start); $cache->addProperty($end); @@ -565,7 +571,6 @@ class AccountController extends Controller if ($cache->has()) { return response()->json($cache->get()); } - $start->subDay(); // prep some vars: $currencies = []; diff --git a/app/Http/Controllers/PiggyBank/EditController.php b/app/Http/Controllers/PiggyBank/EditController.php index b46fea3ff0..b7b29683ae 100644 --- a/app/Http/Controllers/PiggyBank/EditController.php +++ b/app/Http/Controllers/PiggyBank/EditController.php @@ -78,13 +78,14 @@ class EditController extends Controller $startDate = $piggyBank->start_date?->format('Y-m-d'); $preFilled = [ - 'name' => $piggyBank->name, - 'target_amount' => app('steam')->bcround($piggyBank->target_amount, $piggyBank->transactionCurrency->decimal_places), - 'target_date' => $targetDate, - 'start_date' => $startDate, - 'accounts' => [], - 'object_group' => null !== $piggyBank->objectGroups->first() ? $piggyBank->objectGroups->first()->title : '', - 'notes' => null === $note ? '' : $note->text, + 'name' => $piggyBank->name, + 'transaction_currency_id' => (int) $piggyBank->transaction_currency_id, + 'target_amount' => app('steam')->bcround($piggyBank->target_amount, $piggyBank->transactionCurrency->decimal_places), + 'target_date' => $targetDate, + 'start_date' => $startDate, + 'accounts' => [], + 'object_group' => null !== $piggyBank->objectGroups->first() ? $piggyBank->objectGroups->first()->title : '', + 'notes' => null === $note ? '' : $note->text, ]; foreach ($piggyBank->accounts as $account) { $preFilled['accounts'][] = $account->id; diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index 986bb2e5b8..b36aaa900e 100644 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -32,11 +32,11 @@ use Symfony\Component\HttpFoundation\Request; class TrustProxies extends Middleware { // After... - protected $headers - = Request::HEADER_X_FORWARDED_FOR + protected $headers = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO + | Request::HEADER_X_FORWARDED_PREFIX | Request::HEADER_X_FORWARDED_AWS_ELB; /** diff --git a/app/Http/Requests/PiggyBankUpdateRequest.php b/app/Http/Requests/PiggyBankUpdateRequest.php index 249d948478..d1d38467e5 100644 --- a/app/Http/Requests/PiggyBankUpdateRequest.php +++ b/app/Http/Requests/PiggyBankUpdateRequest.php @@ -49,12 +49,13 @@ class PiggyBankUpdateRequest extends FormRequest { $accounts = $this->get('accounts'); $data = [ - 'name' => $this->convertString('name'), - 'start_date' => $this->getCarbonDate('start_date'), - 'target_amount' => trim($this->convertString('target_amount')), - 'target_date' => $this->getCarbonDate('target_date'), - 'notes' => $this->stringWithNewlines('notes'), - 'object_group_title' => $this->convertString('object_group'), + 'name' => $this->convertString('name'), + 'start_date' => $this->getCarbonDate('start_date'), + 'target_amount' => trim($this->convertString('target_amount')), + 'target_date' => $this->getCarbonDate('target_date'), + 'transaction_currency_id' => $this->convertInteger('transaction_currency_id'), + 'notes' => $this->stringWithNewlines('notes'), + 'object_group_title' => $this->convertString('object_group'), ]; if (!is_array($accounts)) { $accounts = []; @@ -75,15 +76,16 @@ class PiggyBankUpdateRequest extends FormRequest $piggy = $this->route()->parameter('piggyBank'); return [ - 'name' => sprintf('required|min:1|max:255|uniquePiggyBankForUser:%d', $piggy->id), - 'accounts' => 'required|array', - 'accounts.*' => 'required|belongsToUser:accounts', - 'target_amount' => ['nullable', new IsValidPositiveAmount()], - 'start_date' => 'date', - 'target_date' => 'date|nullable', - 'order' => 'integer|max:32768|min:1', - 'object_group' => 'min:0|max:255', - 'notes' => 'min:1|max:32768|nullable', + 'name' => sprintf('required|min:1|max:255|uniquePiggyBankForUser:%d', $piggy->id), + 'accounts' => 'required|array', + 'accounts.*' => 'required|belongsToUser:accounts', + 'target_amount' => ['nullable', new IsValidPositiveAmount()], + 'start_date' => 'date', + 'transaction_currency_id' => 'exists:transaction_currencies,id', + 'target_date' => 'date|nullable', + 'order' => 'integer|max:32768|min:1', + 'object_group' => 'min:0|max:255', + 'notes' => 'min:1|max:32768|nullable', ]; } diff --git a/app/Repositories/PiggyBank/ModifiesPiggyBanks.php b/app/Repositories/PiggyBank/ModifiesPiggyBanks.php index 568e37895d..0be738ef48 100644 --- a/app/Repositories/PiggyBank/ModifiesPiggyBanks.php +++ b/app/Repositories/PiggyBank/ModifiesPiggyBanks.php @@ -31,6 +31,7 @@ use FireflyIII\Models\Account; use FireflyIII\Models\Note; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups; use FireflyIII\Support\Http\Api\ExchangeRateConverter; @@ -223,6 +224,8 @@ trait ModifiesPiggyBanks $factory = new PiggyBankFactory(); $factory->user = $this->user; + // the piggy bank currency is set or updated FIRST, if it exists. + $factory->linkToAccountIds($piggyBank, $data['accounts'] ?? []); @@ -280,6 +283,13 @@ trait ModifiesPiggyBanks if (array_key_exists('name', $data) && '' !== $data['name']) { $piggyBank->name = $data['name']; } + if (array_key_exists('transaction_currency_id', $data) && is_int($data['transaction_currency_id'])) { + $currency = TransactionCurrency::find($data['transaction_currency_id']); + if (null !== $currency) { + $piggyBank->transaction_currency_id = $currency->id; + } + } + if (array_key_exists('target_amount', $data) && '' !== $data['target_amount']) { $piggyBank->target_amount = $data['target_amount']; } diff --git a/app/Support/Export/ExportDataGenerator.php b/app/Support/Export/ExportDataGenerator.php index 3b86d45ec9..ca58d58384 100644 --- a/app/Support/Export/ExportDataGenerator.php +++ b/app/Support/Export/ExportDataGenerator.php @@ -57,6 +57,7 @@ use FireflyIII\Support\Facades\Steam; use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\User; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; use League\Csv\CannotInsertRecord; use League\Csv\Exception; use League\Csv\Writer; diff --git a/changelog.md b/changelog.md index e42065170c..78e8c940df 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## 6.2.12 - 2025-04-21 + +### Fixed + +- [Issue 9755](https://github.com/firefly-iii/firefly-iii/issues/9755) (Unable to create transactions with non-native currency accounts when "display amounts in native currency" is enabled) reported by @dicksonleong +- [Issue 9867](https://github.com/firefly-iii/firefly-iii/issues/9867) (Transactions from Jan 31 being counted in February) reported by @edbingo +- [Issue 9878](https://github.com/firefly-iii/firefly-iii/issues/9878) (Piggy bank currency - wrong setting displayed or setting not saved) reported by @dethegeek +- [Issue 10068](https://github.com/firefly-iii/firefly-iii/issues/10068) (Export Data isn't exporting all transactions in the data) reported by @firsttiger +- [Discussion 10162](https://github.com/orgs/firefly-iii/discussions/10162) (Reverse proxy and `X-Forwarded-Prefix` header) started by @frenchu + ## 6.2.11 - 2025-04-21 ### Added diff --git a/config/firefly.php b/config/firefly.php index f83c90da92..e16d645d92 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -78,7 +78,7 @@ return [ 'running_balance_column' => env('USE_RUNNING_BALANCE', false), // see cer.php for exchange rates feature flag. ], - 'version' => '6.2.11', + 'version' => '6.2.12', 'api_version' => '2.1.0', // field is no longer used. 'db_version' => 25, diff --git a/resources/assets/v1/src/components/transactions/AccountSelect.vue b/resources/assets/v1/src/components/transactions/AccountSelect.vue index 097a3bcf1b..4e3f3b1b1b 100644 --- a/resources/assets/v1/src/components/transactions/AccountSelect.vue +++ b/resources/assets/v1/src/components/transactions/AccountSelect.vue @@ -216,7 +216,7 @@ export default { } }, selectedItem: function (e) { - console.log('In SelectedItem()'); + // console.log('In SelectedItem()'); if (typeof this.name === 'undefined') { // console.log('Is undefined'); return; diff --git a/resources/assets/v1/src/components/transactions/EditTransaction.vue b/resources/assets/v1/src/components/transactions/EditTransaction.vue index 97fbf78f41..cdab12ba1f 100644 --- a/resources/assets/v1/src/components/transactions/EditTransaction.vue +++ b/resources/assets/v1/src/components/transactions/EditTransaction.vue @@ -540,12 +540,18 @@ export default { allowed_types: window.expectedSourceTypes.destination[this.ucFirst(transaction.type)] } }; + // console.log('Destination currency id is ' + result.destination_account.currency_id); // if transaction type is transfer, the destination currency_id etc. MUST match the actual account currency info. - if ('transfer' === transaction.type && null !== transaction.foreign_currency_code) { + // OR if the transaction type is a withdrawal, and the destination account is a liability account, same as above. + if ( + ('transfer' === transaction.type && null !== transaction.foreign_currency_code) || + ('withdrawal' === transaction.type && ['Loan', 'Debt', 'Mortgage'].includes(transaction.destination_type) && null !== transaction.foreign_currency_code) + ) { result.destination_account.currency_id = transaction.foreign_currency_id; result.destination_account.currency_name = transaction.foreign_currency_name; result.destination_account.currency_code = transaction.foreign_currency_code; result.destination_account.currency_decimal_places = transaction.foreign_currency_decimal_places; + // console.log('Set destination currency_id to ' + result.destination_account.currency_id); } diff --git a/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue b/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue index a09016e23e..3f61bec9ed 100644 --- a/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue +++ b/resources/assets/v1/src/components/transactions/ForeignAmountSelect.vue @@ -137,7 +137,6 @@ export default { // lock dropdown list on currencyID of destination. for (const key in this.currencies) { if (this.currencies.hasOwnProperty(key) && /^0$|^[1-9]\d*$/.test(key) && key <= 4294967294) { - if ( parseInt(this.currencies[key].id) === parseInt(this.destination.currency_id) ) { diff --git a/resources/views/piggy-banks/edit.twig b/resources/views/piggy-banks/edit.twig index 23617a4a27..8489ca8064 100644 --- a/resources/views/piggy-banks/edit.twig +++ b/resources/views/piggy-banks/edit.twig @@ -23,7 +23,7 @@
{{ ExpandedForm.text('name') }} {{ ExpandedForm.amountNoCurrency('target_amount') }} - {{ CurrencyForm.currencyList('transaction_currency_id', null, {helpText:'piggy_default_currency'|_}) }} + {{ CurrencyForm.currencyList('transaction_currency_id', preFilled.transaction_currency_id, {helpText:'piggy_default_currency'|_}) }} {{ AccountForm.assetLiabilityMultiAccountList('accounts', preFilled.accounts, {label: 'saveOnAccounts'|_, helpText: 'piggy_account_currency_match'|_ }) }}