import { ref, computed } from 'vue'; import { useI18n } from '@/locales/helpers.ts'; import { useUserStore } from '@/stores/user.ts'; import { useAccountsStore } from '@/stores/account.ts'; import { useTransactionCategoriesStore } from '@/stores/transactionCategory.ts'; import type { TypeAndDisplayName } from '@/core/base.ts'; import type { WeekDayValue } from '@/core/datetime.ts'; import { TimezoneTypeForStatistics } from '@/core/timezone.ts'; import { TransactionType } from '@/core/transaction.ts'; import { StatisticsAnalysisType, ChartDateAggregationType } from '@/core/statistics.ts'; import { KnownFileType } from '@/core/file.ts'; import type { Account } from '@/models/account.ts'; import type { TransactionCategory } from '@/models/transaction_category.ts'; import type { TransactionReconciliationStatementResponse, TransactionReconciliationStatementResponseItemWithInfo, TransactionReconciliationStatementResponseWithInfo } from '@/models/transaction.ts'; import { replaceAll } from '@/lib/common.ts'; import { getUtcOffsetByUtcOffsetMinutes, getTimezoneOffsetMinutes, parseDateTimeFromUnixTime, parseDateTimeFromUnixTimeWithTimezoneOffset } from '@/lib/datetime.ts'; export function useReconciliationStatementPageBase() { const { tt, getAllAccountBalanceTrendChartTypes, getAllStatisticsDateAggregationTypesWithShortName, getAllTimezoneTypesUsedForStatistics, formatDateTimeToLongDateTime, formatDateTimeToLongDate, formatDateTimeToShortTime, formatDateTimeToGregorianDefaultDateTime, formatAmountToWesternArabicNumeralsWithoutDigitGrouping, formatAmountToLocalizedNumeralsWithCurrency } = useI18n(); const userStore = useUserStore(); const accountsStore = useAccountsStore(); const transactionCategoriesStore = useTransactionCategoriesStore(); const accountId = ref(''); const startTime = ref(0); const endTime = ref(0); const reconciliationStatements = ref(undefined); const chartDataDateAggregationType = ref(ChartDateAggregationType.Day.type); const timezoneUsedForDateRange = ref(TimezoneTypeForStatistics.ApplicationTimezone.type); const firstDayOfWeek = computed(() => userStore.currentUserFirstDayOfWeek); const fiscalYearStart = computed(() => userStore.currentUserFiscalYearStart); const defaultCurrency = computed(() => userStore.currentUserDefaultCurrency); const allChartTypes = computed(() => getAllAccountBalanceTrendChartTypes()); const allDateAggregationTypes = computed(() => getAllStatisticsDateAggregationTypesWithShortName(StatisticsAnalysisType.AssetTrends)); const allTimezoneTypesUsedForDateRange = computed(() => getAllTimezoneTypesUsedForStatistics()); const currentAccount = computed(() => allAccountsMap.value[accountId.value]); const currentAccountCurrency = computed(() => currentAccount.value?.currency ?? defaultCurrency.value); const isCurrentLiabilityAccount = computed(() => currentAccount.value?.isLiability ?? false); const exportFileName = computed(() => { const nickname = userStore.currentUserNickname; if (nickname) { return tt('dataExport.exportReconciliationStatementsFileName', { nickname: nickname }); } return tt('dataExport.defaultExportReconciliationStatementsFileName'); }); const allAccountsMap = computed>(() => accountsStore.allAccountsMap); const allCategoriesMap = computed>(() => transactionCategoriesStore.allTransactionCategoriesMap); const displayStartDateTime = computed(() => { const dateTime = parseDateTimeFromUnixTime(startTime.value); return formatDateTimeToLongDateTime(dateTime); }); const displayEndDateTime = computed(() => { const dateTime = parseDateTimeFromUnixTime(endTime.value); return formatDateTimeToLongDateTime(dateTime); }); const displayTotalInflows = computed(() => { return formatAmountToLocalizedNumeralsWithCurrency(reconciliationStatements.value?.totalInflows ?? 0, currentAccountCurrency.value); }); const displayTotalOutflows = computed(() => { return formatAmountToLocalizedNumeralsWithCurrency(reconciliationStatements.value?.totalOutflows ?? 0, currentAccountCurrency.value); }); const displayTotalBalance = computed(() => { return formatAmountToLocalizedNumeralsWithCurrency((reconciliationStatements?.value?.totalInflows ?? 0) - (reconciliationStatements.value?.totalOutflows ?? 0), currentAccountCurrency.value); }); const displayOpeningBalance = computed(() => { if (isCurrentLiabilityAccount.value) { return formatAmountToLocalizedNumeralsWithCurrency(-(reconciliationStatements?.value?.openingBalance ?? 0), currentAccountCurrency.value); } else { return formatAmountToLocalizedNumeralsWithCurrency(reconciliationStatements?.value?.openingBalance ?? 0, currentAccountCurrency.value); } }); const displayClosingBalance = computed(() => { if (isCurrentLiabilityAccount.value) { return formatAmountToLocalizedNumeralsWithCurrency(-(reconciliationStatements?.value?.closingBalance ?? 0), currentAccountCurrency.value); } else { return formatAmountToLocalizedNumeralsWithCurrency(reconciliationStatements?.value?.closingBalance ?? 0, currentAccountCurrency.value); } }); function setReconciliationStatements(response: TransactionReconciliationStatementResponse | undefined) { if (!response) { reconciliationStatements.value = undefined; return; } const responseWithInfo: TransactionReconciliationStatementResponseWithInfo = { transactions: response.transactions.map(transaction => { const transactionWithInfo: TransactionReconciliationStatementResponseItemWithInfo = { ...transaction, sourceAccount: allAccountsMap.value[transaction.sourceAccountId], sourceAccountName: allAccountsMap.value[transaction.sourceAccountId]?.name || '', destinationAccount: transaction.destinationAccountId && transaction.destinationAccountId !== '0' ? allAccountsMap.value[transaction.destinationAccountId] : undefined, category: allCategoriesMap.value[transaction.categoryId], categoryName: allCategoriesMap.value[transaction.categoryId]?.name || '' }; return transactionWithInfo; }), totalInflows: response.totalInflows, totalOutflows: response.totalOutflows, openingBalance: response.openingBalance, closingBalance: response.closingBalance }; reconciliationStatements.value = responseWithInfo; } function getDisplayTransactionType(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { if (transaction.type === TransactionType.ModifyBalance) { return tt('Modify Balance'); } else if (transaction.type === TransactionType.Income) { return tt('Income'); } else if (transaction.type === TransactionType.Expense) { return tt('Expense'); } else if (transaction.type === TransactionType.Transfer && transaction.destinationAccountId === accountId.value) { return tt('Transfer In'); } else if (transaction.type === TransactionType.Transfer && transaction.sourceAccountId === accountId.value) { return tt('Transfer Out'); } else if (transaction.type === TransactionType.Transfer) { return tt('Transfer'); } else { return tt('Unknown'); } } function getDisplayDateTime(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset); return formatDateTimeToLongDateTime(dateTime); } function getDisplayDate(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset); return formatDateTimeToLongDate(dateTime); } function getDisplayTime(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { const dateTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset); return formatDateTimeToShortTime(dateTime); } function isSameAsDefaultTimezoneOffsetMinutes(transaction: TransactionReconciliationStatementResponseItemWithInfo): boolean { return transaction.utcOffset === getTimezoneOffsetMinutes(transaction.time); } function getDisplayTimezone(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { return `UTC${getUtcOffsetByUtcOffsetMinutes(transaction.utcOffset)}`; } function getDisplaySourceAmount(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { const currency = transaction.sourceAccount?.currency ?? defaultCurrency.value; return formatAmountToLocalizedNumeralsWithCurrency(transaction.sourceAmount, currency); } function getDisplayDestinationAmount(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { const currency = transaction.destinationAccount?.currency ?? defaultCurrency.value; return formatAmountToLocalizedNumeralsWithCurrency(transaction.destinationAmount, currency); } function getDisplayAccountBalance(transaction: TransactionReconciliationStatementResponseItemWithInfo): string { let currency = defaultCurrency.value; let isLiabilityAccount = false; if (transaction.type === TransactionType.Transfer && transaction.destinationAccountId === accountId.value) { if (transaction.destinationAccount) { currency = transaction.destinationAccount.currency; isLiabilityAccount = transaction.destinationAccount.isLiability; } } else if (transaction.sourceAccount) { currency = transaction.sourceAccount.currency; isLiabilityAccount = transaction.sourceAccount.isLiability; } if (isLiabilityAccount) { return formatAmountToLocalizedNumeralsWithCurrency(-transaction.accountClosingBalance, currency); } else { return formatAmountToLocalizedNumeralsWithCurrency(transaction.accountClosingBalance, currency); } } function getExportedData(fileType: KnownFileType): string { let separator = ','; if (fileType === KnownFileType.TSV) { separator = '\t'; } const accountBalanceName = isCurrentLiabilityAccount.value ? '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 transactions = reconciliationStatements.value?.transactions ?? []; const rows = transactions.map(transaction => { const transactionTime = parseDateTimeFromUnixTimeWithTimezoneOffset(transaction.time, transaction.utcOffset); const type = getDisplayTransactionType(transaction); let categoryName = transaction.categoryName; let displayAmount = formatAmountToWesternArabicNumeralsWithoutDigitGrouping(transaction.sourceAmount); let displayAccountName = transaction.sourceAccountName; if (transaction.type === TransactionType.ModifyBalance) { categoryName = tt('Modify Balance'); } else if (transaction.type === TransactionType.Transfer && transaction.destinationAccountId === accountId.value) { displayAmount = formatAmountToWesternArabicNumeralsWithoutDigitGrouping(transaction.destinationAmount); } if (transaction.type === TransactionType.Transfer && transaction.destinationAccount) { displayAccountName = displayAccountName + ' → ' + (transaction.destinationAccount?.name || ''); } let displayAccountBalance = ''; if (isCurrentLiabilityAccount.value) { displayAccountBalance = formatAmountToWesternArabicNumeralsWithoutDigitGrouping(-transaction.accountClosingBalance); } else { displayAccountBalance = formatAmountToWesternArabicNumeralsWithoutDigitGrouping(transaction.accountClosingBalance); } let description = transaction.comment || ''; if (fileType === KnownFileType.CSV) { description = replaceAll(description, ',', ' '); } else if (fileType === KnownFileType.TSV) { description = replaceAll(description, '\t', ' '); } return [ formatDateTimeToGregorianDefaultDateTime(transactionTime), type, categoryName, displayAmount, displayAccountName, displayAccountBalance, description ].join(separator); }); return header + rows.join('\n'); } return { // states accountId, startTime, endTime, reconciliationStatements, chartDataDateAggregationType, timezoneUsedForDateRange, // computed states firstDayOfWeek, fiscalYearStart, defaultCurrency, allChartTypes, allDateAggregationTypes, allTimezoneTypesUsedForDateRange, currentAccount, currentAccountCurrency, isCurrentLiabilityAccount, exportFileName, displayStartDateTime, displayEndDateTime, displayTotalInflows, displayTotalOutflows, displayTotalBalance, displayOpeningBalance, displayClosingBalance, // functions setReconciliationStatements, getDisplayTransactionType, getDisplayDateTime, getDisplayDate, getDisplayTime, isSameAsDefaultTimezoneOffsetMinutes, getDisplayTimezone, getDisplaySourceAmount, getDisplayDestinationAmount, getDisplayAccountBalance, getExportedData }; }