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();
}