From ea17994c6c14176a9bd70b299062888ff6844967 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Tue, 22 Jul 2025 01:04:29 +0800 Subject: [PATCH] show opening / closing balance in reconciliation statement dialog --- pkg/api/transactions.go | 6 +- pkg/models/transaction.go | 4 +- pkg/services/transactions.go | 12 +-- src/locales/de.json | 3 + src/locales/en.json | 3 + src/locales/es.json | 3 + src/locales/it.json | 3 + src/locales/ja.json | 3 + src/locales/pt_BR.json | 3 + src/locales/ru.json | 3 + src/locales/uk.json | 3 + src/locales/vi.json | 3 + src/locales/zh_Hans.json | 3 + src/locales/zh_Hant.json | 3 + src/models/transaction.ts | 2 + .../ReconciliationStatementPageBase.ts | 81 ++++++++++++++----- .../dialogs/ReconciliationStatementDialog.vue | 73 ++++++++++++++--- 17 files changed, 172 insertions(+), 39 deletions(-) diff --git a/pkg/api/transactions.go b/pkg/api/transactions.go index bacad4ab..6e56e3d7 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, err := a.transactions.GetAllTransactionsWithAccountBalanceByMaxTime(c, uid, pageCountForAccountStatement, maxTransactionTime, minTransactionTime, reconciliationStatementRequest.AccountId) + transactionsWithAccountBalance, openingBalance, closingBalance, err := a.transactions.GetAllTransactionsWithAccountBalanceByMaxTime(c, uid, pageCountForAccountStatement, maxTransactionTime, minTransactionTime, reconciliationStatementRequest.AccountId) 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()) @@ -383,7 +383,9 @@ func (a *TransactionsApi) TransactionReconciliationStatementHandler(c *core.WebC } reconciliationStatementResp := &models.TransactionReconciliationStatementResponse{ - Transactions: responseItems, + Transactions: responseItems, + OpeningBalance: openingBalance, + ClosingBalance: closingBalance, } return reconciliationStatementResp, nil diff --git a/pkg/models/transaction.go b/pkg/models/transaction.go index f996bf59..e8396a27 100644 --- a/pkg/models/transaction.go +++ b/pkg/models/transaction.go @@ -343,7 +343,9 @@ type TransactionReconciliationStatementResponseItem struct { // TransactionReconciliationStatementResponse represents the response of all transaction reconciliation statement response type TransactionReconciliationStatementResponse struct { - Transactions []*TransactionReconciliationStatementResponseItem `json:"transactions"` + Transactions []*TransactionReconciliationStatementResponseItem `json:"transactions"` + OpeningBalance int64 `json:"openingBalance"` + ClosingBalance int64 `json:"closingBalance"` } // TransactionStatisticResponse represents transaction statistic response diff --git a/pkg/services/transactions.go b/pkg/services/transactions.go index 097da436..1e419dff 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, error) { +func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c core.Context, uid int64, pageCount int32, maxTransactionTime int64, minTransactionTime int64, accountId int64) ([]*models.TransactionWithAccountBalance, 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, err + return nil, 0, 0, err } allTransactions = append(allTransactions, transactions...) @@ -135,9 +135,10 @@ func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c cor allTransactionsAndAccountBalance := make([]*models.TransactionWithAccountBalance, 0, len(allTransactions)) if len(allTransactions) < 1 { - return allTransactionsAndAccountBalance, nil + return allTransactionsAndAccountBalance, 0, 0, nil } + openingBalance := int64(0) accumulatedBalance := int64(0) for i := len(allTransactions) - 1; i >= 0; i-- { @@ -155,10 +156,11 @@ 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, errs.ErrTransactionTypeInvalid + return nil, 0, 0, errs.ErrTransactionTypeInvalid } if transaction.TransactionTime < minTransactionTime { + openingBalance = accumulatedBalance continue } @@ -170,7 +172,7 @@ func (s *TransactionService) GetAllTransactionsWithAccountBalanceByMaxTime(c cor allTransactionsAndAccountBalance = append(allTransactionsAndAccountBalance, transactionsAndAccountBalance) } - return allTransactionsAndAccountBalance, nil + return allTransactionsAndAccountBalance, openingBalance, accumulatedBalance, nil } // GetTransactionsByMaxTime returns transactions before given time diff --git a/src/locales/de.json b/src/locales/de.json index ba86e2fa..ce090893 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -1830,6 +1830,9 @@ "Total Outflows": "Total Outflows", "Total Inflows": "Total Inflows", "Total Balance": "Gesamtsaldo", + "Total Transactions": "Total Transactions", + "Opening Balance": "Opening Balance", + "Closing Balance": "Closing Balance", "Expense By Account": "Ausgaben nach Konto", "Expense By Primary Category": "Ausgaben nach Primärkategorie", "Expense By Secondary Category": "Ausgaben nach Sekundärkategorie", diff --git a/src/locales/en.json b/src/locales/en.json index 3cd44e8b..1e636624 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1830,6 +1830,9 @@ "Total Outflows": "Total Outflows", "Total Inflows": "Total Inflows", "Total Balance": "Total Balance", + "Total Transactions": "Total Transactions", + "Opening Balance": "Opening Balance", + "Closing Balance": "Closing Balance", "Expense By Account": "Expense By Account", "Expense By Primary Category": "Expense By Primary Category", "Expense By Secondary Category": "Expense By Secondary Category", diff --git a/src/locales/es.json b/src/locales/es.json index 86d39991..b67cc86a 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -1830,6 +1830,9 @@ "Total Outflows": "Total Outflows", "Total Inflows": "Total Inflows", "Total Balance": "Saldo total", + "Total Transactions": "Total Transactions", + "Opening Balance": "Opening Balance", + "Closing Balance": "Closing Balance", "Expense By Account": "Gasto por cuenta", "Expense By Primary Category": "Gasto por categoría primaria", "Expense By Secondary Category": "Gasto por categoría secundaria", diff --git a/src/locales/it.json b/src/locales/it.json index 80d2ca94..ec882e70 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -1830,6 +1830,9 @@ "Total Outflows": "Total Outflows", "Total Inflows": "Total Inflows", "Total Balance": "Saldo totale", + "Total Transactions": "Total Transactions", + "Opening Balance": "Opening Balance", + "Closing Balance": "Closing Balance", "Expense By Account": "Spesa per conto", "Expense By Primary Category": "Spesa per categoria principale", "Expense By Secondary Category": "Spesa per categoria secondaria", diff --git a/src/locales/ja.json b/src/locales/ja.json index 93dedf90..10c17d43 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -1830,6 +1830,9 @@ "Total Outflows": "Total Outflows", "Total Inflows": "Total Inflows", "Total Balance": "残高合計", + "Total Transactions": "Total Transactions", + "Opening Balance": "Opening Balance", + "Closing Balance": "Closing Balance", "Expense By Account": "口座別の支出", "Expense By Primary Category": "一次カテゴリ別の支出", "Expense By Secondary Category": "二次カテゴリ別の支出", diff --git a/src/locales/pt_BR.json b/src/locales/pt_BR.json index 8234cf8c..95a8199e 100644 --- a/src/locales/pt_BR.json +++ b/src/locales/pt_BR.json @@ -1830,6 +1830,9 @@ "Total Outflows": "Total Outflows", "Total Inflows": "Total Inflows", "Total Balance": "Saldo Total", + "Total Transactions": "Total Transactions", + "Opening Balance": "Opening Balance", + "Closing Balance": "Closing Balance", "Expense By Account": "Despesa por Conta", "Expense By Primary Category": "Despesa por Categoria Primária", "Expense By Secondary Category": "Despesa por Categoria Secundária", diff --git a/src/locales/ru.json b/src/locales/ru.json index 29de3a70..52d4680c 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1830,6 +1830,9 @@ "Total Outflows": "Total Outflows", "Total Inflows": "Total Inflows", "Total Balance": "Общий баланс", + "Total Transactions": "Total Transactions", + "Opening Balance": "Opening Balance", + "Closing Balance": "Closing Balance", "Expense By Account": "Расходы по счетам", "Expense By Primary Category": "Расходы по основной категории", "Expense By Secondary Category": "Расходы по вторичной категории", diff --git a/src/locales/uk.json b/src/locales/uk.json index 1be90c25..6177ffe9 100644 --- a/src/locales/uk.json +++ b/src/locales/uk.json @@ -1830,6 +1830,9 @@ "Total Outflows": "Total Outflows", "Total Inflows": "Total Inflows", "Total Balance": "Загальний баланс", + "Total Transactions": "Total Transactions", + "Opening Balance": "Opening Balance", + "Closing Balance": "Closing Balance", "Expense By Account": "Витрати за рахунками", "Expense By Primary Category": "Витрати за основними категоріями", "Expense By Secondary Category": "Витрати за другорядними категоріями", diff --git a/src/locales/vi.json b/src/locales/vi.json index 8d9368f7..d5b075a9 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -1830,6 +1830,9 @@ "Total Outflows": "Total Outflows", "Total Inflows": "Total Inflows", "Total Balance": "Tổng số dư", + "Total Transactions": "Total Transactions", + "Opening Balance": "Opening Balance", + "Closing Balance": "Closing Balance", "Expense By Account": "Chi phí theo tài khoản", "Expense By Primary Category": "Chi phí theo danh mục chính", "Expense By Secondary Category": "Chi phí theo danh mục phụ", diff --git a/src/locales/zh_Hans.json b/src/locales/zh_Hans.json index d726db96..f3fdbc99 100644 --- a/src/locales/zh_Hans.json +++ b/src/locales/zh_Hans.json @@ -1830,6 +1830,9 @@ "Total Outflows": "总流出", "Total Inflows": "总流入", "Total Balance": "总结余", + "Total Transactions": "总交易数", + "Opening Balance": "期初余额", + "Closing Balance": "期末余额", "Expense By Account": "账户支出", "Expense By Primary Category": "一级分类支出", "Expense By Secondary Category": "二级分类支出", diff --git a/src/locales/zh_Hant.json b/src/locales/zh_Hant.json index b197e2cb..0aae9c69 100644 --- a/src/locales/zh_Hant.json +++ b/src/locales/zh_Hant.json @@ -1830,6 +1830,9 @@ "Total Outflows": "總流出", "Total Inflows": "總流入", "Total Balance": "總結餘", + "Total Transactions": "總交易數", + "Opening Balance": "期初餘額", + "Closing Balance": "期末餘額", "Expense By Account": "帳戶支出", "Expense By Primary Category": "一級分類支出", "Expense By Secondary Category": "二級分類支出", diff --git a/src/models/transaction.ts b/src/models/transaction.ts index 15d5c325..a7dc787f 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 openingBalance: number; + readonly closingBalance: number; } export interface TransactionPageWrapper { diff --git a/src/views/base/transactions/ReconciliationStatementPageBase.ts b/src/views/base/transactions/ReconciliationStatementPageBase.ts index 5d8c9847..4a3263b6 100644 --- a/src/views/base/transactions/ReconciliationStatementPageBase.ts +++ b/src/views/base/transactions/ReconciliationStatementPageBase.ts @@ -34,6 +34,8 @@ export function useReconciliationStatementPageBase() { const startTime = ref(0); const endTime = ref(0); const reconciliationStatements = ref([]); + const openingBalance = ref(0); + const closingBalance = ref(0); const currentTimezoneOffsetMinutes = computed(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone)); const defaultCurrency = computed(() => userStore.currentUserDefaultCurrency); @@ -41,15 +43,17 @@ export function useReconciliationStatementPageBase() { const allAccountsMap = computed>(() => accountsStore.allAccountsMap); const allCategoriesMap = computed>(() => transactionCategoriesStore.allTransactionCategoriesMap); - const displayStartDateTime = computed(() => { - return formatUnixTimeToLongDateTime(startTime.value); + const accountCurrency = computed(() => { + let currency = defaultCurrency.value; + + if (allAccountsMap.value[accountId.value]) { + currency = allAccountsMap.value[accountId.value].currency; + } + + return currency; }); - const displayEndDateTime = computed(() => { - return formatUnixTimeToLongDateTime(endTime.value); - }); - - const displayTotalOutflows = computed(() => { + const totalOutflows = computed(() => { let totalOutflows = 0; for (let i = 0; i < reconciliationStatements.value.length; i++) { @@ -62,16 +66,10 @@ export function useReconciliationStatementPageBase() { } } - let currency = defaultCurrency.value; - - if (allAccountsMap.value[accountId.value]) { - currency = allAccountsMap.value[accountId.value].currency; - } - - return formatAmountWithCurrency(totalOutflows, currency); + return totalOutflows; }); - const displayTotalInflows = computed(() => { + const totalInflows = computed(() => { let totalInflows = 0; for (let i = 0; i < reconciliationStatements.value.length; i++) { @@ -84,13 +82,55 @@ export function useReconciliationStatementPageBase() { } } - let currency = defaultCurrency.value; + return totalInflows; + }); + + const displayStartDateTime = computed(() => { + return formatUnixTimeToLongDateTime(startTime.value); + }); + + const displayEndDateTime = computed(() => { + return formatUnixTimeToLongDateTime(endTime.value); + }); + + const displayTotalOutflows = computed(() => { + return formatAmountWithCurrency(totalOutflows.value, accountCurrency.value); + }); + + const displayTotalInflows = computed(() => { + return formatAmountWithCurrency(totalInflows.value, accountCurrency.value); + }); + + const displayTotalBalance = computed(() => { + return formatAmountWithCurrency(totalInflows.value - totalOutflows.value, accountCurrency.value); + }); + + const displayOpeningBalance = computed(() => { + let isLiabilityAccount = false; if (allAccountsMap.value[accountId.value]) { - currency = allAccountsMap.value[accountId.value].currency; + isLiabilityAccount = allAccountsMap.value[accountId.value].isLiability; } - return formatAmountWithCurrency(totalInflows, currency); + if (isLiabilityAccount) { + return formatAmountWithCurrency(-openingBalance.value, accountCurrency.value); + } else { + return formatAmountWithCurrency(openingBalance.value, accountCurrency.value); + } + }); + + const displayClosingBalance = computed(() => { + let isLiabilityAccount = false; + + if (allAccountsMap.value[accountId.value]) { + isLiabilityAccount = allAccountsMap.value[accountId.value].isLiability; + } + + if (isLiabilityAccount) { + return formatAmountWithCurrency(-closingBalance.value, accountCurrency.value); + } else { + return formatAmountWithCurrency(closingBalance.value, accountCurrency.value); + } }); function getDisplayDateTime(transaction: TransactionReconciliationStatementResponseItem): string { @@ -149,6 +189,8 @@ export function useReconciliationStatementPageBase() { startTime, endTime, reconciliationStatements, + openingBalance, + closingBalance, // computed states currentTimezoneOffsetMinutes, defaultCurrency, @@ -158,6 +200,9 @@ export function useReconciliationStatementPageBase() { displayEndDateTime, displayTotalOutflows, displayTotalInflows, + displayTotalBalance, + displayOpeningBalance, + displayClosingBalance, // functions getDisplayDateTime, getDisplayTimezone, diff --git a/src/views/desktop/accounts/list/dialogs/ReconciliationStatementDialog.vue b/src/views/desktop/accounts/list/dialogs/ReconciliationStatementDialog.vue index 2b3a2072..fe6c647c 100644 --- a/src/views/desktop/accounts/list/dialogs/ReconciliationStatementDialog.vue +++ b/src/views/desktop/accounts/list/dialogs/ReconciliationStatementDialog.vue @@ -9,14 +9,61 @@ + +
+
+ {{ tt('Opening Balance') }} + + + + + {{ displayOpeningBalance }} + + {{ tt('Closing Balance') }} + + + + + {{ displayClosingBalance }} + +
+ +
+ {{ tt('Total Inflows') }} + + + + + {{ displayTotalInflows }} + + {{ tt('Total Outflows') }} + + + + + {{ displayTotalOutflows }} + + {{ tt('Total Balance') }} + + + + + {{ displayTotalBalance }} + +
+
+ {{ getDisplayAccountBalance(item) }}