From 25b715156bb4beb60021e9116248e267a48b6883 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Wed, 3 Sep 2025 22:02:34 -0400 Subject: [PATCH] Added total balances API endpoint. --- finnow-api/source/account/api.d | 6 ++++ finnow-api/source/account/service.d | 34 +++++++++++++++++++++++ finnow-api/source/api_mapping.d | 1 + web-app/src/api/account.ts | 11 ++++++++ web-app/src/pages/home/AccountsModule.vue | 17 +++++++++++- 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/finnow-api/source/account/api.d b/finnow-api/source/account/api.d index 62255bb..cd77fcf 100644 --- a/finnow-api/source/account/api.d +++ b/finnow-api/source/account/api.d @@ -115,6 +115,12 @@ private void writeHistoryResponse(ref ServerHttpResponse response, in Page!Accou response.writeBodyString(jsonStr, ContentTypes.APPLICATION_JSON); } +void handleGetTotalBalances(ref ServerHttpRequest request, ref ServerHttpResponse response) { + auto ds = getProfileDataSource(request); + auto balances = getTotalBalanceForAllAccounts(ds); + writeJsonBody(response, balances); +} + // Value records: const PageRequest VALUE_RECORD_DEFAULT_PAGE_REQUEST = PageRequest(1, 10, [Sort("timestamp", SortDir.DESC)]); diff --git a/finnow-api/source/account/service.d b/finnow-api/source/account/service.d index bc139b3..223720f 100644 --- a/finnow-api/source/account/service.d +++ b/finnow-api/source/account/service.d @@ -6,6 +6,7 @@ import std.datetime; import account.model; import account.data; import profile.data; +import util.money; /** * Gets the balance for an account, at a given point in time. @@ -46,6 +47,39 @@ Optional!long getBalance(ProfileDataSource ds, ulong accountId, SysTime timestam return Optional!long.empty; } +struct CurrencyBalance { + Currency currency; + long balance; +} + +CurrencyBalance[] getTotalBalanceForAllAccounts(ProfileDataSource ds, SysTime timestamp = Clock.currTime(UTC())) { + auto accountRepo = ds.getAccountRepository(); + CurrencyBalance[] balances; + foreach (Account account; accountRepo.findAll()) { + Optional!long accountBalance = getBalance(ds, account.id, timestamp); + if (!accountBalance.isNull) { + long value = accountBalance.value; + if (!account.type.debitsPositive) { + value = -value; + } + // Add the balance to the relevant currency balance: + bool added = false; + foreach (ref cb; balances) { + if (cb.currency.code == account.currency.code) { + cb.balance += value; + added = true; + break; + } + } + if (!added) { + balances ~= CurrencyBalance(account.currency, value); + } + } + } + return balances; + +} + /** * Helper method that derives a balance for an account, by using the nearest * value record, and all journal entries between that record and the desired diff --git a/finnow-api/source/api_mapping.d b/finnow-api/source/api_mapping.d index 5fb2798..a05edb4 100644 --- a/finnow-api/source/api_mapping.d +++ b/finnow-api/source/api_mapping.d @@ -51,6 +51,7 @@ HttpRequestHandler mapApiHandlers(string webOrigin) { import account.api; a.map(HttpMethod.GET, PROFILE_PATH ~ "/accounts", &handleGetAccounts); a.map(HttpMethod.POST, PROFILE_PATH ~ "/accounts", &handleCreateAccount); + a.map(HttpMethod.GET, PROFILE_PATH ~ "/account-balances", &handleGetTotalBalances); const ACCOUNT_PATH = PROFILE_PATH ~ "/accounts/:accountId:ulong"; a.map(HttpMethod.GET, ACCOUNT_PATH, &handleGetAccount); a.map(HttpMethod.PUT, ACCOUNT_PATH, &handleUpdateAccount); diff --git a/web-app/src/api/account.ts b/web-app/src/api/account.ts index d7e69b4..2dc22c6 100644 --- a/web-app/src/api/account.ts +++ b/web-app/src/api/account.ts @@ -123,11 +123,18 @@ export interface AccountHistoryJournalEntryItem extends AccountHistoryItem { transactionDescription: string } +export interface CurrencyBalance { + currency: Currency + balance: number +} + export class AccountApiClient extends ApiClient { readonly path: string + readonly profileName: string constructor(route: RouteLocation) { super() + this.profileName = getSelectedProfile(route) this.path = `/profiles/${getSelectedProfile(route)}/accounts` } @@ -155,6 +162,10 @@ export class AccountApiClient extends ApiClient { return super.getJsonPage(`${this.path}/${id}/history`, pageRequest) } + getTotalBalances(): Promise { + return super.getJson(`/profiles/${this.profileName}/account-balances`) + } + getValueRecords(accountId: number, pageRequest: PageRequest): Promise> { return super.getJsonPage(this.path + '/' + accountId + '/value-records', pageRequest) } diff --git a/web-app/src/pages/home/AccountsModule.vue b/web-app/src/pages/home/AccountsModule.vue index 86367b1..1bcf983 100644 --- a/web-app/src/pages/home/AccountsModule.vue +++ b/web-app/src/pages/home/AccountsModule.vue @@ -1,5 +1,5 @@