diff --git a/finnow-api/source/account/api.d b/finnow-api/source/account/api.d index cd77fcf..781a1f3 100644 --- a/finnow-api/source/account/api.d +++ b/finnow-api/source/account/api.d @@ -79,6 +79,23 @@ void handleDeleteAccount(ref ServerHttpRequest request, ref ServerHttpResponse r ds.getAccountRepository().deleteById(accountId); } +void handleGetAccountBalance(ref ServerHttpRequest request, ref ServerHttpResponse response) { + ulong accountId = request.getPathParamAs!ulong("accountId"); + auto ds = getProfileDataSource(request); + SysTime timestamp = Clock.currTime(UTC()); + string providedTimestamp = request.getParamAs!string("timestamp"); + if (providedTimestamp != null && providedTimestamp.length > 0) { + timestamp = SysTime.fromISOExtString(providedTimestamp); + } + Optional!long balance = getBalance(ds, accountId, timestamp); + if (balance.isNull) { + response.writeBodyString("{\"balance\": null}", ContentTypes.APPLICATION_JSON); + } else { + import std.conv : to; + response.writeBodyString("{\"balance\": " ~ balance.value.to!string ~ "}", ContentTypes.APPLICATION_JSON); + } +} + void handleGetAccountHistory(ref ServerHttpRequest request, ref ServerHttpResponse response) { ulong accountId = request.getPathParamOrThrow!ulong("accountId"); PageRequest pagination = PageRequest.parse(request, PageRequest(1, 10, [Sort("timestamp", SortDir.DESC)])); diff --git a/finnow-api/source/api_mapping.d b/finnow-api/source/api_mapping.d index 803aafb..02ce249 100644 --- a/finnow-api/source/api_mapping.d +++ b/finnow-api/source/api_mapping.d @@ -59,6 +59,7 @@ HttpRequestHandler mapApiHandlers(string webOrigin) { a.map(HttpMethod.GET, ACCOUNT_PATH, &handleGetAccount); a.map(HttpMethod.PUT, ACCOUNT_PATH, &handleUpdateAccount); a.map(HttpMethod.DELETE, ACCOUNT_PATH, &handleDeleteAccount); + a.map(HttpMethod.GET, ACCOUNT_PATH ~ "/balance", &handleGetAccountBalance); a.map(HttpMethod.GET, ACCOUNT_PATH ~ "/history", &handleGetAccountHistory); a.map(HttpMethod.GET, ACCOUNT_PATH ~ "/value-records", &handleGetValueRecords); a.map(HttpMethod.GET, ACCOUNT_PATH ~ "/value-records/:valueRecordId:ulong", &handleGetValueRecord); diff --git a/web-app/src/api/account.ts b/web-app/src/api/account.ts index 1b25b13..ea54543 100644 --- a/web-app/src/api/account.ts +++ b/web-app/src/api/account.ts @@ -173,6 +173,14 @@ export class AccountApiClient extends ApiClient { return super.delete(this.path + '/' + id) } + async getBalance(id: number, timestamp: Date): Promise { + const result: { balance: number | null } = await super.getJson( + `${this.path}/${id}/balance?timestamp=${timestamp.toISOString()}`, + ) + if (result.balance === null) return undefined + return result.balance + } + getHistory(id: number, pageRequest: PageRequest): Promise> { return super.getJsonPage(`${this.path}/${id}/history`, pageRequest) } diff --git a/web-app/src/assets/styles/spacing.css b/web-app/src/assets/styles/spacing.css index f1bb952..61ab236 100644 --- a/web-app/src/assets/styles/spacing.css +++ b/web-app/src/assets/styles/spacing.css @@ -14,3 +14,8 @@ margin-left: 0.5rem; margin-right: 0.5rem; } + +.my-1 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} diff --git a/web-app/src/pages/TransactionPage.vue b/web-app/src/pages/TransactionPage.vue index a93b3a9..d8aaafb 100644 --- a/web-app/src/pages/TransactionPage.vue +++ b/web-app/src/pages/TransactionPage.vue @@ -15,17 +15,26 @@ import AttachmentRow from '@/components/common/AttachmentRow.vue' import LineItemCard from '@/components/LineItemCard.vue' import AppBadge from '@/components/common/AppBadge.vue' import ButtonBar from '@/components/common/ButtonBar.vue' +import { AccountApiClient } from '@/api/account' + +interface BalanceDiff { + before: number + after: number +} const route = useRoute() const router = useRouter() const transactionApi = new TransactionApiClient(getSelectedProfile(route)) const transaction: Ref = ref() +const creditedAccountBalanceDiff = ref() +const debitedAccountBalanceDiff = ref() onMounted(async () => { const transactionId = parseInt(route.params.id as string) try { transaction.value = await transactionApi.getTransaction(transactionId) + fetchBalanceDiffs() } catch (err) { console.error(err) await router.replace('/') @@ -35,6 +44,34 @@ onMounted(async () => { } }) +async function fetchBalanceDiffs() { + const txn = transaction.value + if (!txn) return + const txnTime = new Date(txn.timestamp).getTime() + const beforeTs = new Date(txnTime - 1000) + const afterTs = new Date(txnTime + 1000) + const accountApi = new AccountApiClient(route) + const p1: Promise = txn.creditedAccount + ? accountApi.getBalance(txn.creditedAccount.id, beforeTs) + : Promise.resolve(undefined) + const p2: Promise = txn.creditedAccount + ? accountApi.getBalance(txn.creditedAccount.id, afterTs) + : Promise.resolve(undefined) + const p3: Promise = txn.debitedAccount + ? accountApi.getBalance(txn.debitedAccount.id, beforeTs) + : Promise.resolve(undefined) + const p4: Promise = txn.debitedAccount + ? accountApi.getBalance(txn.debitedAccount.id, afterTs) + : Promise.resolve(undefined) + const results = await Promise.all([p1, p2, p3, p4]) + if (results[0] !== undefined && results[1] !== undefined) { + creditedAccountBalanceDiff.value = { before: results[0], after: results[1] } + } + if (results[2] !== undefined && results[3] !== undefined) { + debitedAccountBalanceDiff.value = { before: results[2], after: results[3] } + } +} + async function deleteTransaction() { if (!transaction.value) return const conf = await showConfirm( @@ -68,18 +105,39 @@ async function deleteTransaction() {

{{ transaction.description }}

-

+

Credited from {{ transaction.creditedAccount.name }} (#{{ transaction.creditedAccount.numberSuffix }}) -

-

+

+ Balance Before: + + {{ formatMoney(creditedAccountBalanceDiff.before, transaction.currency) }} + + / After: + + {{ formatMoney(creditedAccountBalanceDiff.after, transaction.currency) }} + +
+
+ +
Debited to {{ transaction.debitedAccount.name }} (#{{ transaction.debitedAccount.numberSuffix }}) -

+
+ Balance Before: + + {{ formatMoney(debitedAccountBalanceDiff.before, transaction.currency) }} + + / After: + + {{ formatMoney(debitedAccountBalanceDiff.after, transaction.currency) }} + +
+