mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-16 16:07:33 +08:00
329 lines
15 KiB
TypeScript
329 lines
15 KiB
TypeScript
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<string>('');
|
|
const startTime = ref<number>(0);
|
|
const endTime = ref<number>(0);
|
|
const reconciliationStatements = ref<TransactionReconciliationStatementResponseWithInfo | undefined>(undefined);
|
|
const chartDataDateAggregationType = ref<number>(ChartDateAggregationType.Day.type);
|
|
const timezoneUsedForDateRange = ref<number>(TimezoneTypeForStatistics.ApplicationTimezone.type);
|
|
|
|
const firstDayOfWeek = computed<WeekDayValue>(() => userStore.currentUserFirstDayOfWeek);
|
|
const fiscalYearStart = computed<number>(() => userStore.currentUserFiscalYearStart);
|
|
const defaultCurrency = computed<string>(() => userStore.currentUserDefaultCurrency);
|
|
|
|
const allChartTypes = computed<TypeAndDisplayName[]>(() => getAllAccountBalanceTrendChartTypes());
|
|
const allDateAggregationTypes = computed<TypeAndDisplayName[]>(() => getAllStatisticsDateAggregationTypesWithShortName(StatisticsAnalysisType.AssetTrends));
|
|
const allTimezoneTypesUsedForDateRange = computed<TypeAndDisplayName[]>(() => getAllTimezoneTypesUsedForStatistics());
|
|
|
|
const currentAccount = computed(() => allAccountsMap.value[accountId.value]);
|
|
const currentAccountCurrency = computed<string>(() => currentAccount.value?.currency ?? defaultCurrency.value);
|
|
const isCurrentLiabilityAccount = computed<boolean>(() => currentAccount.value?.isLiability ?? false);
|
|
|
|
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);
|
|
|
|
const displayStartDateTime = computed<string>(() => {
|
|
const dateTime = parseDateTimeFromUnixTime(startTime.value);
|
|
return formatDateTimeToLongDateTime(dateTime);
|
|
});
|
|
|
|
const displayEndDateTime = computed<string>(() => {
|
|
const dateTime = parseDateTimeFromUnixTime(endTime.value);
|
|
return formatDateTimeToLongDateTime(dateTime);
|
|
});
|
|
|
|
const displayTotalInflows = computed<string>(() => {
|
|
return formatAmountToLocalizedNumeralsWithCurrency(reconciliationStatements.value?.totalInflows ?? 0, currentAccountCurrency.value);
|
|
});
|
|
|
|
const displayTotalOutflows = computed<string>(() => {
|
|
return formatAmountToLocalizedNumeralsWithCurrency(reconciliationStatements.value?.totalOutflows ?? 0, currentAccountCurrency.value);
|
|
});
|
|
|
|
const displayTotalBalance = computed<string>(() => {
|
|
return formatAmountToLocalizedNumeralsWithCurrency((reconciliationStatements?.value?.totalInflows ?? 0) - (reconciliationStatements.value?.totalOutflows ?? 0), currentAccountCurrency.value);
|
|
});
|
|
|
|
const displayOpeningBalance = computed<string>(() => {
|
|
if (isCurrentLiabilityAccount.value) {
|
|
return formatAmountToLocalizedNumeralsWithCurrency(-(reconciliationStatements?.value?.openingBalance ?? 0), currentAccountCurrency.value);
|
|
} else {
|
|
return formatAmountToLocalizedNumeralsWithCurrency(reconciliationStatements?.value?.openingBalance ?? 0, currentAccountCurrency.value);
|
|
}
|
|
});
|
|
|
|
const displayClosingBalance = computed<string>(() => {
|
|
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
|
|
};
|
|
}
|