export reconciliation statements

This commit is contained in:
MaysWind
2025-07-26 00:58:38 +08:00
parent 3335533a18
commit 602f15fe2e
13 changed files with 162 additions and 3 deletions
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_エクスポートデータ",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_dados_estatísticos",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_dados_estatísticos",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_export_data",
"defaultExportStatisticsFileName": "ezBookkeeping_statistics_data",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_statistics_data",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_reconciliation_statements",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_reconciliation_statements",
"defaultImportDataMappingFileName": "ezBookkeeping_import_data_mapping",
"defaultImportReplaceRuleFileName": "ezBookkeeping_import_replace_rule"
},
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_导出数据",
"defaultExportStatisticsFileName": "ezBookkeeping_统计数据",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_统计数据",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_对账单",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_对账单",
"defaultImportDataMappingFileName": "ezBookkeeping_导入数据映射文件",
"defaultImportReplaceRuleFileName": "ezBookkeeping_导入替换规则文件"
},
+2
View File
@@ -122,6 +122,8 @@
"exportFilename": "ezBookkeeping_{nickname}_匯出資料",
"defaultExportStatisticsFileName": "ezBookkeeping_統計資料",
"exportStatisticsFileName": "ezBookkeeping_{nickname}_統計資料",
"defaultExportReconciliationStatementsFileName": "ezBookkeeping_對帳單",
"exportReconciliationStatementsFileName": "ezBookkeeping_{nickname}_對帳單",
"defaultImportDataMappingFileName": "ezBookkeeping_匯入資料對應檔案",
"defaultImportReplaceRuleFileName": "ezBookkeeping_匯入替換規則檔案"
},
@@ -7,21 +7,32 @@ import { useUserStore } from '@/stores/user.ts';
import { useAccountsStore } from '@/stores/account.ts';
import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import { KnownDateTimeFormat } from '@/core/datetime.ts';
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 {
replaceAll,
removeAll
} from '@/lib/common.ts';
import {
getUtcOffsetByUtcOffsetMinutes,
getTimezoneOffsetMinutes,
parseDateFromUnixTime,
formatUnixTime,
getUnixTime
} from '@/lib/datetime.ts';
export function useReconciliationStatementPageBase() {
const {
tt,
getCurrentDigitGroupingSymbol,
formatUnixTimeToLongDateTime,
formatAmount,
formatAmountWithCurrency
} = useI18n();
@@ -40,6 +51,18 @@ export function useReconciliationStatementPageBase() {
const currentTimezoneOffsetMinutes = computed<number>(() => getTimezoneOffsetMinutes(settingsStore.appSettings.timeZone));
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
const exportFileName = computed<string>(() => {
const nickname = userStore.currentUserNickname;
if (nickname) {
return tt('dataExport.exportReconciliationStatementsFileName', {
nickname: nickname
});
}
return tt('dataExport.defaultExportReconciliationStatementsFileName');
});
const allAccountsMap = computed<Record<string, Account>>(() => accountsStore.allAccountsMap);
const allCategoriesMap = computed<Record<string, TransactionCategory>>(() => transactionCategoriesStore.allTransactionCategoriesMap);
@@ -183,6 +206,91 @@ export function useReconciliationStatementPageBase() {
}
}
function getExportedData(fileType: KnownFileType): string {
let separator = ',';
if (fileType === KnownFileType.TSV) {
separator = '\t';
}
let isLiabilityAccount = false;
if (allAccountsMap.value[accountId.value]) {
isLiabilityAccount = allAccountsMap.value[accountId.value].isLiability;
}
const digitGroupingSymbol = getCurrentDigitGroupingSymbol();
const accountBalanceName = isLiabilityAccount ? 'Account Outstanding Balance' : 'Account Balance';
const header = [
tt('Transaction Time'),
tt('Type'),
tt('Category'),
tt('Amount'),
tt('Account'),
tt(accountBalanceName),
tt('Description')
].join(separator) + '\n';
const rows = reconciliationStatements.value.map(transaction => {
const transactionTime = getUnixTime(parseDateFromUnixTime(transaction.time, transaction.utcOffset, currentTimezoneOffsetMinutes.value));
let type = '';
let categoryName = allCategoriesMap.value[transaction.categoryId]?.name || '';
let displayAmount = removeAll(formatAmount(transaction.sourceAmount), digitGroupingSymbol);
let displayAccountName = allAccountsMap.value[transaction.sourceAccountId]?.name || '';
if (transaction.type === TransactionType.ModifyBalance) {
type = tt('Modify Balance');
categoryName = '-';
} else if (transaction.type === TransactionType.Income) {
type = tt('Income');
} else if (transaction.type === TransactionType.Expense) {
type = tt('Expense');
} else if (transaction.type === TransactionType.Transfer && transaction.destinationAccountId === accountId.value) {
type = tt('Transfer In');
displayAmount = removeAll(formatAmount(transaction.destinationAmount), digitGroupingSymbol);
} else if (transaction.type === TransactionType.Transfer && transaction.sourceAccountId === accountId.value) {
type = tt('Transfer Out');
} else if (transaction.type === TransactionType.Transfer) {
type = tt('Transfer');
} else {
type = tt('Unknown');
}
if (transaction.type === TransactionType.Transfer && allAccountsMap.value[transaction.destinationAccountId]) {
displayAccountName = displayAccountName + ' → ' + (allAccountsMap.value[transaction.destinationAccountId]?.name || '');
}
let displayAccountBalance = '';
if (isLiabilityAccount) {
displayAccountBalance = removeAll(formatAmount(-transaction.accountBalance), digitGroupingSymbol);
} else {
displayAccountBalance = removeAll(formatAmount(transaction.accountBalance), digitGroupingSymbol);
}
let description = transaction.comment || '';
if (fileType === KnownFileType.CSV) {
description = replaceAll(description, ',', ' ');
} else if (fileType === KnownFileType.TSV) {
description = replaceAll(description, '\t', ' ');
}
return [
formatUnixTime(transactionTime, KnownDateTimeFormat.DefaultDateTime.format),
type,
categoryName,
displayAmount,
displayAccountName,
displayAccountBalance,
description
].join(separator);
});
return header + rows.join('\n');
}
return {
// states
accountId,
@@ -194,6 +302,7 @@ export function useReconciliationStatementPageBase() {
// computed states
currentTimezoneOffsetMinutes,
defaultCurrency,
exportFileName,
allAccountsMap,
allCategoriesMap,
displayStartDateTime,
@@ -208,6 +317,7 @@ export function useReconciliationStatementPageBase() {
getDisplayTimezone,
getDisplaySourceAmount,
getDisplayDestinationAmount,
getDisplayAccountBalance
getDisplayAccountBalance,
getExportedData
};
}
@@ -15,6 +15,17 @@
<v-list-item :prepend-icon="mdiReceiptTextPlusOutline"
:title="tt('Add Transaction')"
@click="addTransaction()"></v-list-item>
<v-divider class="my-2"/>
<v-list-item :prepend-icon="mdiComma"
:disabled="!reconciliationStatements || reconciliationStatements.length < 1"
@click="exportReconciliationStatements(KnownFileType.CSV)">
<v-list-item-title>{{ tt('Export to CSV (Comma-separated values) File') }}</v-list-item-title>
</v-list-item>
<v-list-item :prepend-icon="mdiKeyboardTab"
:disabled="!reconciliationStatements || reconciliationStatements.length < 1"
@click="exportReconciliationStatements(KnownFileType.TSV)">
<v-list-item-title>{{ tt('Export to TSV (Tab-separated values) File') }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
@@ -196,12 +207,17 @@ import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts';
import { useTransactionsStore } from '@/stores/transaction.ts';
import { TransactionType } from '@/core/transaction.ts';
import { KnownFileType } from '@/core/file.ts';
import { Transaction, type TransactionReconciliationStatementResponseItem } from '@/models/transaction.ts';
import { startDownloadFile } from '@/lib/ui/common.ts';
import {
mdiArrowRight,
mdiDotsVertical,
mdiReceiptTextPlusOutline
mdiReceiptTextPlusOutline,
mdiComma,
mdiKeyboardTab
} from '@mdi/js';
type SnackBarType = InstanceType<typeof SnackBar>;
@@ -228,6 +244,7 @@ const {
currentTimezoneOffsetMinutes,
allAccountsMap,
allCategoriesMap,
exportFileName,
displayStartDateTime,
displayEndDateTime,
displayTotalOutflows,
@@ -239,7 +256,8 @@ const {
getDisplayTimezone,
getDisplaySourceAmount,
getDisplayDestinationAmount,
getDisplayAccountBalance
getDisplayAccountBalance,
getExportedData
} = useReconciliationStatementPageBase();
const accountsStore = useAccountsStore();
@@ -388,6 +406,15 @@ function addTransaction(): void {
});
}
function exportReconciliationStatements(fileType: KnownFileType): void {
if (!reconciliationStatements.value || reconciliationStatements.value.length < 1) {
return;
}
const exportedData = getExportedData(fileType);
startDownloadFile(fileType.formatFileName(exportFileName.value), fileType.createBlob(exportedData));
}
function showTransaction(transaction: TransactionReconciliationStatementResponseItem): void {
if (transaction.type === TransactionType.ModifyBalance) {
return;