import { computed } from 'vue'; import { useI18n } from '@/locales/helpers.ts'; import { type UnixTimeRange, type YearUnixTime, type YearQuarterUnixTime, type YearMonthUnixTime, YearMonthDayUnixTime, } from '@/core/datetime.ts'; import type { FiscalYearUnixTime } from '@/core/fiscalyear.ts'; import { ChartDateAggregationType } from '@/core/statistics.ts'; import type { AccountInfoResponse } from '@/models/account.ts'; import type { TransactionReconciliationStatementResponseItem } from '@/models/transaction.ts'; import { isDefined, isArray } from '@/lib/common.ts'; import { sumAmounts } from '@/lib/numeral.ts'; import { getGregorianCalendarYearAndMonthFromUnixTime, getYearFirstUnixTimeBySpecifiedUnixTime, getQuarterFirstUnixTimeBySpecifiedUnixTime, getMonthFirstUnixTimeBySpecifiedUnixTime, getDayFirstUnixTimeBySpecifiedUnixTime, getAllDaysStartAndEndUnixTimes, getFiscalYearStartUnixTime } from '@/lib/datetime.ts'; import { getAllDateRangesByYearMonthRange } from '@/lib/statistics.ts'; export interface AccountBalanceUnixTimeAndBalanceRange extends UnixTimeRange { minUnixTimeOpeningBalance: number; minUnixTimeClosingBalance: number; maxUnixTimeClosingBalance: number; } export interface AccountBalanceTrendsChartItem { displayDate: string; openingBalance: number; closingBalance: number; minimumBalance: number; maximumBalance: number; medianBalance: number; averageBalance: number; } export interface CommonAccountBalanceTrendsChartProps { items: TransactionReconciliationStatementResponseItem[] | undefined; dateAggregationType?: number; fiscalYearStart: number; account: AccountInfoResponse; } export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTrendsChartProps) { const { getCalendarShortYearFromUnixTime, getCalendarShortYearMonthFromUnixTime, getCalendarYearQuarterFromUnixTime, getCalendarFiscalYearFromUnixTime, formatUnixTimeToShortDate } = useI18n(); const dataDateRange = computed(() => { if (!props.items || props.items.length < 1) { return null; } let minUnixTime = Number.MAX_SAFE_INTEGER, maxUnixTime = 0; let minUnixTimeOpeningBalance = 0; let minUnixTimeClosingBalance = 0; let maxUnixTimeClosingBalance = 0; for (let i = 0; i < props.items.length; i++) { const item = props.items[i]; if (item.time < minUnixTime) { minUnixTime = item.time; minUnixTimeOpeningBalance = item.accountOpeningBalance; minUnixTimeClosingBalance = item.accountClosingBalance; } if (item.time > maxUnixTime) { maxUnixTime = item.time; maxUnixTimeClosingBalance = item.accountClosingBalance; } } if (minUnixTime >= Number.MAX_SAFE_INTEGER || maxUnixTime <= 0) { return null; } return { minUnixTime: minUnixTime, maxUnixTime: maxUnixTime, minUnixTimeOpeningBalance: minUnixTimeOpeningBalance, minUnixTimeClosingBalance: minUnixTimeClosingBalance, maxUnixTimeClosingBalance: maxUnixTimeClosingBalance }; }); const allDateRanges = computed(() => { if (!dataDateRange.value) { return []; } if (!isDefined(props.dateAggregationType)) { return getAllDaysStartAndEndUnixTimes(dataDateRange.value.minUnixTime, dataDateRange.value.maxUnixTime); } else { const startYearMonth = getGregorianCalendarYearAndMonthFromUnixTime(dataDateRange.value.minUnixTime); const endYearMonth = getGregorianCalendarYearAndMonthFromUnixTime(dataDateRange.value.maxUnixTime); return getAllDateRangesByYearMonthRange(startYearMonth, endYearMonth, props.fiscalYearStart, props.dateAggregationType); } }); const allDataItems = computed(() => { const ret: AccountBalanceTrendsChartItem[] = []; if (!dataDateRange.value || !allDateRanges.value || allDateRanges.value.length < 1 || !props.items || props.items.length < 1) { return ret; } const dayDataItemsMap: Record = {}; for (let i = 0; i < props.items.length; i++) { const dateItem = props.items[i]; let dateRangeMinUnixTime = 0; if (props.dateAggregationType === ChartDateAggregationType.Year.type) { dateRangeMinUnixTime = getYearFirstUnixTimeBySpecifiedUnixTime(dateItem.time); } else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) { dateRangeMinUnixTime = getFiscalYearStartUnixTime(dateItem.time, props.fiscalYearStart); } else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) { dateRangeMinUnixTime = getQuarterFirstUnixTimeBySpecifiedUnixTime(dateItem.time); } else if (props.dateAggregationType === ChartDateAggregationType.Month.type) { dateRangeMinUnixTime = getMonthFirstUnixTimeBySpecifiedUnixTime(dateItem.time); } else { dateRangeMinUnixTime = getDayFirstUnixTimeBySpecifiedUnixTime(dateItem.time); } const dataItems: TransactionReconciliationStatementResponseItem[] = dayDataItemsMap[dateRangeMinUnixTime] || []; dataItems.push(dateItem); dayDataItemsMap[dateRangeMinUnixTime] = dataItems; } let lastOpeningBalance = dataDateRange.value.minUnixTimeOpeningBalance; let lastClosingBalance = dataDateRange.value.minUnixTimeClosingBalance; let lastMinimumBalance = lastClosingBalance; let lastMaximumBalance = lastClosingBalance; let lastMedianBalance = lastClosingBalance; let lastAverageBalance = lastClosingBalance; for (let i = 0; i < allDateRanges.value.length; i++) { const dateRange = allDateRanges.value[i]; const dataItems = dayDataItemsMap[dateRange.minUnixTime]; let displayDate = ''; if (props.dateAggregationType === ChartDateAggregationType.Year.type) { displayDate = getCalendarShortYearFromUnixTime(dateRange.minUnixTime); } else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) { displayDate = getCalendarFiscalYearFromUnixTime(dateRange.minUnixTime); } else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) { displayDate = getCalendarYearQuarterFromUnixTime(dateRange.minUnixTime); } else if (props.dateAggregationType === ChartDateAggregationType.Month.type) { displayDate = getCalendarShortYearMonthFromUnixTime(dateRange.minUnixTime); } else { displayDate = formatUnixTimeToShortDate(dateRange.minUnixTime); } if (isArray(dataItems)) { if (dataItems.length < 1) { continue; } dataItems.sort(function (data1: TransactionReconciliationStatementResponseItem, data2: TransactionReconciliationStatementResponseItem) { return data1.time - data2.time; }); const openingBalance = dataItems[0].accountOpeningBalance; const closingBalance = dataItems[dataItems.length - 1].accountClosingBalance; const minimumBalance = Math.min(...dataItems.map(item => item.accountClosingBalance)); const maximumBalance = Math.max(...dataItems.map(item => item.accountClosingBalance)); const medianBalance = dataItems[Math.floor(dataItems.length / 2)].accountClosingBalance; const averageBalance = Math.floor(sumAmounts(dataItems.map(item => item.accountClosingBalance)) / dataItems.length); if (props.account.isAsset) { lastOpeningBalance = openingBalance; lastClosingBalance = closingBalance; lastMinimumBalance = minimumBalance; lastMaximumBalance = maximumBalance; lastMedianBalance = medianBalance; lastAverageBalance = averageBalance; } else if (props.account.isLiability) { lastOpeningBalance = -openingBalance; lastClosingBalance = -closingBalance; lastMinimumBalance = -minimumBalance; lastMaximumBalance = -maximumBalance; lastMedianBalance = -medianBalance; lastAverageBalance = -averageBalance; } else { lastOpeningBalance = openingBalance; lastClosingBalance = closingBalance; lastMinimumBalance = minimumBalance; lastMaximumBalance = maximumBalance; lastMedianBalance = medianBalance; lastAverageBalance = averageBalance; } } ret.push({ displayDate: displayDate, openingBalance: lastOpeningBalance, closingBalance: lastClosingBalance, minimumBalance: lastMinimumBalance, maximumBalance: lastMaximumBalance, medianBalance: lastMedianBalance, averageBalance: lastAverageBalance }); lastOpeningBalance = lastClosingBalance; } return ret; }); const allDisplayDateRanges = computed(() => { if (!allDataItems.value || allDataItems.value.length < 1) { return []; } return allDataItems.value.map(item => item.displayDate); }); return { // computed states allDateRanges, allDataItems, allDisplayDateRanges }; }