diff --git a/app/Api/V2/Controllers/Autocomplete/AccountController.php b/app/Api/V2/Controllers/Autocomplete/AccountController.php index 53fde1fc89..3063850113 100644 --- a/app/Api/V2/Controllers/Autocomplete/AccountController.php +++ b/app/Api/V2/Controllers/Autocomplete/AccountController.php @@ -101,11 +101,16 @@ class AccountController extends Controller private function parseAccount(Account $account): array { + $currency = $this->adminRepository->getAccountCurrency($account); return [ 'id' => (string) $account->id, 'title' => $account->name, 'meta' => [ 'type' => $account->accountType->type, + 'currency_id' => null === $currency ? null : (string) $currency->id, + 'currency_code' => $currency?->code, + 'currency_symbol' => $currency?->symbol, + 'currency_decimal' => $currency?->decimal_places, 'account_balances' => $this->getAccountBalances($account), ], ]; diff --git a/app/Api/V2/Request/Autocomplete/AutocompleteRequest.php b/app/Api/V2/Request/Autocomplete/AutocompleteRequest.php index 5c22f9737b..4d2f353b16 100644 --- a/app/Api/V2/Request/Autocomplete/AutocompleteRequest.php +++ b/app/Api/V2/Request/Autocomplete/AutocompleteRequest.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V2\Request\Autocomplete; use Carbon\Carbon; +use Carbon\Exceptions\InvalidFormatException; use FireflyIII\JsonApi\Rules\IsValidFilter; use FireflyIII\JsonApi\Rules\IsValidPage; use FireflyIII\Models\AccountType; @@ -33,7 +34,7 @@ use FireflyIII\Support\Request\ConvertsDataTypes; use Illuminate\Foundation\Http\FormRequest; use LaravelJsonApi\Core\Query\QueryParameters; use LaravelJsonApi\Validation\Rule as JsonApiRule; - +use Illuminate\Support\Facades\Log; /** * Class AutocompleteRequest */ @@ -54,10 +55,15 @@ class AutocompleteRequest extends FormRequest public function getParameters(): array { $queryParameters = QueryParameters::cast($this->all()); - $date = Carbon::createFromFormat('Y-m-d', $queryParameters->filter()->value('date', date('Y-m-d')), config('app.timezone')); - $query = $queryParameters->filter()->value('query', ''); + try { + $date = Carbon::createFromFormat('Y-m-d', $queryParameters->filter()?->value('date', date('Y-m-d')), config('app.timezone')); + } catch(InvalidFormatException $e) { + Log::debug(sprintf('Invalid date format in autocomplete request. Using today: %s', $e->getMessage())); + $date = today(); + } + $query = $queryParameters->filter()?->value('query', '') ?? ''; $size = (int) ($queryParameters->page()['size'] ?? 50); - $accountTypes = $this->getAccountTypeParameter($queryParameters->filter()->value('account_types', '')); + $accountTypes = $this->getAccountTypeParameter($queryParameters->filter()?->value('account_types', '') ?? ''); return [ diff --git a/app/Support/Models/AccountBalanceCalculator.php b/app/Support/Models/AccountBalanceCalculator.php index b02bfccd5b..ccd5c62dfb 100644 --- a/app/Support/Models/AccountBalanceCalculator.php +++ b/app/Support/Models/AccountBalanceCalculator.php @@ -37,9 +37,13 @@ class AccountBalanceCalculator // first collect normal amounts (in whatever currency), and set them. // select account_id, transaction_currency_id, foreign_currency_id, sum(amount), sum(foreign_amount) from transactions group by account_id, transaction_currency_id, foreign_currency_id - $result = Transaction - ::groupBy(['account_id', 'transaction_currency_id', 'foreign_currency_id']) - ->get(['account_id', 'transaction_currency_id', 'foreign_currency_id', DB::raw('SUM(amount) as sum_amount'), DB::raw('SUM(foreign_amount) as sum_foreign_amount')]); + $query = Transaction::groupBy(['account_id', 'transaction_currency_id', 'foreign_currency_id']); + + if (null !== $account) { + $query->where('account_id', $account->id); + } + + $result = $query->get(['account_id', 'transaction_currency_id', 'foreign_currency_id', DB::raw('SUM(amount) as sum_amount'), DB::raw('SUM(foreign_amount) as sum_foreign_amount')]); // reset account balances: self::resetAccountBalances($account); @@ -60,7 +64,7 @@ class AccountBalanceCalculator // then do foreign amount, if present: if ($foreignCurrency > 0) { - $entry = self::getBalance('balance', $account, $foreignCurrency); + $entry = self::getBalance('balance', $account, $foreignCurrency); $entry->balance = bcadd($entry->balance, $sumForeignAmount); $entry->save(); Log::debug(sprintf('Set balance entry #%d to amount %s', $entry->id, $entry->balance)); @@ -68,6 +72,7 @@ class AccountBalanceCalculator } return; } + private static function getBalance(string $title, int $account, int $currency): AccountBalance { $entry = AccountBalance::where('title', $title)->where('account_id', $account)->where('transaction_currency_id', $currency)->first(); diff --git a/database/migrations/2024_05_12_060551_create_account_balance_table.php b/database/migrations/2024_05_12_060551_create_account_balance_table.php index 8ee35a27c8..432023e6e4 100644 --- a/database/migrations/2024_05_12_060551_create_account_balance_table.php +++ b/database/migrations/2024_05_12_060551_create_account_balance_table.php @@ -17,9 +17,15 @@ return new class extends Migration { $table->string('title', 100)->nullable(); $table->integer('account_id', false, true); $table->integer('transaction_currency_id', false, true); + $table->date('date')->nullable(); + $table->integer('transaction_journal_id', false, true)->nullable(); $table->decimal('balance', 32, 12); $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); + $table->foreign('transaction_journal_id')->references('id')->on('transaction_journals')->onDelete('cascade'); $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); + + + $table->unique(['account_id', 'transaction_currency_id', 'title'], 'unique_account_currency'); }); } diff --git a/resources/assets/v2/src/pages/transactions/create.js b/resources/assets/v2/src/pages/transactions/create.js index 490e369388..7b8d9999e4 100644 --- a/resources/assets/v2/src/pages/transactions/create.js +++ b/resources/assets/v2/src/pages/transactions/create.js @@ -486,13 +486,15 @@ let transactions = function () { // addedSplit, is called from the HTML // for source account const renderAccount = function (item, b, c) { - return item.name_with_balance + '
' + i18next.t('firefly.account_type_' + item.type) + ''; + return item.title + '
' + i18next.t('firefly.account_type_' + item.meta.type) + ''; }; addAutocomplete({ selector: 'input.ac-source', serverUrl: urls.account, // filters: this.filters.source, - // onRenderItem: renderAccount, + onRenderItem: renderAccount, + valueField: 'id', + labelField: 'title', onChange: changeSourceAccount, onSelectItem: selectSourceAccount, hiddenValue: this.entries[count].source_account.alpine_name @@ -500,7 +502,9 @@ let transactions = function () { addAutocomplete({ selector: 'input.ac-dest', serverUrl: urls.account, - filters: this.filters.destination, + account_types: this.filters.destination, + valueField: 'id', + labelField: 'title', onRenderItem: renderAccount, onChange: changeDestinationAccount, onSelectItem: selectDestinationAccount diff --git a/resources/assets/v2/src/pages/transactions/edit.js b/resources/assets/v2/src/pages/transactions/edit.js index 817b5d6299..433520f951 100644 --- a/resources/assets/v2/src/pages/transactions/edit.js +++ b/resources/assets/v2/src/pages/transactions/edit.js @@ -200,20 +200,23 @@ let transactions = function () { // addedSplit, is called from the HTML // for source account const renderAccount = function (item, b, c) { - return item.name_with_balance + '
' + i18next.t('firefly.account_type_' + item.type) + ''; + console.log(item); + return item.title + '
' + i18next.t('firefly.account_type_' + item.meta.type) + ''; }; addAutocomplete({ selector: 'input.ac-source', serverUrl: urls.account, - filters: this.filters.source, + account_types: this.filters.source, onRenderItem: renderAccount, + valueField: 'id', + labelField: 'title', onChange: changeSourceAccount, onSelectItem: selectSourceAccount }); addAutocomplete({ selector: 'input.ac-dest', serverUrl: urls.account, - filters: this.filters.destination, + account_types: this.filters.destination, onRenderItem: renderAccount, onChange: changeDestinationAccount, onSelectItem: selectDestinationAccount @@ -222,7 +225,7 @@ let transactions = function () { selector: 'input.ac-category', serverUrl: urls.category, valueField: 'id', - labelField: 'name', + labelField: 'title', onChange: changeCategory, onSelectItem: changeCategory }); diff --git a/resources/assets/v2/src/pages/transactions/shared/add-autocomplete.js b/resources/assets/v2/src/pages/transactions/shared/add-autocomplete.js index 0acb04f368..d037448707 100644 --- a/resources/assets/v2/src/pages/transactions/shared/add-autocomplete.js +++ b/resources/assets/v2/src/pages/transactions/shared/add-autocomplete.js @@ -38,13 +38,14 @@ export function addAutocomplete(options) { 'X-CSRF-TOKEN': document.head.querySelector('meta[name="csrf-token"]').content } }, + queryParam: 'filter[query]', hiddenInput: true, // preventBrowserAutocomplete: true, highlightTyped: true, liveServer: true, }; - if (typeof options.filters !== 'undefined' && options.filters.length > 0) { - params.serverParams.types = options.filters; + if (typeof options.account_types !== 'undefined' && options.account_types.length > 0) { + params.serverParams['filter[account_types]'] = options.account_types; } if (typeof options.onRenderItem !== 'undefined' && null !== options.onRenderItem) { params.onRenderItem = options.onRenderItem; diff --git a/resources/assets/v2/src/pages/transactions/shared/autocomplete-functions.js b/resources/assets/v2/src/pages/transactions/shared/autocomplete-functions.js index 19e11aad52..d878837dcc 100644 --- a/resources/assets/v2/src/pages/transactions/shared/autocomplete-functions.js +++ b/resources/assets/v2/src/pages/transactions/shared/autocomplete-functions.js @@ -55,13 +55,12 @@ export function changeDestinationAccount(item, ac) { export function selectDestinationAccount(item, ac) { const index = parseInt(ac._searchInput.attributes['data-index'].value); document.querySelector('#form')._x_dataStack[0].$data.entries[index].destination_account = { - id: item.id, name: item.name, alpine_name: item.name, type: item.type, currency_code: item.currency_code, + id: item.id, name: item.title, alpine_name: item.title, type: item.meta.type, currency_code: item.meta.currency_code, }; document.querySelector('#form')._x_dataStack[0].changedDestinationAccount(); } export function changeSourceAccount(item, ac) { - // console.log('changeSourceAccount'); if (typeof item === 'undefined') { const index = parseInt(ac._searchInput.attributes['data-index'].value); let source = document.querySelector('#form')._x_dataStack[0].$data.entries[index].source_account; @@ -79,7 +78,7 @@ export function changeSourceAccount(item, ac) { export function selectSourceAccount(item, ac) { const index = parseInt(ac._searchInput.attributes['data-index'].value); document.querySelector('#form')._x_dataStack[0].$data.entries[index].source_account = { - id: item.id, name: item.name, alpine_name: item.name, type: item.type, currency_code: item.currency_code, + id: item.id, name: item.title, alpine_name: item.title, type: item.meta.type, currency_code: item.meta.currency_code, }; document.querySelector('#form')._x_dataStack[0].changedSourceAccount(); }