diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index 6e56e3d7..8581abec 100644 --- a/pkg/api/transactions.go +++ b/pkg/api/transactions.go @@ -340,7 +340,7 @@ func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebC minTransactionTime = utils.GetMinTransactionTimeFromUnixTime(reconciliationStatementRequest.StartTime) } - transactionsWithAccountBalance, openingBalance, closingBalance, err := a.transactions.GetAllTransactionsWithAccountBalanceByMaxTime(c, uid, pageCountForAccountStatement, maxTransactionTime, minTransactionTime, reconciliationStatementRequest.AccountId) + transactionsWithAccountBalance, totalInflows, totalOutflows, openingBalance, closingBalance, err := a.transactions.GetAllTransactionsWithAccountBalanceByMaxTime(c, uid, pageCountForAccountStatement, maxTransactionTime, minTransactionTime, reconciliationStatementRequest.AccountId, account.Category) if err != nil { log.Errorf(c, "[transactions.TransactionReconciliationStatementHandler] failed to get transactions from \"%d\" to \"%d\" for user \"uid:%d\", because %s", reconciliationStatementRequest.StartTime, reconciliationStatementRequest.EndTime, uid, err.Error()) @@ -384,6 +384,8 @@ func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebC reconciliationStatementResp := &models.TransactionReconciliationStatementResponse{ Transactions: responseItems, + TotalInflows: totalInflows, + TotalOutflows: totalOutflows, OpeningBalance: openingBalance, ClosingBalance: closingBalance, } diff --git a/pkg/models/account.go b/pkg/models/account.go index e4a2fb0c..dd8ec37a 100644 --- a/pkg/models/account.go +++ b/pkg/models/account.go @@ -45,6 +45,16 @@ var liabilityAccountCategory = map[AccountCategory]bool{ ACCOUNT_CATEGORY_CERTIFICATE_OF_DEPOSIT: false, } +// IsAsset returns whether the account category is an asset category +func (c AccountCategory) IsAsset() bool { + return assetAccountCategory[c] +} + +// IsLiability returns whether the account category is a liability category +func (c AccountCategory) IsLiability() bool { + return liabilityAccountCategory[c] +} + // AccountType represents account type type AccountType byte diff --git a/pkg/models/transaction.go b/pkg/models/transaction.go index e8396a27..46271e6b 100644 --- a/pkg/models/transaction.go +++ b/pkg/models/transaction.go @@ -344,6 +344,8 @@ type TransactionReconciliationStatementResponseItem struct { // TransactionReconciliationStatementResponse represents the response of all transaction reconciliation statement response type TransactionReconciliationStatementResponse struct { Transactions []*TransactionReconciliationStatementResponseItem `json:"transactions"` + TotalInflows int64 `json:"totalInflows"` + TotalOutflows int64 `json:"totalOutflows"` OpeningBalance int64 `json:"openingBalance"` ClosingBalance int64 `json:"closingBalance"` } diff --git a/pkg/services/transactions.go b/pkg/services/transactions.go index 1e419dff..2d452606 100644 --- a/pkg/services/transactions.go +++ b/pkg/services/transactions.go @@ -108,7 +108,7 @@ func (s *TransactionService) GetAllSpecifiedTransactions(c core.Context, uid int } // GetAllTransactionsWithAccountBalanceByMaxTime returns account statement within time range -func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c core.Context, uid int64, pageCount int32, maxTransactionTime int64, minTransactionTime int64, accountId int64) ([]*models.TransactionWithAccountBalance, int64, int64, error) { +func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c core.Context, uid int64, pageCount int32, maxTransactionTime int64, minTransactionTime int64, accountId int64, accountCategory models.AccountCategory) ([]*models.TransactionWithAccountBalance, int64, int64, int64, int64, error) { if maxTransactionTime <= 0 { maxTransactionTime = utils.GetMaxTransactionTimeFromUnixTime(time.Now().Unix()) } @@ -119,7 +119,7 @@ func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c cor transactions, err := s.GetTransactionsByMaxTime(c, uid, maxTransactionTime, 0, 0, nil, []int64{accountId}, nil, false, models.TRANSACTION_TAG_FILTER_HAS_ANY, "", "", 1, pageCount, false, true) if err != nil { - return nil, 0, 0, err + return nil, 0, 0, 0, 0, err } allTransactions = append(allTransactions, transactions...) @@ -135,9 +135,11 @@ func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c cor allTransactionsAndAccountBalance := make([]*models.TransactionWithAccountBalance, 0, len(allTransactions)) if len(allTransactions) < 1 { - return allTransactionsAndAccountBalance, 0, 0, nil + return allTransactionsAndAccountBalance, 0, 0, 0, 0, nil } + totalInflows := int64(0) + totalOutflows := int64(0) openingBalance := int64(0) accumulatedBalance := int64(0) @@ -156,7 +158,7 @@ func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c cor accumulatedBalance = accumulatedBalance + transaction.Amount } else { log.Errorf(c, "[transactions.GetAllTransactionsWithAccountBalanceByMaxTime] trasaction type (%d) is invalid (id:%d)", transaction.TransactionId, transaction.Type) - return nil, 0, 0, errs.ErrTransactionTypeInvalid + return nil, 0, 0, 0, 0, errs.ErrTransactionTypeInvalid } if transaction.TransactionTime < minTransactionTime { @@ -164,6 +166,22 @@ func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c cor continue } + if transaction.Type == models.TRANSACTION_DB_TYPE_MODIFY_BALANCE { + if accountCategory.IsAsset() { + totalInflows = totalInflows + transaction.RelatedAccountAmount + } else if accountCategory.IsLiability() { + totalOutflows = totalOutflows - transaction.RelatedAccountAmount + } + } else if transaction.Type == models.TRANSACTION_DB_TYPE_INCOME { + totalInflows = totalInflows + transaction.Amount + } else if transaction.Type == models.TRANSACTION_DB_TYPE_EXPENSE { + totalOutflows = totalOutflows + transaction.Amount + } else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_OUT { + totalOutflows = totalOutflows + transaction.Amount + } else if transaction.Type == models.TRANSACTION_DB_TYPE_TRANSFER_IN { + totalInflows = totalInflows + transaction.Amount + } + transactionsAndAccountBalance := &models.TransactionWithAccountBalance{ Transaction: transaction, AccountBalance: accumulatedBalance, @@ -172,7 +190,7 @@ func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c cor allTransactionsAndAccountBalance = append(allTransactionsAndAccountBalance, transactionsAndAccountBalance) } - return allTransactionsAndAccountBalance, openingBalance, accumulatedBalance, nil + return allTransactionsAndAccountBalance, totalInflows, totalOutflows, openingBalance, accumulatedBalance, nil } // GetTransactionsByMaxTime returns transactions before given time diff --git a/src/models/transaction.ts b/src/models/transaction.ts index a7dc787f..5d64c658 100644 --- a/src/models/transaction.ts +++ b/src/models/transaction.ts @@ -667,6 +667,8 @@ export interface TransactionReconciliationStatementResponseItem extends Transact export interface TransactionReconciliationStatementResponse { readonly transactions: TransactionReconciliationStatementResponseItem[]; + readonly totalInflows: number; + readonly totalOutflows: number; readonly openingBalance: number; readonly closingBalance: number; } diff --git a/src/views/base/accounts/ReconciliationStatementPageBase.ts b/src/views/base/accounts/ReconciliationStatementPageBase.ts index f6924ecf..d3c09e49 100644 --- a/src/views/base/accounts/ReconciliationStatementPageBase.ts +++ b/src/views/base/accounts/ReconciliationStatementPageBase.ts @@ -12,7 +12,10 @@ import { TransactionType } from '@/core/transaction.ts'; import { KnownFileType } from '@/core/file.ts'; import type { Account } from '@/models/account.ts'; import type { TransactionCategory } from '@/models/transaction_category.ts'; -import type { TransactionReconciliationStatementResponseItem } from '@/models/transaction.ts'; +import type { + TransactionReconciliationStatementResponse, + TransactionReconciliationStatementResponseItem +} from '@/models/transaction.ts'; import { replaceAll, @@ -46,9 +49,7 @@ export function useReconciliationStatementPageBase() { const accountId = ref(''); const startTime = ref(0); const endTime = ref(0); - const reconciliationStatements = ref([]); - const openingBalance = ref(0); - const closingBalance = ref(0); + const reconciliationStatements = ref(undefined); const firstDayOfWeek = computed(() => userStore.currentUserFirstDayOfWeek); const fiscalYearStart = computed(() => userStore.currentUserFiscalYearStart); @@ -74,38 +75,6 @@ export function useReconciliationStatementPageBase() { const allAccountsMap = computed>(() => accountsStore.allAccountsMap); const allCategoriesMap = computed>(() => transactionCategoriesStore.allTransactionCategoriesMap); - const totalOutflows = computed(() => { - let totalOutflows = 0; - - for (let i = 0; i < reconciliationStatements.value.length; i++) { - const transaction = reconciliationStatements.value[i]; - - if (transaction.type === TransactionType.Expense) { - totalOutflows += transaction.sourceAmount; - } else if (transaction.type === TransactionType.Transfer && transaction.sourceAccountId === accountId.value) { - totalOutflows += transaction.sourceAmount; - } - } - - return totalOutflows; - }); - - const totalInflows = computed(() => { - let totalInflows = 0; - - for (let i = 0; i < reconciliationStatements.value.length; i++) { - const transaction = reconciliationStatements.value[i]; - - if (transaction.type === TransactionType.Income) { - totalInflows += transaction.sourceAmount; - } else if (transaction.type === TransactionType.Transfer && transaction.destinationAccountId === accountId.value) { - totalInflows += transaction.destinationAmount; - } - } - - return totalInflows; - }); - const displayStartDateTime = computed(() => { return formatUnixTimeToLongDateTime(startTime.value); }); @@ -114,31 +83,31 @@ export function useReconciliationStatementPageBase() { return formatUnixTimeToLongDateTime(endTime.value); }); - const displayTotalOutflows = computed(() => { - return formatAmountWithCurrency(totalOutflows.value, currentAccountCurrency.value); + const displayTotalInflows = computed(() => { + return formatAmountWithCurrency(reconciliationStatements.value?.totalInflows ?? 0, currentAccountCurrency.value); }); - const displayTotalInflows = computed(() => { - return formatAmountWithCurrency(totalInflows.value, currentAccountCurrency.value); + const displayTotalOutflows = computed(() => { + return formatAmountWithCurrency(reconciliationStatements.value?.totalOutflows ?? 0, currentAccountCurrency.value); }); const displayTotalBalance = computed(() => { - return formatAmountWithCurrency(totalInflows.value - totalOutflows.value, currentAccountCurrency.value); + return formatAmountWithCurrency((reconciliationStatements?.value?.totalInflows ?? 0) - (reconciliationStatements.value?.totalOutflows ?? 0), currentAccountCurrency.value); }); const displayOpeningBalance = computed(() => { if (isCurrentLiabilityAccount.value) { - return formatAmountWithCurrency(-openingBalance.value, currentAccountCurrency.value); + return formatAmountWithCurrency(-(reconciliationStatements?.value?.openingBalance ?? 0), currentAccountCurrency.value); } else { - return formatAmountWithCurrency(openingBalance.value, currentAccountCurrency.value); + return formatAmountWithCurrency(reconciliationStatements?.value?.openingBalance ?? 0, currentAccountCurrency.value); } }); const displayClosingBalance = computed(() => { if (isCurrentLiabilityAccount.value) { - return formatAmountWithCurrency(-closingBalance.value, currentAccountCurrency.value); + return formatAmountWithCurrency(-(reconciliationStatements?.value?.closingBalance ?? 0), currentAccountCurrency.value); } else { - return formatAmountWithCurrency(closingBalance.value, currentAccountCurrency.value); + return formatAmountWithCurrency(reconciliationStatements?.value?.closingBalance ?? 0, currentAccountCurrency.value); } }); @@ -220,7 +189,8 @@ export function useReconciliationStatementPageBase() { tt('Description') ].join(separator) + '\n'; - const rows = reconciliationStatements.value.map(transaction => { + const transactions = reconciliationStatements.value?.transactions ?? []; + const rows = transactions.map(transaction => { const transactionTime = getUnixTime(parseDateFromUnixTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value)); let type = ''; let categoryName = allCategoriesMap.value[transaction.categoryId]?.name || ''; @@ -285,8 +255,6 @@ export function useReconciliationStatementPageBase() { startTime, endTime, reconciliationStatements, - openingBalance, - closingBalance, // computed states firstDayOfWeek, fiscalYearStart, @@ -300,8 +268,8 @@ export function useReconciliationStatementPageBase() { allCategoriesMap, displayStartDateTime, displayEndDateTime, - displayTotalOutflows, displayTotalInflows, + displayTotalOutflows, displayTotalBalance, displayOpeningBalance, displayClosingBalance, diff --git a/src/views/desktop/accounts/list/dialogs/ReconciliationStatementDialog.vue b/src/views/desktop/accounts/list/dialogs/ReconciliationStatementDialog.vue index 5d17ac67..cc5efd53 100644 --- a/src/views/desktop/accounts/list/dialogs/ReconciliationStatementDialog.vue +++ b/src/views/desktop/accounts/list/dialogs/ReconciliationStatementDialog.vue @@ -17,12 +17,12 @@ @click="addTransaction()"> {{ tt('Export to CSV (Comma-separated values) File') }} {{ tt('Export to TSV (Tab-separated values) File') }} @@ -94,7 +94,7 @@ item-value="index" :class="{ 'disabled': loading }" :headers="dataTableHeaders" - :items="reconciliationStatements" + :items="reconciliationStatements?.transactions ?? []" :no-data-text="loading ? '' : tt('No transaction data')" v-model:items-per-page="countPerPage" v-model:page="currentPage" @@ -150,16 +150,16 @@ @@ -243,8 +243,6 @@ const { startTime, endTime, reconciliationStatements, - openingBalance, - closingBalance, currentTimezoneOffsetMinutes, allAccountsMap, allCategoriesMap, @@ -252,8 +250,8 @@ const { exportFileName, displayStartDateTime, displayEndDateTime, - displayTotalOutflows, displayTotalInflows, + displayTotalOutflows, displayTotalBalance, displayOpeningBalance, displayClosingBalance, @@ -279,16 +277,16 @@ const countPerPage = ref(10); let rejectFunc: ((reason?: unknown) => void) | null = null; -const reconciliationStatementsTablePageOptions = computed(() => getTablePageOptions(reconciliationStatements.value?.length)); +const reconciliationStatementsTablePageOptions = computed(() => getTablePageOptions(reconciliationStatements.value?.transactions.length)); const totalPageCount = computed(() => { - if (!reconciliationStatements.value || reconciliationStatements.value.length < 1) { + if (!reconciliationStatements.value || !reconciliationStatements.value.transactions || reconciliationStatements.value.transactions.length < 1) { return 1; } let count = 0; - for (let i = 0; i < reconciliationStatements.value.length; i++) { + for (let i = 0; i < reconciliationStatements.value.transactions.length; i++) { count++; } @@ -339,7 +337,7 @@ function open(options: { accountId: string, startTime: number, endTime: number } accountId.value = options.accountId; startTime.value = options.startTime; endTime.value = options.endTime; - reconciliationStatements.value = []; + reconciliationStatements.value = undefined; currentPage.value = 1; countPerPage.value = 10; showState.value = true; @@ -355,9 +353,7 @@ function open(options: { accountId: string, startTime: number, endTime: number } endTime: options.endTime }); }).then(result => { - reconciliationStatements.value = result.transactions; - openingBalance.value = result.openingBalance; - closingBalance.value = result.closingBalance; + reconciliationStatements.value = result; loading.value = false; }).catch(error => { loading.value = false; @@ -381,9 +377,7 @@ function reload(): void { startTime: startTime.value, endTime: endTime.value }).then(result => { - reconciliationStatements.value = result.transactions; - openingBalance.value = result.openingBalance; - closingBalance.value = result.closingBalance; + reconciliationStatements.value = result; loading.value = false; }).catch(error => { loading.value = false; @@ -411,7 +405,7 @@ function addTransaction(): void { } function exportReconciliationStatements(fileType: KnownFileType): void { - if (!reconciliationStatements.value || reconciliationStatements.value.length < 1) { + if (!reconciliationStatements.value || !reconciliationStatements.value.transactions || reconciliationStatements.value.transactions.length < 1) { return; } diff --git a/src/views/mobile/accounts/ReconciliationStatementPage.vue b/src/views/mobile/accounts/ReconciliationStatementPage.vue index 2b01e458..fb0b9b36 100644 --- a/src/views/mobile/accounts/ReconciliationStatementPage.vue +++ b/src/views/mobile/accounts/ReconciliationStatementPage.vue @@ -51,7 +51,9 @@ - + @@ -283,8 +285,6 @@ const { startTime, endTime, reconciliationStatements, - openingBalance, - closingBalance, firstDayOfWeek, fiscalYearStart, currentTimezoneOffsetMinutes, @@ -293,8 +293,8 @@ const { currentAccount, displayStartDateTime, displayEndDateTime, - displayTotalOutflows, displayTotalInflows, + displayTotalOutflows, displayTotalBalance, displayOpeningBalance, displayClosingBalance, @@ -329,15 +329,15 @@ const displayEndTime = computed(() => formatUnixTimeToLongDateTime(endTi const allReconciliationStatementVirtualListItems = computed(() => { const ret: ReconciliationStatementVirtualListItem[] = []; - if (!reconciliationStatements.value || reconciliationStatements.value.length < 1) { + if (!reconciliationStatements.value || !reconciliationStatements.value.transactions || reconciliationStatements.value.transactions.length < 1) { return ret; } let index = 0; let lastDisplayDate: string | null = null; - for (let i = 0; i < reconciliationStatements.value.length; i++) { - const transaction = reconciliationStatements.value[i]; + for (let i = 0; i < reconciliationStatements.value.transactions.length; i++) { + const transaction = reconciliationStatements.value.transactions[i]; const displayDate = getDisplayDate(transaction); if (lastDisplayDate !== displayDate) { @@ -368,7 +368,7 @@ function init(): void { accountId.value = query['accountId'] || ''; startTime.value = defaultDateRange?.minTime || 0; endTime.value = defaultDateRange?.maxTime || 0; - reconciliationStatements.value = []; + reconciliationStatements.value = undefined; Promise.all([ accountsStore.loadAllAccounts({ force: false }), @@ -433,9 +433,7 @@ function reload(force: boolean): void { } loading.value = false; - reconciliationStatements.value = result.transactions; - openingBalance.value = result.openingBalance; - closingBalance.value = result.closingBalance; + reconciliationStatements.value = result; }).catch(error => { loading.value = false;