Files
ezbookkeeping/src/components/base/AccountBalanceTrendsChartBase.ts
T

278 lines
12 KiB
TypeScript

import { computed } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import {
type DateTime,
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 { isArray } from '@/lib/common.ts';
import { sumAmounts } from '@/lib/numeral.ts';
import {
parseDateTimeFromUnixTime,
getGregorianCalendarYearAndMonthFromUnixTime,
getYearFirstDateTimeBySpecifiedDateTime,
getQuarterFirstTimeTimeBySpecifiedUnixTime,
getMonthFirstDateTimeBySpecifiedUnixTime,
getDayFirstDateTimeBySpecifiedUnixTime,
getAllDaysStartAndEndUnixTimes,
getFiscalYearStartDateTime
} from '@/lib/datetime.ts';
import { TimezoneTypeForStatistics } from '@/core/timezone.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;
q1Balance: number;
q3Balance: number;
}
export interface CommonAccountBalanceTrendsChartProps {
items: TransactionReconciliationStatementResponseItem[] | undefined;
dateAggregationType: number;
timezoneUsedForDateRange: number;
fiscalYearStart: number;
account: AccountInfoResponse;
}
export function useAccountBalanceTrendsChartBase(props: CommonAccountBalanceTrendsChartProps) {
const {
formatDateTimeToShortDate,
formatDateTimeToGregorianLikeShortYear,
formatDateTimeToGregorianLikeShortYearMonth,
formatDateTimeToGregorianLikeYearQuarter,
formatDateTimeToGregorianLikeFiscalYear
} = useI18n();
const dataDateRange = computed<AccountBalanceUnixTimeAndBalanceRange | null>(() => {
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 (const item of props.items) {
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<YearUnixTime[] | FiscalYearUnixTime[] | YearQuarterUnixTime[] | YearMonthUnixTime[] | YearMonthDayUnixTime[]>(() => {
if (!dataDateRange.value) {
return [];
}
if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
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<AccountBalanceTrendsChartItem[]>(() => {
const ret: AccountBalanceTrendsChartItem[] = [];
if (!dataDateRange.value || !allDateRanges.value || allDateRanges.value.length < 1 || !props.items || props.items.length < 1) {
return ret;
}
const dayDataItemsMap: Record<string, TransactionReconciliationStatementResponseItem[]> = {};
for (const dateItem of props.items) {
let minDateTime: DateTime;
let displayDate = '';
let transactionTimeUtfOffset: number | undefined = undefined;
if (props.timezoneUsedForDateRange === TimezoneTypeForStatistics.TransactionTimezone.type) {
transactionTimeUtfOffset = dateItem.utcOffset;
}
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
minDateTime = getYearFirstDateTimeBySpecifiedDateTime(dateItem.time, transactionTimeUtfOffset);
displayDate = formatDateTimeToGregorianLikeShortYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
minDateTime = getFiscalYearStartDateTime(dateItem.time, props.fiscalYearStart, transactionTimeUtfOffset);
displayDate = formatDateTimeToGregorianLikeFiscalYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
minDateTime = getQuarterFirstTimeTimeBySpecifiedUnixTime(dateItem.time, transactionTimeUtfOffset);
displayDate = formatDateTimeToGregorianLikeYearQuarter(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
minDateTime = getMonthFirstDateTimeBySpecifiedUnixTime(dateItem.time, transactionTimeUtfOffset);
displayDate = formatDateTimeToGregorianLikeShortYearMonth(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
minDateTime = getDayFirstDateTimeBySpecifiedUnixTime(dateItem.time, transactionTimeUtfOffset);
displayDate = formatDateTimeToShortDate(minDateTime);
} else {
return ret;
}
const dataItems: TransactionReconciliationStatementResponseItem[] = dayDataItemsMap[displayDate] || [];
dataItems.push(dateItem);
dayDataItemsMap[displayDate] = dataItems;
}
let lastOpeningBalance = dataDateRange.value.minUnixTimeOpeningBalance;
let lastClosingBalance = dataDateRange.value.minUnixTimeClosingBalance;
let lastMinimumBalance = lastClosingBalance;
let lastMaximumBalance = lastClosingBalance;
let lastMedianBalance = lastClosingBalance;
let lastAverageBalance = lastClosingBalance;
let lastQ1Balance = lastClosingBalance;
let lastQ3Balance = lastClosingBalance;
for (const dateRange of allDateRanges.value) {
const minDateTime = parseDateTimeFromUnixTime(dateRange.minUnixTime);
let displayDate = '';
if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
displayDate = formatDateTimeToGregorianLikeShortYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
displayDate = formatDateTimeToGregorianLikeFiscalYear(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
displayDate = formatDateTimeToGregorianLikeYearQuarter(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
displayDate = formatDateTimeToGregorianLikeShortYearMonth(minDateTime);
} else if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
displayDate = formatDateTimeToShortDate(minDateTime);
} else {
return ret;
}
const dataItems = dayDataItemsMap[displayDate];
if (isArray(dataItems)) {
if (dataItems.length < 1) {
continue;
}
dataItems.sort(function (data1: TransactionReconciliationStatementResponseItem, data2: TransactionReconciliationStatementResponseItem) {
return data1.time - data2.time;
});
const allDataItemsSortedByClosingBalance = Array.from(dataItems)
.sort(function (data1: TransactionReconciliationStatementResponseItem, data2: TransactionReconciliationStatementResponseItem) {
return data1.accountClosingBalance - data2.accountClosingBalance;
}
);
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 = allDataItemsSortedByClosingBalance[Math.floor(allDataItemsSortedByClosingBalance.length / 2)]!.accountClosingBalance;
const averageBalance = Math.trunc(sumAmounts(dataItems.map(item => item.accountClosingBalance)) / dataItems.length);
const q1Balance = allDataItemsSortedByClosingBalance[Math.floor(allDataItemsSortedByClosingBalance.length / 4)]!.accountClosingBalance;
const q3Balance = allDataItemsSortedByClosingBalance[Math.floor(allDataItemsSortedByClosingBalance.length * 3 / 4)]!.accountClosingBalance;
if (props.account.isAsset) {
lastOpeningBalance = openingBalance;
lastClosingBalance = closingBalance;
lastMinimumBalance = minimumBalance;
lastMaximumBalance = maximumBalance;
lastMedianBalance = medianBalance;
lastAverageBalance = averageBalance;
lastQ1Balance = q1Balance;
lastQ3Balance = q3Balance;
} else if (props.account.isLiability) {
lastOpeningBalance = -openingBalance;
lastClosingBalance = -closingBalance;
lastMinimumBalance = -minimumBalance;
lastMaximumBalance = -maximumBalance;
lastMedianBalance = -medianBalance;
lastAverageBalance = -averageBalance;
lastQ1Balance = -q1Balance;
lastQ3Balance = -q3Balance;
} else {
lastOpeningBalance = openingBalance;
lastClosingBalance = closingBalance;
lastMinimumBalance = minimumBalance;
lastMaximumBalance = maximumBalance;
lastMedianBalance = medianBalance;
lastAverageBalance = averageBalance;
lastQ1Balance = q1Balance;
lastQ3Balance = q3Balance;
}
}
ret.push({
displayDate: displayDate,
openingBalance: lastOpeningBalance,
closingBalance: lastClosingBalance,
minimumBalance: lastMinimumBalance,
maximumBalance: lastMaximumBalance,
medianBalance: lastMedianBalance,
averageBalance: lastAverageBalance,
q1Balance: lastQ1Balance,
q3Balance: lastQ3Balance
});
lastOpeningBalance = lastClosingBalance;
}
return ret;
});
const allDisplayDateRanges = computed<string[]>(() => {
if (!allDataItems.value || allDataItems.value.length < 1) {
return [];
}
return allDataItems.value.map(item => item.displayDate);
});
return {
// computed states
allDateRanges,
allDataItems,
allDisplayDateRanges
};
}