mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-14 06:57:35 +08:00
1585 lines
79 KiB
TypeScript
1585 lines
79 KiB
TypeScript
import { ref, computed } from 'vue';
|
|
import { defineStore } from 'pinia';
|
|
|
|
import { useSettingsStore } from './setting.ts';
|
|
import { useUserStore } from './user.ts';
|
|
import { useAccountsStore } from './account.ts';
|
|
import { useTransactionCategoriesStore } from './transactionCategory.ts';
|
|
import { useExchangeRatesStore } from './exchangeRates.ts';
|
|
|
|
import { entries, values } from '@/core/base.ts';
|
|
import { type TextualYearMonth, type TimeRangeAndDateType, DateRangeScene, DateRange } from '@/core/datetime.ts';
|
|
import { TimezoneTypeForStatistics } from '@/core/timezone.ts';
|
|
import { CategoryType } from '@/core/category.ts';
|
|
import {
|
|
TransactionRelatedAccountType,
|
|
TransactionTagFilterType
|
|
} from '@/core/transaction.ts';
|
|
import {
|
|
StatisticsAnalysisType,
|
|
CategoricalChartType,
|
|
TrendChartType,
|
|
ChartDataType,
|
|
ChartSortingType,
|
|
ChartDateAggregationType,
|
|
DEFAULT_CATEGORICAL_CHART_DATA_RANGE,
|
|
DEFAULT_TREND_CHART_DATA_RANGE
|
|
} from '@/core/statistics.ts';
|
|
import { DEFAULT_ACCOUNT_ICON, DEFAULT_CATEGORY_ICON } from '@/consts/icon.ts';
|
|
import { DEFAULT_ACCOUNT_COLOR, DEFAULT_CATEGORY_COLOR } from '@/consts/color.ts';
|
|
|
|
import {
|
|
type TransactionStatisticResponse,
|
|
type TransactionStatisticResponseItem,
|
|
type TransactionStatisticTrendsResponseItem,
|
|
type TransactionStatisticResponseItemWithInfo,
|
|
type TransactionStatisticResponseWithInfo,
|
|
type TransactionStatisticTrendsResponseItemWithInfo,
|
|
type TransactionStatisticDataItemType,
|
|
type TransactionStatisticDataItemBase,
|
|
type TransactionCategoricalOverviewAnalysisData,
|
|
type TransactionCategoricalOverviewAnalysisDataItem,
|
|
type TransactionCategoricalAnalysisData,
|
|
type TransactionCategoricalAnalysisDataItem,
|
|
type TransactionTrendsAnalysisData,
|
|
type TransactionTrendsAnalysisDataItem,
|
|
type TransactionTrendsAnalysisDataAmount,
|
|
TransactionCategoricalOverviewAnalysisDataItemType
|
|
} from '@/models/transaction.ts';
|
|
|
|
import {
|
|
isEquals,
|
|
isNumber,
|
|
isString,
|
|
isObject,
|
|
isInteger,
|
|
isYearMonth,
|
|
isYearMonthEquals,
|
|
isObjectEmpty,
|
|
objectFieldToArrayItem
|
|
} from '@/lib/common.ts';
|
|
import { getGregorianCalendarYearAndMonthFromUnixTime, getDateRangeByDateType } from '@/lib/datetime.ts';
|
|
import { getFinalAccountIdsByFilteredAccountIds } from '@/lib/account.ts';
|
|
import { getFinalCategoryIdsByFilteredCategoryIds } from '@/lib/category.ts';
|
|
import { sortStatisticsItems } from '@/lib/statistics.ts';
|
|
import logger from '@/lib/logger.ts';
|
|
import services from '@/lib/services.ts';
|
|
|
|
interface WritableTransactionCategoricalAnalysisData {
|
|
totalAmount: number;
|
|
totalNonNegativeAmount: number;
|
|
items: Record<string, WritableTransactionCategoricalAnalysisDataItem>;
|
|
}
|
|
|
|
interface WritableTransactionCategoricalAnalysisDataItem extends Record<string, unknown> {
|
|
name: string;
|
|
type: TransactionStatisticDataItemType;
|
|
id: string;
|
|
icon: string;
|
|
color: string;
|
|
hidden: boolean;
|
|
displayOrders: number[];
|
|
totalAmount: number;
|
|
percent?: number;
|
|
}
|
|
|
|
interface WritableTransactionTrendsAnalysisDataItem extends Record<string, unknown> {
|
|
name: string;
|
|
type: TransactionStatisticDataItemType;
|
|
id: string;
|
|
icon: string;
|
|
color: string;
|
|
hidden: boolean;
|
|
displayOrders: number[];
|
|
totalAmount: number;
|
|
items: TransactionTrendsAnalysisDataAmount[];
|
|
}
|
|
|
|
export interface TransactionStatisticsPartialFilter {
|
|
chartDataType?: number;
|
|
categoricalChartType?: number;
|
|
categoricalChartDateType?: number;
|
|
categoricalChartStartTime?: number;
|
|
categoricalChartEndTime?: number;
|
|
trendChartType?: number;
|
|
trendChartDateType?: number;
|
|
trendChartStartYearMonth?: TextualYearMonth | '';
|
|
trendChartEndYearMonth?: TextualYearMonth | '';
|
|
filterAccountIds?: Record<string, boolean>;
|
|
filterCategoryIds?: Record<string, boolean>;
|
|
tagIds?: string;
|
|
tagFilterType?: number;
|
|
keyword?: string;
|
|
sortingType?: number;
|
|
}
|
|
|
|
export interface TransactionStatisticsFilter extends TransactionStatisticsPartialFilter {
|
|
chartDataType: number;
|
|
categoricalChartType: number;
|
|
categoricalChartDateType: number;
|
|
categoricalChartStartTime: number;
|
|
categoricalChartEndTime: number;
|
|
trendChartType: number;
|
|
trendChartDateType: number;
|
|
trendChartStartYearMonth: TextualYearMonth | '';
|
|
trendChartEndYearMonth: TextualYearMonth | '';
|
|
filterAccountIds: Record<string, boolean>;
|
|
filterCategoryIds: Record<string, boolean>;
|
|
tagIds: string;
|
|
tagFilterType: number;
|
|
keyword: string;
|
|
sortingType: number;
|
|
}
|
|
|
|
export const useStatisticsStore = defineStore('statistics', () => {
|
|
const settingsStore = useSettingsStore();
|
|
const userStore = useUserStore();
|
|
const accountsStore = useAccountsStore();
|
|
const transactionCategoriesStore = useTransactionCategoriesStore();
|
|
const exchangeRatesStore = useExchangeRatesStore();
|
|
|
|
const transactionStatisticsFilter = ref<TransactionStatisticsFilter>({
|
|
chartDataType: ChartDataType.Default.type,
|
|
categoricalChartType: CategoricalChartType.Default.type,
|
|
categoricalChartDateType: DEFAULT_CATEGORICAL_CHART_DATA_RANGE.type,
|
|
categoricalChartStartTime: 0,
|
|
categoricalChartEndTime: 0,
|
|
trendChartType: TrendChartType.Default.type,
|
|
trendChartDateType: DEFAULT_TREND_CHART_DATA_RANGE.type,
|
|
trendChartStartYearMonth: '',
|
|
trendChartEndYearMonth: '',
|
|
filterAccountIds: {},
|
|
filterCategoryIds: {},
|
|
tagIds: '',
|
|
tagFilterType: TransactionTagFilterType.Default.type,
|
|
keyword: '',
|
|
sortingType: ChartSortingType.Default.type
|
|
});
|
|
|
|
const transactionCategoryStatisticsData = ref<TransactionStatisticResponse | null>(null);
|
|
const transactionCategoryTrendsData = ref<TransactionStatisticTrendsResponseItem[]>([]);
|
|
const transactionStatisticsStateInvalid = ref<boolean>(true);
|
|
|
|
const categoricalAnalysisChartDataCategory = computed<string>(() => {
|
|
if (transactionStatisticsFilter.value.chartDataType === ChartDataType.OutflowsByAccount.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByAccount.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.InflowsByAccount.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByAccount.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalAssets.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
|
|
return 'account';
|
|
} else if (transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByPrimaryCategory.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseBySecondaryCategory.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByPrimaryCategory.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeBySecondaryCategory.type) {
|
|
return 'category';
|
|
} else {
|
|
return '';
|
|
}
|
|
});
|
|
|
|
const transactionCategoryStatisticsDataWithCategoryAndAccountInfo = computed<TransactionStatisticResponseWithInfo | null>(() => {
|
|
const statistics = transactionCategoryStatisticsData.value;
|
|
|
|
if (!statistics) {
|
|
return null;
|
|
}
|
|
|
|
const finalStatistics: TransactionStatisticResponseWithInfo = {
|
|
startTime: statistics.startTime,
|
|
endTime: statistics.endTime,
|
|
items: []
|
|
};
|
|
|
|
if (statistics && statistics.items && statistics.items.length) {
|
|
finalStatistics.items.push(...assembleAccountAndCategoryInfo(statistics.items));
|
|
}
|
|
|
|
return finalStatistics;
|
|
});
|
|
|
|
const transactionCategoryTotalAmountAnalysisData = computed<WritableTransactionCategoricalAnalysisData | null>(() => {
|
|
if (!transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value || !transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.items) {
|
|
return null;
|
|
}
|
|
|
|
return getCategoryTotalAmountItems(transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.items, transactionStatisticsFilter.value);
|
|
});
|
|
|
|
const categoricalOverviewAnalysisData = computed<TransactionCategoricalOverviewAnalysisData | null>(() => {
|
|
if (!transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value || !transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.items) {
|
|
return null;
|
|
}
|
|
|
|
const allDataItemsMap: Record<string, TransactionCategoricalOverviewAnalysisDataItem> = {};
|
|
const allIncomeByPrimaryCategoryDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
|
const allIncomeBySecondaryCategoryDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
|
const allIncomeByAccountDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
|
const allExpenseByAccountDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
|
const allExpenseBySecondaryCategoryDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
|
const allExpenseByPrimaryCategoryDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
|
const allOpeningBalanceDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
|
const allNetCashFlowDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [];
|
|
|
|
let totalIncome: number = 0;
|
|
let totalExpense: number = 0;
|
|
|
|
for (const item of transactionCategoryStatisticsDataWithCategoryAndAccountInfo.value.items) {
|
|
if (!item.primaryAccount || !item.account || !item.primaryCategory || !item.category) {
|
|
continue;
|
|
}
|
|
|
|
if (item.relatedAccount && item.relatedAccountType === TransactionRelatedAccountType.TransferFrom) {
|
|
continue;
|
|
}
|
|
|
|
if (!isNumber(item.amountInDefaultCurrency)) {
|
|
continue;
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.filterAccountIds && transactionStatisticsFilter.value.filterAccountIds[item.account.id]) {
|
|
continue;
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.filterCategoryIds && transactionStatisticsFilter.value.filterCategoryIds[item.category.id]) {
|
|
continue;
|
|
}
|
|
|
|
if (item.category.type === CategoryType.Income) {
|
|
totalIncome += item.amountInDefaultCurrency;
|
|
} else if (item.category.type === CategoryType.Expense) {
|
|
totalExpense += item.amountInDefaultCurrency;
|
|
}
|
|
|
|
const incomeByAccountKey = `${TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount}:${item.account.id}`;
|
|
const expenseByAccountKey = `${TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount}:${item.account.id}`;
|
|
let incomeByAccountItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[incomeByAccountKey];
|
|
let expenseByAccountItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[expenseByAccountKey];
|
|
|
|
if (!incomeByAccountItem) {
|
|
incomeByAccountItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
|
item.account.id,
|
|
item.account.name,
|
|
TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount,
|
|
[item.primaryAccount.category, item.primaryAccount.displayOrder, item.account.displayOrder],
|
|
item.primaryAccount.hidden || item.account.hidden);
|
|
allDataItemsMap[incomeByAccountKey] = incomeByAccountItem;
|
|
allIncomeByAccountDataItems.push(incomeByAccountItem);
|
|
}
|
|
|
|
if (!expenseByAccountItem) {
|
|
expenseByAccountItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
|
item.account.id,
|
|
item.account.name,
|
|
TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount,
|
|
[item.primaryAccount.category, item.primaryAccount.displayOrder, item.account.displayOrder],
|
|
item.primaryAccount.hidden || item.account.hidden);
|
|
allDataItemsMap[expenseByAccountKey] = expenseByAccountItem;
|
|
allExpenseByAccountDataItems.push(expenseByAccountItem);
|
|
}
|
|
|
|
if (item.category.type === CategoryType.Income) {
|
|
const primaryCategoryItemKey = `${TransactionCategoricalOverviewAnalysisDataItemType.IncomeByPrimaryCategory}:${item.primaryCategory.id}`;
|
|
const secondaryCategoryItemKey = `${TransactionCategoricalOverviewAnalysisDataItemType.IncomeBySecondaryCategory}:${item.category.id}`;
|
|
|
|
let primaryCategoryDataItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[primaryCategoryItemKey];
|
|
let secondaryCategoryDataItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[secondaryCategoryItemKey];
|
|
|
|
if (!primaryCategoryDataItem) {
|
|
primaryCategoryDataItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
|
item.primaryCategory.id,
|
|
item.primaryCategory.name,
|
|
TransactionCategoricalOverviewAnalysisDataItemType.IncomeByPrimaryCategory,
|
|
[item.primaryCategory.displayOrder],
|
|
item.primaryCategory.hidden);
|
|
allDataItemsMap[primaryCategoryItemKey] = primaryCategoryDataItem;
|
|
allIncomeByPrimaryCategoryDataItems.push(primaryCategoryDataItem);
|
|
}
|
|
|
|
if (!secondaryCategoryDataItem) {
|
|
secondaryCategoryDataItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
|
item.category.id,
|
|
item.category.name,
|
|
TransactionCategoricalOverviewAnalysisDataItemType.IncomeBySecondaryCategory,
|
|
[item.primaryCategory.displayOrder, item.category.displayOrder],
|
|
item.primaryCategory.hidden || item.category.hidden);
|
|
allDataItemsMap[secondaryCategoryItemKey] = secondaryCategoryDataItem;
|
|
allIncomeBySecondaryCategoryDataItems.push(secondaryCategoryDataItem);
|
|
}
|
|
|
|
primaryCategoryDataItem.totalAmount += item.amountInDefaultCurrency;
|
|
primaryCategoryDataItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0;
|
|
primaryCategoryDataItem.includeInPercent = true;
|
|
primaryCategoryDataItem.outflows.push({ amount: item.amountInDefaultCurrency, relatedItem: secondaryCategoryDataItem });
|
|
|
|
secondaryCategoryDataItem.totalAmount += item.amountInDefaultCurrency;
|
|
secondaryCategoryDataItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0;
|
|
secondaryCategoryDataItem.includeInPercent = true;
|
|
secondaryCategoryDataItem.inflows.push({ amount: item.amountInDefaultCurrency, relatedItem: primaryCategoryDataItem });
|
|
secondaryCategoryDataItem.outflows.push({ amount: item.amountInDefaultCurrency, relatedItem: incomeByAccountItem });
|
|
|
|
incomeByAccountItem.totalAmount += item.amountInDefaultCurrency;
|
|
incomeByAccountItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0;
|
|
incomeByAccountItem.includeInPercent = true;
|
|
incomeByAccountItem.inflows.push({ amount: item.amountInDefaultCurrency, relatedItem: secondaryCategoryDataItem });
|
|
} else if (item.category.type === CategoryType.Expense) {
|
|
const primaryCategoryItemKey = `${TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByPrimaryCategory}:${item.primaryCategory.id}`;
|
|
const secondaryCategoryItemKey = `${TransactionCategoricalOverviewAnalysisDataItemType.ExpenseBySecondaryCategory}:${item.category.id}`;
|
|
|
|
let primaryCategoryDataItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[primaryCategoryItemKey];
|
|
let secondaryCategoryDataItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[secondaryCategoryItemKey];
|
|
|
|
if (!primaryCategoryDataItem) {
|
|
primaryCategoryDataItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
|
item.primaryCategory.id,
|
|
item.primaryCategory.name,
|
|
TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByPrimaryCategory,
|
|
[item.primaryCategory.displayOrder],
|
|
item.primaryCategory.hidden);
|
|
allDataItemsMap[primaryCategoryItemKey] = primaryCategoryDataItem;
|
|
allExpenseByPrimaryCategoryDataItems.push(primaryCategoryDataItem);
|
|
}
|
|
|
|
if (!secondaryCategoryDataItem) {
|
|
secondaryCategoryDataItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
|
item.category.id,
|
|
item.category.name,
|
|
TransactionCategoricalOverviewAnalysisDataItemType.ExpenseBySecondaryCategory,
|
|
[item.primaryCategory.displayOrder, item.category.displayOrder],
|
|
item.primaryCategory.hidden || item.category.hidden);
|
|
allDataItemsMap[secondaryCategoryItemKey] = secondaryCategoryDataItem;
|
|
allExpenseBySecondaryCategoryDataItems.push(secondaryCategoryDataItem);
|
|
}
|
|
|
|
expenseByAccountItem.totalAmount += item.amountInDefaultCurrency;
|
|
expenseByAccountItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0;
|
|
expenseByAccountItem.includeInPercent = true;
|
|
expenseByAccountItem.outflows.push({ amount: item.amountInDefaultCurrency, relatedItem: secondaryCategoryDataItem });
|
|
|
|
secondaryCategoryDataItem.totalAmount += item.amountInDefaultCurrency;
|
|
secondaryCategoryDataItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0
|
|
secondaryCategoryDataItem.includeInPercent = true;
|
|
secondaryCategoryDataItem.inflows.push({ amount: item.amountInDefaultCurrency, relatedItem: expenseByAccountItem });
|
|
secondaryCategoryDataItem.outflows.push({ amount: item.amountInDefaultCurrency, relatedItem: primaryCategoryDataItem });
|
|
|
|
primaryCategoryDataItem.totalAmount += item.amountInDefaultCurrency;
|
|
primaryCategoryDataItem.totalNonNegativeAmount += item.amountInDefaultCurrency > 0 ? item.amountInDefaultCurrency : 0;
|
|
primaryCategoryDataItem.includeInPercent = true;
|
|
primaryCategoryDataItem.inflows.push({ amount: item.amountInDefaultCurrency, relatedItem: secondaryCategoryDataItem });
|
|
} else if (item.category.type === CategoryType.Transfer && item.relatedPrimaryAccount && item.relatedAccount) {
|
|
const transferToAccountKey = `${TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount}:${item.relatedAccount.id}`;
|
|
let transferToAccountItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[transferToAccountKey];
|
|
|
|
if (!transferToAccountItem) {
|
|
transferToAccountItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
|
item.relatedAccount.id,
|
|
item.relatedAccount.name,
|
|
TransactionCategoricalOverviewAnalysisDataItemType.ExpenseByAccount,
|
|
[item.relatedPrimaryAccount.category, item.relatedPrimaryAccount.displayOrder, item.relatedAccount.displayOrder],
|
|
item.relatedPrimaryAccount.hidden || item.relatedAccount.hidden);
|
|
allDataItemsMap[transferToAccountKey] = transferToAccountItem;
|
|
allExpenseByAccountDataItems.push(transferToAccountItem);
|
|
}
|
|
|
|
incomeByAccountItem.outflows.push({ amount: item.amountInDefaultCurrency, relatedItem: transferToAccountItem });
|
|
transferToAccountItem.inflows.push({ amount: item.amountInDefaultCurrency, relatedItem: incomeByAccountItem });
|
|
}
|
|
}
|
|
|
|
sortCategoricalOverviewAnalysisDataItems(allIncomeByPrimaryCategoryDataItems, transactionStatisticsFilter.value);
|
|
sortCategoricalOverviewAnalysisDataItems(allIncomeBySecondaryCategoryDataItems, transactionStatisticsFilter.value);
|
|
sortCategoricalOverviewAnalysisDataItems(allIncomeByAccountDataItems, transactionStatisticsFilter.value);
|
|
sortCategoricalOverviewAnalysisDataItems(allExpenseByAccountDataItems, transactionStatisticsFilter.value);
|
|
sortCategoricalOverviewAnalysisDataItems(allExpenseBySecondaryCategoryDataItems, transactionStatisticsFilter.value);
|
|
sortCategoricalOverviewAnalysisDataItems(allExpenseByPrimaryCategoryDataItems, transactionStatisticsFilter.value);
|
|
|
|
for (const item of allExpenseByAccountDataItems) {
|
|
const incomeByAccountKey = `${TransactionCategoricalOverviewAnalysisDataItemType.IncomeByAccount}:${item.id}`;
|
|
const incomeByAccountItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[incomeByAccountKey];
|
|
|
|
let accountTotalInflowsAmount: number = 0;
|
|
let accountTotalIncomeAmount: number = 0;
|
|
let accountTotalTransferAmount: number = 0;
|
|
let accountTotalOutflowsAmount: number = 0;
|
|
|
|
if (incomeByAccountItem) {
|
|
for (const inflow of incomeByAccountItem.inflows) {
|
|
accountTotalInflowsAmount += inflow.amount;
|
|
accountTotalIncomeAmount += inflow.amount;
|
|
}
|
|
|
|
for (const outflow of incomeByAccountItem.outflows) {
|
|
accountTotalTransferAmount += outflow.amount;
|
|
}
|
|
}
|
|
|
|
for (const inflow of item.inflows) {
|
|
if (inflow.relatedItem.type === item.type && inflow.relatedItem.id === item.id) {
|
|
continue;
|
|
}
|
|
|
|
accountTotalInflowsAmount += inflow.amount;
|
|
}
|
|
|
|
for (const outflow of item.outflows) {
|
|
accountTotalOutflowsAmount += outflow.amount;
|
|
}
|
|
|
|
const accountBalance: number = accountTotalIncomeAmount - accountTotalTransferAmount - accountTotalOutflowsAmount;
|
|
const accountNetCashFlow: number = accountTotalInflowsAmount - accountTotalTransferAmount - accountTotalOutflowsAmount;
|
|
|
|
if (incomeByAccountItem && accountsStore.allAccountsMap[item.id]?.isAsset) {
|
|
if (accountBalance > 0) { // has positive balance, transfer the amount from income account to expense account
|
|
incomeByAccountItem.outflows.push({ amount: accountBalance + accountTotalOutflowsAmount, relatedItem: item });
|
|
item.inflows.push({ amount: accountBalance + accountTotalOutflowsAmount, relatedItem: incomeByAccountItem });
|
|
} else if (accountNetCashFlow < 0) { // has negative net cash flow, add the difference to income account
|
|
incomeByAccountItem.totalAmount += -accountNetCashFlow;
|
|
incomeByAccountItem.totalNonNegativeAmount += -accountNetCashFlow > 0 ? -accountNetCashFlow : 0;
|
|
incomeByAccountItem.outflows.push({ amount: -accountNetCashFlow, relatedItem: item });
|
|
item.inflows.push({ amount: -accountNetCashFlow, relatedItem: incomeByAccountItem });
|
|
}
|
|
}
|
|
|
|
if (accountNetCashFlow > 0) {
|
|
let netCashFlowItem: TransactionCategoricalOverviewAnalysisDataItem | undefined = allDataItemsMap[TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow];
|
|
|
|
if (!netCashFlowItem) {
|
|
netCashFlowItem = createNewTransactionCategoricalOverviewAnalysisDataItem(
|
|
TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow,
|
|
'Net Cash Flow',
|
|
TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow,
|
|
[Number.MAX_SAFE_INTEGER],
|
|
false);
|
|
allDataItemsMap[TransactionCategoricalOverviewAnalysisDataItemType.NetCashFlow] = netCashFlowItem;
|
|
allNetCashFlowDataItems.push(netCashFlowItem);
|
|
}
|
|
|
|
item.outflows.push({ amount: accountNetCashFlow, relatedItem: netCashFlowItem });
|
|
|
|
netCashFlowItem.totalAmount += accountNetCashFlow;
|
|
netCashFlowItem.totalNonNegativeAmount += accountNetCashFlow > 0 ? accountNetCashFlow : 0;
|
|
netCashFlowItem.inflows.push({ amount: accountNetCashFlow, relatedItem: item });
|
|
}
|
|
}
|
|
|
|
const allDataItems: TransactionCategoricalOverviewAnalysisDataItem[] = [
|
|
...allIncomeByPrimaryCategoryDataItems,
|
|
...allIncomeBySecondaryCategoryDataItems,
|
|
...allIncomeByAccountDataItems,
|
|
...allOpeningBalanceDataItems,
|
|
...allExpenseByAccountDataItems,
|
|
...allExpenseBySecondaryCategoryDataItems,
|
|
...allNetCashFlowDataItems,
|
|
...allExpenseByPrimaryCategoryDataItems
|
|
];
|
|
|
|
return {
|
|
totalIncome: totalIncome,
|
|
totalExpense: totalExpense,
|
|
items: allDataItems
|
|
};
|
|
});
|
|
|
|
const accountTotalAmountAnalysisData = computed<WritableTransactionCategoricalAnalysisData | null>(() => {
|
|
if (!accountsStore.allPlainAccounts) {
|
|
return null;
|
|
}
|
|
|
|
const allDataItems: Record<string, WritableTransactionCategoricalAnalysisDataItem> = {};
|
|
let totalAmount = 0;
|
|
let totalNonNegativeAmount = 0;
|
|
|
|
for (const account of accountsStore.allPlainAccounts) {
|
|
if (transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalAssets.type) {
|
|
if (!account.isAsset) {
|
|
continue;
|
|
}
|
|
} else if (transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
|
|
if (!account.isLiability) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.filterAccountIds && transactionStatisticsFilter.value.filterAccountIds[account.id]) {
|
|
continue;
|
|
}
|
|
|
|
let primaryAccount = accountsStore.allAccountsMap[account.parentId];
|
|
|
|
if (!primaryAccount) {
|
|
primaryAccount = account;
|
|
}
|
|
|
|
let amount = account.balance;
|
|
|
|
if (account.currency !== userStore.currentUserDefaultCurrency) {
|
|
const finalAmount = exchangeRatesStore.getExchangedAmount(amount, account.currency, userStore.currentUserDefaultCurrency);
|
|
|
|
if (!isNumber(finalAmount)) {
|
|
continue;
|
|
}
|
|
|
|
amount = Math.trunc(finalAmount);
|
|
}
|
|
|
|
if (account.isLiability) {
|
|
amount = -amount;
|
|
}
|
|
|
|
const data: WritableTransactionCategoricalAnalysisDataItem = {
|
|
name: account.name,
|
|
type: 'account',
|
|
id: account.id,
|
|
icon: account.icon || DEFAULT_ACCOUNT_ICON.icon,
|
|
color: account.color || DEFAULT_ACCOUNT_COLOR,
|
|
hidden: primaryAccount.hidden || account.hidden,
|
|
displayOrders: [primaryAccount.category, primaryAccount.displayOrder, account.displayOrder],
|
|
totalAmount: amount
|
|
};
|
|
|
|
totalAmount += amount;
|
|
|
|
if (amount > 0) {
|
|
totalNonNegativeAmount += amount;
|
|
}
|
|
|
|
allDataItems[account.id] = data;
|
|
}
|
|
|
|
return {
|
|
totalAmount: totalAmount,
|
|
totalNonNegativeAmount: totalNonNegativeAmount,
|
|
items: allDataItems
|
|
};
|
|
});
|
|
|
|
const categoricalAnalysisData = computed<TransactionCategoricalAnalysisData>(() => {
|
|
let combinedData: WritableTransactionCategoricalAnalysisData | null = null;
|
|
|
|
if (transactionStatisticsFilter.value.chartDataType === ChartDataType.OutflowsByAccount.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByAccount.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByPrimaryCategory.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseBySecondaryCategory.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.InflowsByAccount.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByAccount.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByPrimaryCategory.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeBySecondaryCategory.type) {
|
|
combinedData = transactionCategoryTotalAmountAnalysisData.value;
|
|
} else if (transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalAssets.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalLiabilities.type) {
|
|
combinedData = accountTotalAmountAnalysisData.value;
|
|
}
|
|
|
|
const allStatisticsItems: TransactionCategoricalAnalysisDataItem[] = [];
|
|
|
|
if (combinedData && combinedData.items) {
|
|
let maxTotalAmount = 0;
|
|
|
|
for (const dataItem of values(combinedData.items)) {
|
|
if (dataItem.totalAmount > maxTotalAmount) {
|
|
maxTotalAmount = dataItem.totalAmount;
|
|
}
|
|
}
|
|
|
|
for (const dataItem of values(combinedData.items)) {
|
|
let percent = 0;
|
|
|
|
if (transactionStatisticsFilter.value.chartDataType === ChartDataType.OutflowsByAccount.type ||
|
|
transactionStatisticsFilter.value.chartDataType === ChartDataType.InflowsByAccount.type) {
|
|
if (maxTotalAmount > 0) {
|
|
percent = dataItem.totalAmount * 100 / maxTotalAmount;
|
|
} else {
|
|
percent = 0;
|
|
}
|
|
|
|
if (percent < 0) {
|
|
percent = 0;
|
|
}
|
|
} else {
|
|
if (dataItem.totalAmount > 0) {
|
|
percent = dataItem.totalAmount * 100 / combinedData.totalNonNegativeAmount;
|
|
} else {
|
|
percent = 0;
|
|
}
|
|
|
|
if (percent < 0) {
|
|
percent = 0;
|
|
}
|
|
}
|
|
|
|
const statisticDataItem: TransactionCategoricalAnalysisDataItem = {
|
|
name: dataItem.name,
|
|
type: dataItem.type,
|
|
id: dataItem.id,
|
|
icon: dataItem.icon,
|
|
color: dataItem.color,
|
|
hidden: dataItem.hidden,
|
|
displayOrders: dataItem.displayOrders,
|
|
totalAmount: dataItem.totalAmount,
|
|
percent: percent
|
|
};
|
|
|
|
allStatisticsItems.push(statisticDataItem);
|
|
}
|
|
}
|
|
|
|
sortCategoryTotalAmountItems(allStatisticsItems, transactionStatisticsFilter.value);
|
|
|
|
const statisticData: TransactionCategoricalAnalysisData = {
|
|
totalAmount: combinedData?.totalAmount || 0,
|
|
items: allStatisticsItems
|
|
};
|
|
|
|
return statisticData;
|
|
});
|
|
|
|
const transactionCategoryTrendsDataWithCategoryAndAccountInfo = computed<TransactionStatisticTrendsResponseItemWithInfo[]>(() => {
|
|
const trendsData = transactionCategoryTrendsData.value;
|
|
const finalTrendsData: TransactionStatisticTrendsResponseItemWithInfo[] = [];
|
|
|
|
if (trendsData && trendsData.length) {
|
|
for (const trendItem of trendsData) {
|
|
const finalTrendItem: TransactionStatisticTrendsResponseItemWithInfo = {
|
|
year: trendItem.year,
|
|
month: trendItem.month,
|
|
items: []
|
|
};
|
|
|
|
if (trendItem && trendItem.items && trendItem.items.length) {
|
|
finalTrendItem.items.push(...assembleAccountAndCategoryInfo(trendItem.items));
|
|
}
|
|
|
|
finalTrendsData.push(finalTrendItem);
|
|
}
|
|
}
|
|
|
|
return finalTrendsData;
|
|
});
|
|
|
|
const trendsAnalysisData = computed<TransactionTrendsAnalysisData | null>(() => {
|
|
if (!transactionCategoryTrendsDataWithCategoryAndAccountInfo.value || !transactionCategoryTrendsDataWithCategoryAndAccountInfo.value.length) {
|
|
return null;
|
|
}
|
|
|
|
const combinedDataMap: Record<string, WritableTransactionTrendsAnalysisDataItem> = {};
|
|
|
|
for (const trendItem of transactionCategoryTrendsDataWithCategoryAndAccountInfo.value) {
|
|
const totalAmountItems = getCategoryTotalAmountItems(trendItem.items, transactionStatisticsFilter.value);
|
|
|
|
for (const [id, item] of entries(totalAmountItems.items)) {
|
|
let combinedData = combinedDataMap[id];
|
|
|
|
if (!combinedData) {
|
|
combinedData = {
|
|
name: item.name,
|
|
type: item.type,
|
|
id: item.id,
|
|
icon: item.icon,
|
|
color: item.color,
|
|
hidden: item.hidden,
|
|
displayOrders: item.displayOrders,
|
|
totalAmount: 0,
|
|
items: []
|
|
};
|
|
}
|
|
|
|
combinedData.items.push({
|
|
year: trendItem.year,
|
|
month1base: trendItem.month,
|
|
totalAmount: item.totalAmount
|
|
});
|
|
|
|
combinedData.totalAmount += item.totalAmount;
|
|
combinedDataMap[id] = combinedData;
|
|
}
|
|
}
|
|
|
|
const totalAmountsTrends: TransactionTrendsAnalysisDataItem[] = [];
|
|
|
|
for (const trendData of values(combinedDataMap)) {
|
|
totalAmountsTrends.push(trendData);
|
|
}
|
|
|
|
sortCategoryTotalAmountItems(totalAmountsTrends, transactionStatisticsFilter.value);
|
|
|
|
const trendsData: TransactionTrendsAnalysisData = {
|
|
items: totalAmountsTrends
|
|
};
|
|
|
|
return trendsData;
|
|
});
|
|
|
|
function createNewTransactionCategoricalOverviewAnalysisDataItem(id: string, name: string, type: TransactionCategoricalOverviewAnalysisDataItemType, displayOrders: number[], hidden: boolean): TransactionCategoricalOverviewAnalysisDataItem {
|
|
const dataItem: TransactionCategoricalOverviewAnalysisDataItem = {
|
|
id: id,
|
|
name: name,
|
|
type: type,
|
|
displayOrders: displayOrders,
|
|
hidden: hidden,
|
|
inflows: [],
|
|
outflows: [],
|
|
totalAmount: 0,
|
|
totalNonNegativeAmount: 0
|
|
};
|
|
|
|
return dataItem;
|
|
}
|
|
|
|
function sortCategoricalOverviewAnalysisDataItems(items: TransactionCategoricalOverviewAnalysisDataItem[], transactionStatisticsFilter: TransactionStatisticsFilter): void {
|
|
let totalNonNegativeAmount: number = 0;
|
|
|
|
for (const item of items) {
|
|
totalNonNegativeAmount += item.totalNonNegativeAmount;
|
|
}
|
|
|
|
if (totalNonNegativeAmount > 0) {
|
|
for (const item of items) {
|
|
if (!item.includeInPercent) {
|
|
continue;
|
|
}
|
|
|
|
item.percent = item.totalAmount * 100 / totalNonNegativeAmount;
|
|
}
|
|
}
|
|
|
|
sortStatisticsItems(items, transactionStatisticsFilter.sortingType);
|
|
}
|
|
|
|
function assembleAccountAndCategoryInfo(items: TransactionStatisticResponseItem[]): TransactionStatisticResponseItemWithInfo[] {
|
|
const finalItems: TransactionStatisticResponseItemWithInfo[] = [];
|
|
const defaultCurrency = userStore.currentUserDefaultCurrency;
|
|
|
|
for (const dataItem of items) {
|
|
const item: TransactionStatisticResponseItemWithInfo = {
|
|
categoryId: dataItem.categoryId,
|
|
accountId: dataItem.accountId,
|
|
relatedAccountId: dataItem.relatedAccountId,
|
|
relatedAccountType: dataItem.relatedAccountType,
|
|
amount: dataItem.amount,
|
|
amountInDefaultCurrency: null
|
|
};
|
|
|
|
if (item.accountId) {
|
|
item.account = accountsStore.allAccountsMap[item.accountId];
|
|
}
|
|
|
|
if (item.account && item.account.parentId !== '0') {
|
|
item.primaryAccount = accountsStore.allAccountsMap[item.account.parentId];
|
|
} else {
|
|
item.primaryAccount = item.account;
|
|
}
|
|
|
|
if (item.relatedAccountId) {
|
|
item.relatedAccount = accountsStore.allAccountsMap[item.relatedAccountId];
|
|
}
|
|
|
|
if (item.relatedAccount && item.relatedAccount.parentId !== '0') {
|
|
item.relatedPrimaryAccount = accountsStore.allAccountsMap[item.relatedAccount.parentId];
|
|
} else {
|
|
item.relatedPrimaryAccount = item.relatedAccount;
|
|
}
|
|
|
|
if (item.categoryId) {
|
|
item.category = transactionCategoriesStore.allTransactionCategoriesMap[item.categoryId];
|
|
}
|
|
|
|
if (item.category && item.category.parentId !== '0') {
|
|
item.primaryCategory = transactionCategoriesStore.allTransactionCategoriesMap[item.category.parentId];
|
|
} else {
|
|
item.primaryCategory = item.category;
|
|
}
|
|
|
|
if (item.account && item.account.currency !== defaultCurrency) {
|
|
const amount = exchangeRatesStore.getExchangedAmount(item.amount, item.account.currency, defaultCurrency);
|
|
|
|
if (isNumber(amount)) {
|
|
item.amountInDefaultCurrency = Math.trunc(amount);
|
|
}
|
|
} else if (item.account && item.account.currency === defaultCurrency) {
|
|
item.amountInDefaultCurrency = item.amount;
|
|
} else {
|
|
item.amountInDefaultCurrency = null;
|
|
}
|
|
|
|
finalItems.push(item);
|
|
}
|
|
|
|
return finalItems;
|
|
}
|
|
|
|
function getCategoryTotalAmountItems(items: TransactionStatisticResponseItemWithInfo[], transactionStatisticsFilter: TransactionStatisticsFilter): WritableTransactionCategoricalAnalysisData {
|
|
const allDataItems: Record<string, WritableTransactionCategoricalAnalysisDataItem> = {};
|
|
let totalAmount = 0;
|
|
let totalNonNegativeAmount = 0;
|
|
|
|
for (const item of items) {
|
|
if (!item.primaryAccount || !item.account || !item.primaryCategory || !item.category) {
|
|
continue;
|
|
}
|
|
|
|
if (transactionStatisticsFilter.chartDataType === ChartDataType.OutflowsByAccount.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.TotalOutflows.type) {
|
|
if (item.category.type === CategoryType.Transfer) {
|
|
if (item.relatedAccountType !== TransactionRelatedAccountType.TransferTo) {
|
|
continue;
|
|
}
|
|
} else if (item.category.type !== CategoryType.Expense) {
|
|
continue;
|
|
}
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.ExpenseByAccount.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.ExpenseByPrimaryCategory.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.ExpenseBySecondaryCategory.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.TotalExpense.type) {
|
|
if (item.category.type !== CategoryType.Expense) {
|
|
continue;
|
|
}
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.InflowsByAccount.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.TotalInflows.type) {
|
|
if (item.category.type === CategoryType.Transfer) {
|
|
if (item.relatedAccountType !== TransactionRelatedAccountType.TransferFrom) {
|
|
continue;
|
|
}
|
|
} else if (item.category.type !== CategoryType.Income) {
|
|
continue;
|
|
}
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.IncomeByAccount.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.IncomeByPrimaryCategory.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.IncomeBySecondaryCategory.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.TotalIncome.type) {
|
|
if (item.category.type !== CategoryType.Income) {
|
|
continue;
|
|
}
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.NetCashFlow.type) {
|
|
// Do Nothing
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.NetIncome.type) {
|
|
if (item.category.type === CategoryType.Transfer) {
|
|
continue;
|
|
}
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
if (transactionStatisticsFilter.filterAccountIds && transactionStatisticsFilter.filterAccountIds[item.account.id]) {
|
|
continue;
|
|
}
|
|
|
|
if (transactionStatisticsFilter.filterCategoryIds && transactionStatisticsFilter.filterCategoryIds[item.category.id]) {
|
|
continue;
|
|
}
|
|
|
|
if (transactionStatisticsFilter.chartDataType === ChartDataType.OutflowsByAccount.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.ExpenseByAccount.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.InflowsByAccount.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.IncomeByAccount.type) {
|
|
if (isNumber(item.amountInDefaultCurrency)) {
|
|
let data = allDataItems[item.account.id];
|
|
|
|
if (data) {
|
|
data.totalAmount += item.amountInDefaultCurrency;
|
|
} else {
|
|
data = {
|
|
name: item.account.name,
|
|
type: 'account',
|
|
id: item.account.id,
|
|
icon: item.account.icon || DEFAULT_ACCOUNT_ICON.icon,
|
|
color: item.account.color || DEFAULT_ACCOUNT_COLOR,
|
|
hidden: item.primaryAccount.hidden || item.account.hidden,
|
|
displayOrders: [item.primaryAccount.category, item.primaryAccount.displayOrder, item.account.displayOrder],
|
|
totalAmount: item.amountInDefaultCurrency
|
|
};
|
|
}
|
|
|
|
let includeInTotal: boolean = true;
|
|
|
|
// total outflows / inflows do not include transfer transactions between unfiltered accounts
|
|
if (transactionStatisticsFilter.chartDataType === ChartDataType.OutflowsByAccount.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.InflowsByAccount.type) {
|
|
if (item.relatedAccount && (!transactionStatisticsFilter.filterAccountIds || !transactionStatisticsFilter.filterAccountIds[item.relatedAccount.id])) {
|
|
includeInTotal = false;
|
|
}
|
|
}
|
|
|
|
if (includeInTotal) {
|
|
totalAmount += item.amountInDefaultCurrency;
|
|
|
|
if (item.amountInDefaultCurrency > 0) {
|
|
totalNonNegativeAmount += item.amountInDefaultCurrency;
|
|
}
|
|
}
|
|
|
|
allDataItems[item.account.id] = data;
|
|
}
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.ExpenseByPrimaryCategory.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.IncomeByPrimaryCategory.type) {
|
|
if (isNumber(item.amountInDefaultCurrency)) {
|
|
let data = allDataItems[item.primaryCategory.id];
|
|
|
|
if (data) {
|
|
data.totalAmount += item.amountInDefaultCurrency;
|
|
} else {
|
|
data = {
|
|
name: item.primaryCategory.name,
|
|
type: 'category',
|
|
id: item.primaryCategory.id,
|
|
icon: item.primaryCategory.icon || DEFAULT_CATEGORY_ICON.icon,
|
|
color: item.primaryCategory.color || DEFAULT_CATEGORY_COLOR,
|
|
hidden: item.primaryCategory.hidden,
|
|
displayOrders: [item.primaryCategory.type, item.primaryCategory.displayOrder],
|
|
totalAmount: item.amountInDefaultCurrency
|
|
};
|
|
}
|
|
|
|
totalAmount += item.amountInDefaultCurrency;
|
|
|
|
if (item.amountInDefaultCurrency > 0) {
|
|
totalNonNegativeAmount += item.amountInDefaultCurrency;
|
|
}
|
|
|
|
allDataItems[item.primaryCategory.id] = data;
|
|
}
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.ExpenseBySecondaryCategory.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.IncomeBySecondaryCategory.type) {
|
|
if (isNumber(item.amountInDefaultCurrency)) {
|
|
let data = allDataItems[item.category.id];
|
|
|
|
if (data) {
|
|
data.totalAmount += item.amountInDefaultCurrency;
|
|
} else {
|
|
data = {
|
|
name: item.category.name,
|
|
type: 'category',
|
|
id: item.category.id,
|
|
icon: item.category.icon || DEFAULT_CATEGORY_ICON.icon,
|
|
color: item.category.color || DEFAULT_CATEGORY_COLOR,
|
|
hidden: item.primaryCategory.hidden || item.category.hidden,
|
|
displayOrders: [item.primaryCategory.type, item.primaryCategory.displayOrder, item.category.displayOrder],
|
|
totalAmount: item.amountInDefaultCurrency
|
|
};
|
|
}
|
|
|
|
totalAmount += item.amountInDefaultCurrency;
|
|
|
|
if (item.amountInDefaultCurrency > 0) {
|
|
totalNonNegativeAmount += item.amountInDefaultCurrency;
|
|
}
|
|
|
|
allDataItems[item.category.id] = data;
|
|
}
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.TotalOutflows.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.TotalExpense.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.TotalInflows.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.TotalIncome.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.NetCashFlow.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.NetIncome.type) {
|
|
if (isNumber(item.amountInDefaultCurrency)) {
|
|
let data = allDataItems['total'];
|
|
let amount = item.amountInDefaultCurrency;
|
|
let includeInTotal: boolean = true;
|
|
|
|
if (transactionStatisticsFilter.chartDataType === ChartDataType.NetCashFlow.type &&
|
|
(item.category.type === CategoryType.Expense || (item.category.type === CategoryType.Transfer && item.relatedAccountType === TransactionRelatedAccountType.TransferTo))) {
|
|
amount = -amount;
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.NetIncome.type &&
|
|
item.category.type === CategoryType.Expense) {
|
|
amount = -amount;
|
|
}
|
|
|
|
// total outflows / inflows do not include transfer transactions between unfiltered accounts
|
|
if (transactionStatisticsFilter.chartDataType === ChartDataType.TotalOutflows.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.TotalInflows.type ||
|
|
transactionStatisticsFilter.chartDataType === ChartDataType.NetCashFlow.type) {
|
|
if (item.relatedAccount && (!transactionStatisticsFilter.filterAccountIds || !transactionStatisticsFilter.filterAccountIds[item.relatedAccount.id])) {
|
|
includeInTotal = false;
|
|
}
|
|
}
|
|
|
|
if (!data) {
|
|
let name = '';
|
|
|
|
if (transactionStatisticsFilter.chartDataType === ChartDataType.TotalOutflows.type) {
|
|
name = ChartDataType.TotalOutflows.name;
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.TotalExpense.type) {
|
|
name = ChartDataType.TotalExpense.name;
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.TotalInflows.type) {
|
|
name = ChartDataType.TotalInflows.name;
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.TotalIncome.type) {
|
|
name = ChartDataType.TotalIncome.name;
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.NetCashFlow.type) {
|
|
name = ChartDataType.NetCashFlow.name;
|
|
} else if (transactionStatisticsFilter.chartDataType === ChartDataType.NetIncome.type) {
|
|
name = ChartDataType.NetIncome.name;
|
|
}
|
|
|
|
data = {
|
|
name: name,
|
|
type: 'total',
|
|
id: 'total',
|
|
icon: '',
|
|
color: '',
|
|
hidden: false,
|
|
displayOrders: [1],
|
|
totalAmount: 0
|
|
};
|
|
}
|
|
|
|
if (includeInTotal) {
|
|
data.totalAmount += amount;
|
|
|
|
totalAmount += amount;
|
|
|
|
if (item.amountInDefaultCurrency > 0) {
|
|
totalNonNegativeAmount += amount;
|
|
}
|
|
}
|
|
|
|
allDataItems['total'] = data;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
totalAmount: totalAmount,
|
|
totalNonNegativeAmount: totalNonNegativeAmount,
|
|
items: allDataItems
|
|
};
|
|
}
|
|
|
|
function sortCategoryTotalAmountItems(items: TransactionStatisticDataItemBase[], transactionStatisticsFilter: TransactionStatisticsFilter): void {
|
|
sortStatisticsItems(items, transactionStatisticsFilter.sortingType);
|
|
}
|
|
|
|
function updateTransactionStatisticsInvalidState(invalidState: boolean): void {
|
|
transactionStatisticsStateInvalid.value = invalidState;
|
|
}
|
|
|
|
function resetTransactionStatistics(): void {
|
|
transactionStatisticsFilter.value.chartDataType = ChartDataType.Default.type;
|
|
transactionStatisticsFilter.value.categoricalChartType = CategoricalChartType.Default.type;
|
|
transactionStatisticsFilter.value.categoricalChartDateType = DEFAULT_CATEGORICAL_CHART_DATA_RANGE.type;
|
|
transactionStatisticsFilter.value.categoricalChartStartTime = 0;
|
|
transactionStatisticsFilter.value.categoricalChartEndTime = 0;
|
|
transactionStatisticsFilter.value.trendChartType = TrendChartType.Default.type;
|
|
transactionStatisticsFilter.value.trendChartDateType = DEFAULT_TREND_CHART_DATA_RANGE.type;
|
|
transactionStatisticsFilter.value.trendChartStartYearMonth = '';
|
|
transactionStatisticsFilter.value.trendChartEndYearMonth = '';
|
|
transactionStatisticsFilter.value.filterAccountIds = {};
|
|
transactionStatisticsFilter.value.filterCategoryIds = {};
|
|
transactionStatisticsFilter.value.tagIds = '';
|
|
transactionStatisticsFilter.value.tagFilterType = TransactionTagFilterType.Default.type;
|
|
transactionStatisticsFilter.value.keyword = '';
|
|
transactionCategoryStatisticsData.value = null;
|
|
transactionCategoryTrendsData.value = [];
|
|
transactionStatisticsStateInvalid.value = true;
|
|
}
|
|
|
|
function initTransactionStatisticsFilter(analysisType: StatisticsAnalysisType, filter?: TransactionStatisticsPartialFilter): void {
|
|
if (filter && isInteger(filter.chartDataType)) {
|
|
transactionStatisticsFilter.value.chartDataType = filter.chartDataType;
|
|
} else {
|
|
transactionStatisticsFilter.value.chartDataType = settingsStore.appSettings.statistics.defaultChartDataType;
|
|
}
|
|
|
|
if (analysisType === StatisticsAnalysisType.CategoricalAnalysis || analysisType === StatisticsAnalysisType.TrendAnalysis) {
|
|
if (!ChartDataType.isAvailableForAnalysisType(transactionStatisticsFilter.value.chartDataType, analysisType)) {
|
|
transactionStatisticsFilter.value.chartDataType = ChartDataType.Default.type;
|
|
}
|
|
}
|
|
|
|
if (filter && isInteger(filter.categoricalChartType)) {
|
|
transactionStatisticsFilter.value.categoricalChartType = filter.categoricalChartType;
|
|
} else {
|
|
transactionStatisticsFilter.value.categoricalChartType = settingsStore.appSettings.statistics.defaultCategoricalChartType;
|
|
}
|
|
|
|
if (!CategoricalChartType.isValidType(transactionStatisticsFilter.value.categoricalChartType)) {
|
|
transactionStatisticsFilter.value.categoricalChartType = CategoricalChartType.Default.type;
|
|
}
|
|
|
|
if (filter && isInteger(filter.categoricalChartDateType)) {
|
|
transactionStatisticsFilter.value.categoricalChartDateType = filter.categoricalChartDateType;
|
|
} else {
|
|
transactionStatisticsFilter.value.categoricalChartDateType = settingsStore.appSettings.statistics.defaultCategoricalChartDataRangeType;
|
|
}
|
|
|
|
let categoricalChartDateTypeValid = true;
|
|
|
|
if (!DateRange.isAvailableForScene(transactionStatisticsFilter.value.categoricalChartDateType, DateRangeScene.Normal)) {
|
|
transactionStatisticsFilter.value.categoricalChartDateType = DEFAULT_CATEGORICAL_CHART_DATA_RANGE.type;
|
|
categoricalChartDateTypeValid = false;
|
|
}
|
|
|
|
if (categoricalChartDateTypeValid && transactionStatisticsFilter.value.categoricalChartDateType === DateRange.Custom.type) {
|
|
if (filter && isInteger(filter.categoricalChartStartTime)) {
|
|
transactionStatisticsFilter.value.categoricalChartStartTime = filter.categoricalChartStartTime;
|
|
} else {
|
|
transactionStatisticsFilter.value.categoricalChartStartTime = 0;
|
|
}
|
|
|
|
if (filter && isInteger(filter.categoricalChartEndTime)) {
|
|
transactionStatisticsFilter.value.categoricalChartEndTime = filter.categoricalChartEndTime;
|
|
} else {
|
|
transactionStatisticsFilter.value.categoricalChartEndTime = 0;
|
|
}
|
|
} else {
|
|
const categoricalChartDateRange = getDateRangeByDateType(transactionStatisticsFilter.value.categoricalChartDateType, userStore.currentUserFirstDayOfWeek, userStore.currentUserFiscalYearStart);
|
|
|
|
if (categoricalChartDateRange) {
|
|
transactionStatisticsFilter.value.categoricalChartDateType = categoricalChartDateRange.dateType;
|
|
transactionStatisticsFilter.value.categoricalChartStartTime = categoricalChartDateRange.minTime;
|
|
transactionStatisticsFilter.value.categoricalChartEndTime = categoricalChartDateRange.maxTime;
|
|
}
|
|
}
|
|
|
|
if (filter && isInteger(filter.trendChartType)) {
|
|
transactionStatisticsFilter.value.trendChartType = filter.trendChartType;
|
|
} else {
|
|
transactionStatisticsFilter.value.trendChartType = settingsStore.appSettings.statistics.defaultTrendChartType;
|
|
}
|
|
|
|
if (!TrendChartType.isValidType(transactionStatisticsFilter.value.trendChartType)) {
|
|
transactionStatisticsFilter.value.trendChartType = TrendChartType.Default.type;
|
|
}
|
|
|
|
if (filter && isInteger(filter.trendChartDateType)) {
|
|
transactionStatisticsFilter.value.trendChartDateType = filter.trendChartDateType;
|
|
} else {
|
|
transactionStatisticsFilter.value.trendChartDateType = settingsStore.appSettings.statistics.defaultTrendChartDataRangeType;
|
|
}
|
|
|
|
let trendChartDateTypeValid = true;
|
|
|
|
if (!DateRange.isAvailableForScene(transactionStatisticsFilter.value.trendChartDateType, DateRangeScene.TrendAnalysis)) {
|
|
transactionStatisticsFilter.value.trendChartDateType = DEFAULT_TREND_CHART_DATA_RANGE.type;
|
|
trendChartDateTypeValid = false;
|
|
}
|
|
|
|
if (trendChartDateTypeValid && transactionStatisticsFilter.value.trendChartDateType === DateRange.Custom.type) {
|
|
if (filter && isYearMonth(filter.trendChartStartYearMonth)) {
|
|
transactionStatisticsFilter.value.trendChartStartYearMonth = filter.trendChartStartYearMonth;
|
|
} else {
|
|
transactionStatisticsFilter.value.trendChartStartYearMonth = '';
|
|
}
|
|
|
|
if (filter && isYearMonth(filter.trendChartEndYearMonth)) {
|
|
transactionStatisticsFilter.value.trendChartEndYearMonth = filter.trendChartEndYearMonth;
|
|
} else {
|
|
transactionStatisticsFilter.value.trendChartEndYearMonth = '';
|
|
}
|
|
} else {
|
|
const trendChartDateRange = getDateRangeByDateType(transactionStatisticsFilter.value.trendChartDateType, userStore.currentUserFirstDayOfWeek, userStore.currentUserFiscalYearStart);
|
|
|
|
if (trendChartDateRange) {
|
|
transactionStatisticsFilter.value.trendChartDateType = trendChartDateRange.dateType;
|
|
transactionStatisticsFilter.value.trendChartStartYearMonth = getGregorianCalendarYearAndMonthFromUnixTime(trendChartDateRange.minTime);
|
|
transactionStatisticsFilter.value.trendChartEndYearMonth = getGregorianCalendarYearAndMonthFromUnixTime(trendChartDateRange.maxTime);
|
|
}
|
|
}
|
|
|
|
if (filter && isObject(filter.filterAccountIds)) {
|
|
transactionStatisticsFilter.value.filterAccountIds = filter.filterAccountIds;
|
|
} else {
|
|
transactionStatisticsFilter.value.filterAccountIds = settingsStore.appSettings.statistics.defaultAccountFilter || {};
|
|
}
|
|
|
|
if (filter && isObject(filter.filterCategoryIds)) {
|
|
transactionStatisticsFilter.value.filterCategoryIds = filter.filterCategoryIds;
|
|
} else {
|
|
transactionStatisticsFilter.value.filterCategoryIds = settingsStore.appSettings.statistics.defaultTransactionCategoryFilter || {};
|
|
}
|
|
|
|
if (filter && isString(filter.tagIds)) {
|
|
transactionStatisticsFilter.value.tagIds = filter.tagIds;
|
|
} else {
|
|
transactionStatisticsFilter.value.tagIds = '';
|
|
}
|
|
|
|
if (filter && isInteger(filter.tagFilterType)) {
|
|
transactionStatisticsFilter.value.tagFilterType = filter.tagFilterType;
|
|
} else {
|
|
transactionStatisticsFilter.value.tagFilterType = TransactionTagFilterType.Default.type;
|
|
}
|
|
|
|
if (filter && isString(filter.keyword)) {
|
|
transactionStatisticsFilter.value.keyword = filter.keyword;
|
|
} else {
|
|
transactionStatisticsFilter.value.keyword = '';
|
|
}
|
|
|
|
if (filter && isInteger(filter.sortingType)) {
|
|
transactionStatisticsFilter.value.sortingType = filter.sortingType;
|
|
} else {
|
|
transactionStatisticsFilter.value.sortingType = settingsStore.appSettings.statistics.defaultSortingType;
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.sortingType < ChartSortingType.Amount.type || transactionStatisticsFilter.value.sortingType > ChartSortingType.Name.type) {
|
|
transactionStatisticsFilter.value.sortingType = ChartSortingType.Default.type;
|
|
}
|
|
}
|
|
|
|
function updateTransactionStatisticsFilter(filter: TransactionStatisticsPartialFilter): boolean {
|
|
let changed = false;
|
|
|
|
if (filter && isInteger(filter.chartDataType) && transactionStatisticsFilter.value.chartDataType !== filter.chartDataType) {
|
|
transactionStatisticsFilter.value.chartDataType = filter.chartDataType;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isInteger(filter.categoricalChartType) && transactionStatisticsFilter.value.categoricalChartType !== filter.categoricalChartType) {
|
|
transactionStatisticsFilter.value.categoricalChartType = filter.categoricalChartType;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isInteger(filter.categoricalChartDateType) && transactionStatisticsFilter.value.categoricalChartDateType !== filter.categoricalChartDateType) {
|
|
transactionStatisticsFilter.value.categoricalChartDateType = filter.categoricalChartDateType;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isInteger(filter.categoricalChartStartTime) && transactionStatisticsFilter.value.categoricalChartStartTime !== filter.categoricalChartStartTime) {
|
|
transactionStatisticsFilter.value.categoricalChartStartTime = filter.categoricalChartStartTime;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isInteger(filter.categoricalChartEndTime) && transactionStatisticsFilter.value.categoricalChartEndTime !== filter.categoricalChartEndTime) {
|
|
transactionStatisticsFilter.value.categoricalChartEndTime = filter.categoricalChartEndTime;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isInteger(filter.trendChartType) && transactionStatisticsFilter.value.trendChartType !== filter.trendChartType) {
|
|
transactionStatisticsFilter.value.trendChartType = filter.trendChartType;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isInteger(filter.trendChartDateType) && transactionStatisticsFilter.value.trendChartDateType !== filter.trendChartDateType) {
|
|
transactionStatisticsFilter.value.trendChartDateType = filter.trendChartDateType;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && (isYearMonth(filter.trendChartStartYearMonth) || filter.trendChartStartYearMonth === '') && !isYearMonthEquals(transactionStatisticsFilter.value.trendChartStartYearMonth, filter.trendChartStartYearMonth)) {
|
|
transactionStatisticsFilter.value.trendChartStartYearMonth = filter.trendChartStartYearMonth;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && (isYearMonth(filter.trendChartEndYearMonth) || filter.trendChartEndYearMonth === '') && !isYearMonthEquals(transactionStatisticsFilter.value.trendChartEndYearMonth, filter.trendChartEndYearMonth)) {
|
|
transactionStatisticsFilter.value.trendChartEndYearMonth = filter.trendChartEndYearMonth;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isObject(filter.filterAccountIds) && !isEquals(transactionStatisticsFilter.value.filterAccountIds, filter.filterAccountIds)) {
|
|
transactionStatisticsFilter.value.filterAccountIds = filter.filterAccountIds;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isObject(filter.filterCategoryIds) && !isEquals(transactionStatisticsFilter.value.filterCategoryIds, filter.filterCategoryIds)) {
|
|
transactionStatisticsFilter.value.filterCategoryIds = filter.filterCategoryIds;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isString(filter.tagIds) && transactionStatisticsFilter.value.tagIds !== filter.tagIds) {
|
|
transactionStatisticsFilter.value.tagIds = filter.tagIds;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isInteger(filter.tagFilterType) && transactionStatisticsFilter.value.tagFilterType !== filter.tagFilterType) {
|
|
transactionStatisticsFilter.value.tagFilterType = filter.tagFilterType;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isString(filter.keyword) && transactionStatisticsFilter.value.keyword !== filter.keyword) {
|
|
transactionStatisticsFilter.value.keyword = filter.keyword;
|
|
changed = true;
|
|
}
|
|
|
|
if (filter && isInteger(filter.sortingType) && transactionStatisticsFilter.value.sortingType !== filter.sortingType) {
|
|
transactionStatisticsFilter.value.sortingType = filter.sortingType;
|
|
changed = true;
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
function getTransactionStatisticsPageParams(analysisType: StatisticsAnalysisType, trendDateAggregationType: number): string {
|
|
const querys: string[] = [];
|
|
|
|
querys.push('analysisType=' + analysisType);
|
|
querys.push('chartDataType=' + transactionStatisticsFilter.value.chartDataType);
|
|
|
|
if (analysisType === StatisticsAnalysisType.CategoricalAnalysis) {
|
|
querys.push('chartType=' + transactionStatisticsFilter.value.categoricalChartType);
|
|
querys.push('chartDateType=' + transactionStatisticsFilter.value.categoricalChartDateType);
|
|
|
|
if (transactionStatisticsFilter.value.categoricalChartDateType === DateRange.Custom.type) {
|
|
querys.push('startTime=' + transactionStatisticsFilter.value.categoricalChartStartTime);
|
|
querys.push('endTime=' + transactionStatisticsFilter.value.categoricalChartEndTime);
|
|
}
|
|
} else if (analysisType === StatisticsAnalysisType.TrendAnalysis) {
|
|
querys.push('chartType=' + transactionStatisticsFilter.value.trendChartType);
|
|
querys.push('chartDateType=' + transactionStatisticsFilter.value.trendChartDateType);
|
|
|
|
if (transactionStatisticsFilter.value.trendChartDateType === DateRange.Custom.type) {
|
|
querys.push('startTime=' + transactionStatisticsFilter.value.trendChartStartYearMonth);
|
|
querys.push('endTime=' + transactionStatisticsFilter.value.trendChartEndYearMonth);
|
|
}
|
|
|
|
if (trendDateAggregationType !== ChartDateAggregationType.Month.type) {
|
|
querys.push('trendDateAggregationType=' + trendDateAggregationType);
|
|
}
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.filterAccountIds) {
|
|
const ids = objectFieldToArrayItem(transactionStatisticsFilter.value.filterAccountIds);
|
|
|
|
if (ids && ids.length) {
|
|
querys.push('filterAccountIds=' + ids.join(','));
|
|
}
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.filterCategoryIds) {
|
|
const ids = objectFieldToArrayItem(transactionStatisticsFilter.value.filterCategoryIds);
|
|
|
|
if (ids && ids.length) {
|
|
querys.push('filterCategoryIds=' + ids.join(','));
|
|
}
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.tagIds) {
|
|
querys.push('tagIds=' + transactionStatisticsFilter.value.tagIds);
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.tagFilterType) {
|
|
querys.push('tagFilterType=' + transactionStatisticsFilter.value.tagFilterType);
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.keyword) {
|
|
querys.push('keyword=' + encodeURIComponent(transactionStatisticsFilter.value.keyword));
|
|
}
|
|
|
|
querys.push('sortingType=' + transactionStatisticsFilter.value.sortingType);
|
|
|
|
return querys.join('&');
|
|
}
|
|
|
|
function getTransactionListPageParams(analysisType: StatisticsAnalysisType, itemId: string, dateRange?: TimeRangeAndDateType): string {
|
|
const querys: string[] = [];
|
|
|
|
if (transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByAccount.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByPrimaryCategory.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeBySecondaryCategory.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.TotalIncome.type) {
|
|
querys.push('type=2');
|
|
} else if (transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByAccount.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByPrimaryCategory.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseBySecondaryCategory.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.TotalExpense.type) {
|
|
querys.push('type=3');
|
|
}
|
|
|
|
if (itemId && transactionStatisticsFilter.value.chartDataType === ChartDataType.Overview.type) {
|
|
const items = itemId.split('-');
|
|
const sourceItems = (items[0] || '').split(':');
|
|
const queryAccountIds: string[] = [];
|
|
const queryCategoryIds: string[] = [];
|
|
|
|
if (sourceItems.length === 2) {
|
|
if (sourceItems[0] === 'account') {
|
|
queryAccountIds.push(sourceItems[1] as string);
|
|
} else if (sourceItems[0] === 'category') {
|
|
queryCategoryIds.push(sourceItems[1] as string);
|
|
}
|
|
}
|
|
|
|
if (items.length === 2) {
|
|
const targetItems = (items[1] || '').split(':');
|
|
|
|
if (targetItems.length === 2) {
|
|
if (targetItems[0] === 'account') {
|
|
queryAccountIds.push(targetItems[1] as string);
|
|
} else if (targetItems[0] === 'category') {
|
|
queryCategoryIds.push(targetItems[1] as string);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (queryAccountIds.length) {
|
|
if (queryAccountIds.length === 2) {
|
|
querys.push('type=4');
|
|
}
|
|
|
|
querys.push('accountIds=' + queryAccountIds.join(','));
|
|
} else {
|
|
querys.push('accountIds=' + getFinalAccountIdsByFilteredAccountIds(accountsStore.allAccountsMap, transactionStatisticsFilter.value.filterAccountIds));
|
|
}
|
|
|
|
if (queryCategoryIds.length) {
|
|
querys.push('categoryIds=' + queryCategoryIds.join(','));
|
|
} else {
|
|
querys.push('categoryIds=' + getFinalCategoryIdsByFilteredCategoryIds(transactionCategoriesStore.allTransactionCategoriesMap, transactionStatisticsFilter.value.filterCategoryIds));
|
|
}
|
|
} else if (itemId && (transactionStatisticsFilter.value.chartDataType === ChartDataType.InflowsByAccount.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByAccount.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.OutflowsByAccount.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByAccount.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalAssets.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.AccountTotalLiabilities.type)) {
|
|
querys.push('accountIds=' + itemId);
|
|
|
|
if (!isObjectEmpty(transactionStatisticsFilter.value.filterCategoryIds)) {
|
|
querys.push('categoryIds=' + getFinalCategoryIdsByFilteredCategoryIds(transactionCategoriesStore.allTransactionCategoriesMap, transactionStatisticsFilter.value.filterCategoryIds));
|
|
}
|
|
} else if (itemId && (transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeByPrimaryCategory.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.IncomeBySecondaryCategory.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseByPrimaryCategory.type
|
|
|| transactionStatisticsFilter.value.chartDataType === ChartDataType.ExpenseBySecondaryCategory.type)) {
|
|
querys.push('categoryIds=' + itemId);
|
|
|
|
if (!isObjectEmpty(transactionStatisticsFilter.value.filterAccountIds)) {
|
|
querys.push('accountIds=' + getFinalAccountIdsByFilteredAccountIds(accountsStore.allAccountsMap, transactionStatisticsFilter.value.filterAccountIds));
|
|
}
|
|
} else if (!itemId) {
|
|
if (!isObjectEmpty(transactionStatisticsFilter.value.filterCategoryIds)) {
|
|
querys.push('categoryIds=' + getFinalCategoryIdsByFilteredCategoryIds(transactionCategoriesStore.allTransactionCategoriesMap, transactionStatisticsFilter.value.filterCategoryIds));
|
|
}
|
|
|
|
if (!isObjectEmpty(transactionStatisticsFilter.value.filterAccountIds)) {
|
|
querys.push('accountIds=' + getFinalAccountIdsByFilteredAccountIds(accountsStore.allAccountsMap, transactionStatisticsFilter.value.filterAccountIds));
|
|
}
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.tagIds) {
|
|
querys.push('tagIds=' + transactionStatisticsFilter.value.tagIds);
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.tagFilterType) {
|
|
querys.push('tagFilterType=' + transactionStatisticsFilter.value.tagFilterType);
|
|
}
|
|
|
|
if (transactionStatisticsFilter.value.keyword) {
|
|
querys.push('keyword=' + encodeURIComponent(transactionStatisticsFilter.value.keyword));
|
|
}
|
|
|
|
if (analysisType === StatisticsAnalysisType.CategoricalAnalysis
|
|
&& transactionStatisticsFilter.value.chartDataType !== ChartDataType.AccountTotalAssets.type
|
|
&& transactionStatisticsFilter.value.chartDataType !== ChartDataType.AccountTotalLiabilities.type) {
|
|
querys.push('dateType=' + transactionStatisticsFilter.value.categoricalChartDateType);
|
|
|
|
if (transactionStatisticsFilter.value.categoricalChartDateType === DateRange.Custom.type) {
|
|
querys.push('minTime=' + transactionStatisticsFilter.value.categoricalChartStartTime);
|
|
querys.push('maxTime=' + transactionStatisticsFilter.value.categoricalChartEndTime);
|
|
}
|
|
} else if (analysisType === StatisticsAnalysisType.TrendAnalysis && dateRange) {
|
|
querys.push('dateType=' + dateRange.dateType);
|
|
querys.push('minTime=' + dateRange.minTime);
|
|
querys.push('maxTime=' + dateRange.maxTime);
|
|
}
|
|
|
|
return querys.join('&');
|
|
}
|
|
|
|
function loadCategoricalAnalysis({ force }: { force: boolean }): Promise<TransactionStatisticResponse> {
|
|
return new Promise((resolve, reject) => {
|
|
services.getTransactionStatistics({
|
|
startTime: transactionStatisticsFilter.value.categoricalChartStartTime,
|
|
endTime: transactionStatisticsFilter.value.categoricalChartEndTime,
|
|
tagIds: transactionStatisticsFilter.value.tagIds,
|
|
tagFilterType: transactionStatisticsFilter.value.tagFilterType,
|
|
keyword: transactionStatisticsFilter.value.keyword,
|
|
useTransactionTimezone: settingsStore.appSettings.statistics.defaultTimezoneType === TimezoneTypeForStatistics.TransactionTimezone.type
|
|
}).then(response => {
|
|
const data = response.data;
|
|
|
|
if (!data || !data.success || !data.result) {
|
|
reject({ message: 'Unable to retrieve transaction statistics' });
|
|
return;
|
|
}
|
|
|
|
if (transactionStatisticsStateInvalid.value) {
|
|
updateTransactionStatisticsInvalidState(false);
|
|
}
|
|
|
|
if (force && data.result && isEquals(transactionCategoryStatisticsData.value, data.result)) {
|
|
reject({ message: 'Data is up to date', isUpToDate: true });
|
|
return;
|
|
}
|
|
|
|
transactionCategoryStatisticsData.value = data.result;
|
|
|
|
resolve(data.result);
|
|
}).catch(error => {
|
|
logger.error('failed to retrieve transaction statistics', 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 statistics' });
|
|
} else {
|
|
reject(error);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
function loadTrendAnalysis({ force }: { force: boolean }): Promise<TransactionStatisticTrendsResponseItem[]> {
|
|
return new Promise((resolve, reject) => {
|
|
services.getTransactionStatisticsTrends({
|
|
startYearMonth: transactionStatisticsFilter.value.trendChartStartYearMonth,
|
|
endYearMonth: transactionStatisticsFilter.value.trendChartEndYearMonth,
|
|
tagIds: transactionStatisticsFilter.value.tagIds,
|
|
tagFilterType: transactionStatisticsFilter.value.tagFilterType,
|
|
keyword: transactionStatisticsFilter.value.keyword,
|
|
useTransactionTimezone: settingsStore.appSettings.statistics.defaultTimezoneType === TimezoneTypeForStatistics.TransactionTimezone.type
|
|
}).then(response => {
|
|
const data = response.data;
|
|
|
|
if (!data || !data.success || !data.result) {
|
|
reject({ message: 'Unable to retrieve transaction statistics' });
|
|
return;
|
|
}
|
|
|
|
if (transactionStatisticsStateInvalid.value) {
|
|
updateTransactionStatisticsInvalidState(false);
|
|
}
|
|
|
|
if (force && data.result && isEquals(transactionCategoryTrendsData.value, data.result)) {
|
|
reject({ message: 'Data is up to date', isUpToDate: true });
|
|
return;
|
|
}
|
|
|
|
transactionCategoryTrendsData.value = data.result;
|
|
|
|
resolve(data.result);
|
|
}).catch(error => {
|
|
logger.error('failed to retrieve transaction statistics', 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 statistics' });
|
|
} else {
|
|
reject(error);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
return {
|
|
// states
|
|
transactionStatisticsFilter,
|
|
transactionCategoryStatisticsData,
|
|
transactionCategoryTrendsData,
|
|
transactionStatisticsStateInvalid,
|
|
// computed states
|
|
categoricalAnalysisChartDataCategory,
|
|
categoricalOverviewAnalysisData,
|
|
categoricalAnalysisData,
|
|
trendsAnalysisData,
|
|
// functions
|
|
updateTransactionStatisticsInvalidState,
|
|
resetTransactionStatistics,
|
|
initTransactionStatisticsFilter,
|
|
updateTransactionStatisticsFilter,
|
|
getTransactionStatisticsPageParams,
|
|
getTransactionListPageParams,
|
|
loadCategoricalAnalysis,
|
|
loadTrendAnalysis
|
|
};
|
|
});
|