import { ref, computed } from 'vue'; import { defineStore } from 'pinia'; import { useSettingsStore } from './setting.ts'; import { useUserStore } from './user.ts'; import { useExchangeRatesStore } from './exchangeRates.ts'; import type { WritableStartEndTime } from '@/core/datetime.ts'; import { TimezoneTypeForStatistics } from '@/core/timezone.ts'; import type { TransactionAmountsRequestType, TransactionAmountsRequestParams, TransactionAmountsResponse, TransactionOverviewResponse } from '@/models/transaction.ts'; import { ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE } from '@/models/transaction.ts'; import { isNumber, isEquals } from '@/lib/common.ts'; import { getUnixTimeBeforeUnixTime, getTodayFirstUnixTime, getTodayLastUnixTime, getThisWeekFirstUnixTime, getThisWeekLastUnixTime, getThisMonthFirstUnixTime, getThisMonthLastUnixTime, getThisYearFirstUnixTime, getThisYearLastUnixTime } from '@/lib/datetime.ts'; import logger from '@/lib/logger.ts'; import services from '@/lib/services.ts'; interface TransactionDataRange extends Record { today: { startTime: number; endTime: number; }; thisWeek: { startTime: number; endTime: number; }; thisMonth: { startTime: number; endTime: number; }; thisYear: { startTime: number; endTime: number; }; lastMonth: { startTime: number; endTime: number; }; monthBeforeLastMonth: { startTime: number; endTime: number; }; monthBeforeLast2Months: { startTime: number; endTime: number; }; monthBeforeLast3Months: { startTime: number; endTime: number; }; monthBeforeLast4Months: { startTime: number; endTime: number; }; monthBeforeLast5Months: { startTime: number; endTime: number; }; monthBeforeLast6Months: { startTime: number; endTime: number; }; monthBeforeLast7Months: { startTime: number; endTime: number; }; monthBeforeLast8Months: { startTime: number; endTime: number; }; monthBeforeLast9Months: { startTime: number; endTime: number; }; monthBeforeLast10Months: { startTime: number; endTime: number; }; } interface TransactionOverviewOptions { loadLast11Months: boolean; } export const useOverviewStore = defineStore('overview', () => { const settingsStore = useSettingsStore(); const userStore = useUserStore(); const exchangeRatesStore = useExchangeRatesStore(); const transactionDataRange = ref(getTransactionDateRange()); const transactionOverviewOptions = ref({ loadLast11Months: false }); const transactionOverviewData = ref({}); const transactionOverviewStateInvalid = ref(true); const transactionOverview = computed(() => { const overviewData = transactionOverviewData.value; if (!overviewData || !overviewData.thisMonth) { return { thisMonth: { valid: false, incomeAmount: 0, expenseAmount: 0, incompleteIncomeAmount: false, incompleteExpenseAmount: false } } as TransactionOverviewResponse; } const finalOverviewData: TransactionOverviewResponse = {}; const defaultCurrency = userStore.currentUserDefaultCurrency; ALL_TRANSACTION_AMOUNTS_REQUEST_TYPE.forEach(field => { const item = overviewData[field]; if (!item) { return; } let totalIncomeAmount = 0; let totalExpenseAmount = 0; let hasUnCalculatedTotalIncome = false; let hasUnCalculatedTotalExpense = false; if (item.amounts) { for (let i = 0; i < item.amounts.length; i++) { const amount = item.amounts[i]; if (amount.currency !== defaultCurrency) { const incomeAmount = exchangeRatesStore.getExchangedAmount(amount.incomeAmount, amount.currency, defaultCurrency); const expenseAmount = exchangeRatesStore.getExchangedAmount(amount.expenseAmount, amount.currency, defaultCurrency); if (isNumber(incomeAmount)) { totalIncomeAmount += Math.floor(incomeAmount); } else { hasUnCalculatedTotalIncome = true; } if (isNumber(expenseAmount)) { totalExpenseAmount += Math.floor(expenseAmount); } else { hasUnCalculatedTotalExpense = true; } } else { totalIncomeAmount += amount.incomeAmount; totalExpenseAmount += amount.expenseAmount; } } } finalOverviewData[field] = { valid: true, incomeAmount: totalIncomeAmount, expenseAmount: totalExpenseAmount, incompleteIncomeAmount: hasUnCalculatedTotalIncome, incompleteExpenseAmount: hasUnCalculatedTotalExpense, amounts: item.amounts || [] }; }); return finalOverviewData; }); function getTransactionDateRange(): TransactionDataRange { const dateRange: TransactionDataRange = { today: { startTime: 0, endTime: 0 }, thisWeek: { startTime: 0, endTime: 0 }, thisMonth: { startTime: 0, endTime: 0 }, thisYear: { startTime: 0, endTime: 0 }, lastMonth: { startTime: 0, endTime: 0 }, monthBeforeLastMonth: { startTime: 0, endTime: 0 }, monthBeforeLast2Months: { startTime: 0, endTime: 0 }, monthBeforeLast3Months: { startTime: 0, endTime: 0 }, monthBeforeLast4Months: { startTime: 0, endTime: 0 }, monthBeforeLast5Months: { startTime: 0, endTime: 0 }, monthBeforeLast6Months: { startTime: 0, endTime: 0 }, monthBeforeLast7Months: { startTime: 0, endTime: 0 }, monthBeforeLast8Months: { startTime: 0, endTime: 0 }, monthBeforeLast9Months: { startTime: 0, endTime: 0 }, monthBeforeLast10Months: { startTime: 0, endTime: 0 } }; initTransactionDateRange(dateRange); return dateRange; } function initTransactionDateRange(dateRange: TransactionDataRange): void { dateRange.today.startTime = getTodayFirstUnixTime(); dateRange.today.endTime = getTodayLastUnixTime(); dateRange.thisWeek.startTime = getThisWeekFirstUnixTime(userStore.currentUserFirstDayOfWeek); dateRange.thisWeek.endTime = getThisWeekLastUnixTime(userStore.currentUserFirstDayOfWeek); dateRange.thisMonth.startTime = getThisMonthFirstUnixTime(); dateRange.thisMonth.endTime = getThisMonthLastUnixTime(); dateRange.thisYear.startTime = getThisYearFirstUnixTime(); dateRange.thisYear.endTime = getThisYearLastUnixTime(); dateRange.lastMonth.startTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 1, 'months'); dateRange.lastMonth.endTime = getUnixTimeBeforeUnixTime(getThisMonthFirstUnixTime(), 1, 'seconds'); dateRange.monthBeforeLastMonth.startTime = getUnixTimeBeforeUnixTime(dateRange.lastMonth.startTime, 1, 'months'); dateRange.monthBeforeLastMonth.endTime = getUnixTimeBeforeUnixTime(dateRange.lastMonth.startTime, 1, 'seconds'); dateRange.monthBeforeLast2Months.startTime = getUnixTimeBeforeUnixTime(dateRange.monthBeforeLastMonth.startTime, 1, 'months'); dateRange.monthBeforeLast2Months.endTime = getUnixTimeBeforeUnixTime(dateRange.monthBeforeLastMonth.startTime, 1, 'seconds'); for (let i = 3; i <= 10; i++) { dateRange[`monthBeforeLast${i}Months` as TransactionAmountsRequestType].startTime = getUnixTimeBeforeUnixTime(dateRange[`monthBeforeLast${i - 1}Months` as TransactionAmountsRequestType].startTime, 1, 'months'); dateRange[`monthBeforeLast${i}Months` as TransactionAmountsRequestType].endTime = getUnixTimeBeforeUnixTime(dateRange[`monthBeforeLast${i - 1}Months` as TransactionAmountsRequestType].startTime, 1, 'seconds'); } } function updateTransactionDateRange(): void { initTransactionDateRange(transactionDataRange.value); } function updateTransactionOverviewInvalidState(invalidState: boolean): void { transactionOverviewStateInvalid.value = invalidState; } function resetTransactionOverview(): void { updateTransactionDateRange(); transactionOverviewOptions.value.loadLast11Months = false; transactionOverviewData.value = {}; transactionOverviewStateInvalid.value = true; } function loadTransactionOverview({ force, loadLast11Months }: { force: boolean, loadLast11Months?: boolean }): Promise { let dateChanged = false; let rangeChanged = false; if (transactionDataRange.value.today.startTime !== getTodayFirstUnixTime()) { dateChanged = true; updateTransactionDateRange(); } if (loadLast11Months && !transactionOverviewOptions.value.loadLast11Months) { rangeChanged = true; } if (!dateChanged && !rangeChanged && !force && !transactionOverviewStateInvalid.value) { return new Promise((resolve) => { resolve(transactionOverviewData.value); }); } const requestParams: TransactionAmountsRequestParams = { useTransactionTimezone: settingsStore.appSettings.timezoneUsedForStatisticsInHomePage === TimezoneTypeForStatistics.TransactionTimezone.type, today: transactionDataRange.value.today, thisWeek: transactionDataRange.value.thisWeek, thisMonth: transactionDataRange.value.thisMonth, thisYear: transactionDataRange.value.thisYear }; if (loadLast11Months) { requestParams.lastMonth = transactionDataRange.value.lastMonth; requestParams.monthBeforeLastMonth = transactionDataRange.value.monthBeforeLastMonth; requestParams.monthBeforeLast2Months = transactionDataRange.value.monthBeforeLast2Months; requestParams.monthBeforeLast3Months = transactionDataRange.value.monthBeforeLast3Months; requestParams.monthBeforeLast4Months = transactionDataRange.value.monthBeforeLast4Months; requestParams.monthBeforeLast5Months = transactionDataRange.value.monthBeforeLast5Months; requestParams.monthBeforeLast6Months = transactionDataRange.value.monthBeforeLast6Months; requestParams.monthBeforeLast7Months = transactionDataRange.value.monthBeforeLast7Months; requestParams.monthBeforeLast8Months = transactionDataRange.value.monthBeforeLast8Months; requestParams.monthBeforeLast9Months = transactionDataRange.value.monthBeforeLast9Months; requestParams.monthBeforeLast10Months = transactionDataRange.value.monthBeforeLast10Months; } return new Promise((resolve, reject) => { services.getTransactionAmounts(requestParams).then(response => { const data = response.data; if (!data || !data.success || !data.result) { reject({ message: 'Unable to retrieve transaction overview' }); return; } if (transactionOverviewStateInvalid.value) { updateTransactionOverviewInvalidState(false); } if (force && data.result && isEquals(transactionOverviewData.value, data.result)) { reject({ message: 'Data is up to date', isUpToDate: true }); return; } transactionOverviewData.value = data.result; transactionOverviewOptions.value.loadLast11Months = !!loadLast11Months; resolve(data.result); }).catch(error => { if (force) { logger.error('failed to force load transaction overview', error); } else { logger.error('failed to load transaction overview', error); } if (error.response && error.response.data && error.response.data.errorMessage) { reject({ error: error.response.data }); } else if (!error.processed) { reject({ message: 'Unable to retrieve transaction overview' }); } else { reject(error); } }); }); } return { // states transactionDataRange, transactionOverviewOptions, transactionOverviewData, transactionOverviewStateInvalid, // computed states, transactionOverview, // functions updateTransactionOverviewInvalidState, resetTransactionOverview, loadTransactionOverview }; });