mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-16 07:57:33 +08:00
migrate transaction category store to composition API and typescript
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@ import { defineStore } from 'pinia';
|
||||
import { useSettingsStore } from './setting.ts';
|
||||
import { useUserStore } from './user.ts';
|
||||
import { useAccountsStore } from './account.js';
|
||||
import { useTransactionCategoriesStore } from './transactionCategory.js';
|
||||
import { useTransactionCategoriesStore } from './transactionCategory.ts';
|
||||
import { useTransactionTagsStore } from './transactionTag.ts';
|
||||
import { useTransactionTemplatesStore } from './transactionTemplate.js';
|
||||
import { useTransactionsStore } from './transaction.js';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineStore } from 'pinia';
|
||||
import { useSettingsStore } from './setting.ts';
|
||||
import { useUserStore } from './user.ts';
|
||||
import { useAccountsStore } from './account.js';
|
||||
import { useTransactionCategoriesStore } from './transactionCategory.js';
|
||||
import { useTransactionCategoriesStore } from './transactionCategory.ts';
|
||||
import { useExchangeRatesStore } from './exchangeRates.ts';
|
||||
|
||||
import { DateRangeScene, DateRange } from '@/core/datetime';
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
} from '@/lib/account.js';
|
||||
import {
|
||||
getFinalCategoryIdsByFilteredCategoryIds
|
||||
} from '@/lib/category.js';
|
||||
} from '@/lib/category.ts';
|
||||
import {
|
||||
sortStatisticsItems
|
||||
} from '@/lib/statistics.ts';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineStore } from 'pinia';
|
||||
import { useSettingsStore } from './setting.ts';
|
||||
import { useUserStore } from './user.ts';
|
||||
import { useAccountsStore } from './account.js';
|
||||
import { useTransactionCategoriesStore } from './transactionCategory.js';
|
||||
import { useTransactionCategoriesStore } from './transactionCategory.ts';
|
||||
import { useOverviewStore } from './overview.ts';
|
||||
import { useStatisticsStore } from './statistics.js';
|
||||
import { useExchangeRatesStore } from './exchangeRates.ts';
|
||||
@@ -39,7 +39,7 @@ import {
|
||||
} from '@/lib/datetime.ts';
|
||||
import { getAmountWithDecimalNumberCount } from '@/lib/numeral.ts';
|
||||
import { getCurrencyFraction } from '@/lib/currency.ts';
|
||||
import { getFirstAvailableCategoryId } from '@/lib/category.js';
|
||||
import { getFirstAvailableCategoryId } from '@/lib/category.ts';
|
||||
|
||||
const emptyTransactionResult = {
|
||||
items: [],
|
||||
|
||||
@@ -1,533 +0,0 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { CategoryType } from '@/core/category.ts';
|
||||
import { DEFAULT_CATEGORY_ICON_ID } from '@/consts/icon.ts';
|
||||
import { DEFAULT_CATEGORY_COLOR } from '@/consts/color.ts';
|
||||
import { isEquals } from '@/lib/common.ts';
|
||||
import services from '@/lib/services.ts';
|
||||
import logger from '@/lib/logger.ts';
|
||||
|
||||
function loadTransactionCategoryList(state, allCategories) {
|
||||
state.allTransactionCategories = allCategories;
|
||||
state.allTransactionCategoriesMap = {};
|
||||
|
||||
for (let categoryType in allCategories) {
|
||||
if (!Object.prototype.hasOwnProperty.call(allCategories, categoryType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const categories = allCategories[categoryType];
|
||||
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
const category = categories[i];
|
||||
state.allTransactionCategoriesMap[category.id] = category;
|
||||
|
||||
for (let j = 0; j < category.subCategories.length; j++) {
|
||||
const subCategory = category.subCategories[j];
|
||||
state.allTransactionCategoriesMap[subCategory.id] = subCategory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addCategoryToTransactionCategoryList(state, category) {
|
||||
let categoryList = null;
|
||||
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
categoryList = state.allTransactionCategories[category.type];
|
||||
} else if (state.allTransactionCategoriesMap[category.parentId]) {
|
||||
categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
|
||||
}
|
||||
|
||||
if (categoryList) {
|
||||
categoryList.push(category);
|
||||
}
|
||||
|
||||
state.allTransactionCategoriesMap[category.id] = category;
|
||||
}
|
||||
|
||||
function updateCategoryInTransactionCategoryList(state, category, oldCategory) {
|
||||
if (oldCategory && category.parentId !== oldCategory.parentId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let categoryList = null;
|
||||
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
categoryList = state.allTransactionCategories[category.type];
|
||||
} else if (state.allTransactionCategoriesMap[category.parentId]) {
|
||||
categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
|
||||
}
|
||||
|
||||
if (categoryList) {
|
||||
for (let i = 0; i < categoryList.length; i++) {
|
||||
if (categoryList[i].id === category.id) {
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
category.subCategories = categoryList[i].subCategories;
|
||||
}
|
||||
|
||||
categoryList.splice(i, 1, category);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.allTransactionCategoriesMap[category.id] = category;
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateCategoryDisplayOrderInCategoryList(state, { category, from, to }) {
|
||||
let categoryList = null;
|
||||
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
categoryList = state.allTransactionCategories[category.type];
|
||||
} else if (state.allTransactionCategoriesMap[category.parentId]) {
|
||||
categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
|
||||
}
|
||||
|
||||
if (categoryList) {
|
||||
categoryList.splice(to, 0, categoryList.splice(from, 1)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCategoryVisibilityInTransactionCategoryList(state, { category, hidden }) {
|
||||
if (state.allTransactionCategoriesMap[category.id]) {
|
||||
state.allTransactionCategoriesMap[category.id].hidden = hidden;
|
||||
}
|
||||
}
|
||||
|
||||
function removeCategoryFromTransactionCategoryList(state, category) {
|
||||
let categoryList = null;
|
||||
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
categoryList = state.allTransactionCategories[category.type];
|
||||
} else if (state.allTransactionCategoriesMap[category.parentId]) {
|
||||
categoryList = state.allTransactionCategoriesMap[category.parentId].subCategories;
|
||||
}
|
||||
|
||||
if (categoryList) {
|
||||
for (let i = 0; i < categoryList.length; i++) {
|
||||
if (categoryList[i].id === category.id) {
|
||||
categoryList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.allTransactionCategoriesMap[category.id] && state.allTransactionCategoriesMap[category.id].subCategories) {
|
||||
const subCategories = state.allTransactionCategoriesMap[category.id].subCategories;
|
||||
|
||||
for (let i = 0; i < subCategories.length; i++) {
|
||||
const subCategory = subCategories[i];
|
||||
if (state.allTransactionCategoriesMap[subCategory.id]) {
|
||||
delete state.allTransactionCategoriesMap[subCategory.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.allTransactionCategoriesMap[category.id]) {
|
||||
delete state.allTransactionCategoriesMap[category.id];
|
||||
}
|
||||
}
|
||||
|
||||
export const useTransactionCategoriesStore = defineStore('transactionCategories', {
|
||||
state: () => ({
|
||||
allTransactionCategories: {},
|
||||
allTransactionCategoriesMap: {},
|
||||
transactionCategoryListStateInvalid: true,
|
||||
}),
|
||||
actions: {
|
||||
generateNewTransactionCategoryModel(type, parentId) {
|
||||
return {
|
||||
type: type || CategoryType.Income,
|
||||
name: '',
|
||||
parentId: parentId || '0',
|
||||
icon: DEFAULT_CATEGORY_ICON_ID,
|
||||
color: DEFAULT_CATEGORY_COLOR,
|
||||
comment: '',
|
||||
visible: true
|
||||
};
|
||||
},
|
||||
updateTransactionCategoryListInvalidState(invalidState) {
|
||||
this.transactionCategoryListStateInvalid = invalidState;
|
||||
},
|
||||
resetTransactionCategories() {
|
||||
this.allTransactionCategories = {};
|
||||
this.allTransactionCategoriesMap = {};
|
||||
this.transactionCategoryListStateInvalid = true;
|
||||
},
|
||||
loadAllCategories({ force }) {
|
||||
const self = this;
|
||||
|
||||
if (!force && !self.transactionCategoryListStateInvalid) {
|
||||
return new Promise((resolve) => {
|
||||
resolve(self.allTransactionCategories);
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getAllTransactionCategories().then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to retrieve category list' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.result[CategoryType.Income]) {
|
||||
data.result[CategoryType.Income] = [];
|
||||
}
|
||||
|
||||
if (!data.result[CategoryType.Expense]) {
|
||||
data.result[CategoryType.Expense] = [];
|
||||
}
|
||||
|
||||
if (!data.result[CategoryType.Transfer]) {
|
||||
data.result[CategoryType.Transfer] = [];
|
||||
}
|
||||
|
||||
for (let categoryType in data.result) {
|
||||
if (!Object.prototype.hasOwnProperty.call(data.result, categoryType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const categories = data.result[categoryType];
|
||||
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
const category = categories[i];
|
||||
|
||||
if (!category.subCategories) {
|
||||
category.subCategories = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.transactionCategoryListStateInvalid) {
|
||||
self.updateTransactionCategoryListInvalidState(false);
|
||||
}
|
||||
|
||||
if (force && data.result && isEquals(self.allTransactionCategories, data.result)) {
|
||||
reject({ message: 'Category list is up to date' });
|
||||
return;
|
||||
}
|
||||
|
||||
loadTransactionCategoryList(self, data.result);
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
if (force) {
|
||||
logger.error('failed to force load category list', error);
|
||||
} else {
|
||||
logger.error('failed to load category list', 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 category list' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
getCategory({ categoryId }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getTransactionCategory({
|
||||
id: categoryId
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to retrieve category' });
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to load category info', 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 category' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
saveCategory({ category, isEdit, clientSessionId }) {
|
||||
const self = this;
|
||||
|
||||
const submitCategory = {
|
||||
type: category.type,
|
||||
name: category.name,
|
||||
parentId: category.parentId,
|
||||
icon: category.icon,
|
||||
color: category.color,
|
||||
comment: category.comment
|
||||
};
|
||||
|
||||
if (clientSessionId) {
|
||||
submitCategory.clientSessionId = clientSessionId;
|
||||
}
|
||||
|
||||
if (isEdit) {
|
||||
submitCategory.id = category.id;
|
||||
submitCategory.hidden = !category.visible;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let promise = null;
|
||||
|
||||
if (!submitCategory.id) {
|
||||
promise = services.addTransactionCategory(submitCategory);
|
||||
} else {
|
||||
promise = services.modifyTransactionCategory(submitCategory);
|
||||
}
|
||||
|
||||
promise.then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
if (!submitCategory.id) {
|
||||
reject({ message: 'Unable to add category' });
|
||||
} else {
|
||||
reject({ message: 'Unable to save category' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.result.subCategories) {
|
||||
data.result.subCategories = [];
|
||||
}
|
||||
|
||||
if (!submitCategory.id) {
|
||||
addCategoryToTransactionCategoryList(self, data.result);
|
||||
} else {
|
||||
const result = updateCategoryInTransactionCategoryList(self, data.result, self.allTransactionCategoriesMap[submitCategory.id]);
|
||||
|
||||
if (!result && !self.transactionCategoryListStateInvalid) {
|
||||
self.updateTransactionCategoryListInvalidState(true);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to save category', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
if (!submitCategory.id) {
|
||||
reject({ message: 'Unable to add category' });
|
||||
} else {
|
||||
reject({ message: 'Unable to save category' });
|
||||
}
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
addCategories({ categories }) {
|
||||
const self = this;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.addTransactionCategoryBatch({
|
||||
categories: categories
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to add preset categories' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self.transactionCategoryListStateInvalid) {
|
||||
self.updateTransactionCategoryListInvalidState(true);
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to add preset categories', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to add preset categories' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
changeCategoryDisplayOrder({ categoryId, from, to }) {
|
||||
const self = this;
|
||||
const category = self.allTransactionCategoriesMap[categoryId];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!category) {
|
||||
reject({ message: 'Unable to move category' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
if (!self.allTransactionCategories[category.type] ||
|
||||
!self.allTransactionCategories[category.type][to]) {
|
||||
reject({ message: 'Unable to move category' });
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!self.allTransactionCategoriesMap[category.parentId].subCategories ||
|
||||
!self.allTransactionCategoriesMap[category.parentId].subCategories[to]) {
|
||||
reject({ message: 'Unable to move category' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.transactionCategoryListStateInvalid) {
|
||||
self.updateTransactionCategoryListInvalidState(true);
|
||||
}
|
||||
|
||||
updateCategoryDisplayOrderInCategoryList(self, {
|
||||
category: category,
|
||||
from: from,
|
||||
to: to
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
updateCategoryDisplayOrders({ type, parentId }) {
|
||||
const self = this;
|
||||
const newDisplayOrders = [];
|
||||
|
||||
let categoryList = null;
|
||||
|
||||
if (!parentId || parentId === '0') {
|
||||
categoryList = self.allTransactionCategories[type];
|
||||
} else if (self.allTransactionCategoriesMap[parentId]) {
|
||||
categoryList = self.allTransactionCategoriesMap[parentId].subCategories;
|
||||
}
|
||||
|
||||
if (categoryList) {
|
||||
for (let i = 0; i < categoryList.length; i++) {
|
||||
newDisplayOrders.push({
|
||||
id: categoryList[i].id,
|
||||
displayOrder: i + 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.moveTransactionCategory({
|
||||
newDisplayOrders: newDisplayOrders
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to move category' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.transactionCategoryListStateInvalid) {
|
||||
self.updateTransactionCategoryListInvalidState(false);
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to save categories display order', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to move category' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
hideCategory({ category, hidden }) {
|
||||
const self = this;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.hideTransactionCategory({
|
||||
id: category.id,
|
||||
hidden: hidden
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
if (hidden) {
|
||||
reject({ message: 'Unable to hide this category' });
|
||||
} else {
|
||||
reject({ message: 'Unable to unhide this category' });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateCategoryVisibilityInTransactionCategoryList(self, {
|
||||
category: category,
|
||||
hidden: hidden
|
||||
});
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to change category visibility', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
if (hidden) {
|
||||
reject({ message: 'Unable to hide this category' });
|
||||
} else {
|
||||
reject({ message: 'Unable to unhide this category' });
|
||||
}
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
deleteCategory({ category, beforeResolve }) {
|
||||
const self = this;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.deleteTransactionCategory({
|
||||
id: category.id
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to delete this category' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (beforeResolve) {
|
||||
beforeResolve(() => {
|
||||
removeCategoryFromTransactionCategoryList(self, category);
|
||||
});
|
||||
} else {
|
||||
removeCategoryFromTransactionCategoryList(self, category);
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to delete category', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to delete this category' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,518 @@
|
||||
import { ref } from 'vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import type { BeforeResolveFunction } from '@/core/base.ts';
|
||||
|
||||
import { CategoryType } from '@/core/category.ts';
|
||||
|
||||
import {
|
||||
type TransactionCategoryInfoResponse,
|
||||
type TransactionCategoryCreateBatchRequest,
|
||||
type TransactionCategoryNewDisplayOrderRequest,
|
||||
TransactionCategory,
|
||||
} from '@/models/transaction_category.ts';
|
||||
|
||||
import { isEquals } from '@/lib/common.ts';
|
||||
import services, { type ApiResponsePromise } from '@/lib/services.ts';
|
||||
import logger from '@/lib/logger.ts';
|
||||
|
||||
export const useTransactionCategoriesStore = defineStore('transactionCategories', () =>{
|
||||
const allTransactionCategories = ref<Record<number, TransactionCategory[]>>({});
|
||||
const allTransactionCategoriesMap = ref<Record<string, TransactionCategory>>({});
|
||||
const transactionCategoryListStateInvalid = ref<boolean>(true);
|
||||
|
||||
function loadTransactionCategoryList(allCategories: Record<number, TransactionCategory[]>): void {
|
||||
allTransactionCategories.value = allCategories;
|
||||
allTransactionCategoriesMap.value = {};
|
||||
|
||||
for (const categoryType in allCategories) {
|
||||
if (!Object.prototype.hasOwnProperty.call(allCategories, categoryType)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const categories = allCategories[categoryType];
|
||||
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
const category = categories[i];
|
||||
allTransactionCategoriesMap.value[category.id] = category;
|
||||
|
||||
if (!category.secondaryCategories) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let j = 0; j < category.secondaryCategories.length; j++) {
|
||||
const subCategory = category.secondaryCategories[j];
|
||||
allTransactionCategoriesMap.value[subCategory.id] = subCategory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addCategoryToTransactionCategoryList(category: TransactionCategory): void {
|
||||
let categoryList: TransactionCategory[] | undefined = undefined;
|
||||
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
categoryList = allTransactionCategories.value[category.type];
|
||||
} else if (allTransactionCategoriesMap.value[category.parentId]) {
|
||||
categoryList = allTransactionCategoriesMap.value[category.parentId].secondaryCategories;
|
||||
}
|
||||
|
||||
if (categoryList) {
|
||||
categoryList.push(category);
|
||||
}
|
||||
|
||||
allTransactionCategoriesMap.value[category.id] = category;
|
||||
}
|
||||
|
||||
function updateCategoryInTransactionCategoryList(category: TransactionCategory, oldCategory: TransactionCategory): boolean {
|
||||
if (oldCategory && category.parentId !== oldCategory.parentId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let categoryList: TransactionCategory[] | undefined = undefined;
|
||||
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
categoryList = allTransactionCategories.value[category.type];
|
||||
} else if (allTransactionCategoriesMap.value[category.parentId]) {
|
||||
categoryList = allTransactionCategoriesMap.value[category.parentId].secondaryCategories;
|
||||
}
|
||||
|
||||
if (categoryList) {
|
||||
for (let i = 0; i < categoryList.length; i++) {
|
||||
if (categoryList[i].id === category.id) {
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
category.secondaryCategories = categoryList[i].secondaryCategories;
|
||||
}
|
||||
|
||||
categoryList.splice(i, 1, category);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allTransactionCategoriesMap.value[category.id] = category;
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateCategoryDisplayOrderInCategoryList(params: { category: TransactionCategory, from: number, to: number }): void {
|
||||
let categoryList: TransactionCategory[] | undefined = undefined;
|
||||
|
||||
if (!params.category.parentId || params.category.parentId === '0') {
|
||||
categoryList = allTransactionCategories.value[params.category.type];
|
||||
} else if (allTransactionCategoriesMap.value[params.category.parentId]) {
|
||||
categoryList = allTransactionCategoriesMap.value[params.category.parentId].secondaryCategories;
|
||||
}
|
||||
|
||||
if (categoryList) {
|
||||
categoryList.splice(params.to, 0, categoryList.splice(params.from, 1)[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCategoryVisibilityInTransactionCategoryList(params: { category: TransactionCategory, hidden: boolean }): void {
|
||||
if (allTransactionCategoriesMap.value[params.category.id]) {
|
||||
allTransactionCategoriesMap.value[params.category.id].visible = !params.hidden;
|
||||
}
|
||||
}
|
||||
|
||||
function removeCategoryFromTransactionCategoryList(category: TransactionCategory): void {
|
||||
let categoryList: TransactionCategory[] | undefined = undefined;
|
||||
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
categoryList = allTransactionCategories.value[category.type];
|
||||
} else if (allTransactionCategoriesMap.value[category.parentId]) {
|
||||
categoryList = allTransactionCategoriesMap.value[category.parentId].secondaryCategories;
|
||||
}
|
||||
|
||||
if (categoryList) {
|
||||
for (let i = 0; i < categoryList.length; i++) {
|
||||
if (categoryList[i].id === category.id) {
|
||||
categoryList.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allTransactionCategoriesMap.value[category.id] && allTransactionCategoriesMap.value[category.id].secondaryCategories) {
|
||||
const subCategoryList = allTransactionCategoriesMap.value[category.id].secondaryCategories;
|
||||
|
||||
if (subCategoryList) {
|
||||
for (let i = 0; i < subCategoryList.length; i++) {
|
||||
const subCategory = subCategoryList[i];
|
||||
if (allTransactionCategoriesMap.value[subCategory.id]) {
|
||||
delete allTransactionCategoriesMap.value[subCategory.id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allTransactionCategoriesMap.value[category.id]) {
|
||||
delete allTransactionCategoriesMap.value[category.id];
|
||||
}
|
||||
}
|
||||
|
||||
function updateTransactionCategoryListInvalidState(invalidState: boolean): void {
|
||||
transactionCategoryListStateInvalid.value = invalidState;
|
||||
}
|
||||
|
||||
function resetTransactionCategories(): void {
|
||||
allTransactionCategories.value = {};
|
||||
allTransactionCategoriesMap.value = {};
|
||||
transactionCategoryListStateInvalid.value = true;
|
||||
}
|
||||
|
||||
function loadAllCategories(params: { force?: boolean }): Promise<Record<number, TransactionCategory[]>> {
|
||||
if (!params.force && !transactionCategoryListStateInvalid.value) {
|
||||
return new Promise((resolve) => {
|
||||
resolve(allTransactionCategories.value);
|
||||
});
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getAllTransactionCategories().then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to retrieve category list' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.result[CategoryType.Income]) {
|
||||
data.result[CategoryType.Income] = [];
|
||||
}
|
||||
|
||||
if (!data.result[CategoryType.Expense]) {
|
||||
data.result[CategoryType.Expense] = [];
|
||||
}
|
||||
|
||||
if (!data.result[CategoryType.Transfer]) {
|
||||
data.result[CategoryType.Transfer] = [];
|
||||
}
|
||||
|
||||
if (transactionCategoryListStateInvalid.value) {
|
||||
updateTransactionCategoryListInvalidState(false);
|
||||
}
|
||||
|
||||
const transactionCategories = TransactionCategory.ofMap(data.result);
|
||||
|
||||
if (params.force && data.result && isEquals(allTransactionCategories.value, transactionCategories)) {
|
||||
reject({ message: 'Category list is up to date' });
|
||||
return;
|
||||
}
|
||||
|
||||
loadTransactionCategoryList(transactionCategories);
|
||||
|
||||
resolve(transactionCategories);
|
||||
}).catch(error => {
|
||||
if (params.force) {
|
||||
logger.error('failed to force load category list', error);
|
||||
} else {
|
||||
logger.error('failed to load category list', 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 category list' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getCategory(params: { categoryId: string }): Promise<TransactionCategory> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getTransactionCategory({
|
||||
id: params.categoryId
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to retrieve category' });
|
||||
return;
|
||||
}
|
||||
|
||||
const transactionCategory = TransactionCategory.of(data.result);
|
||||
|
||||
resolve(transactionCategory);
|
||||
}).catch(error => {
|
||||
logger.error('failed to load category info', 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 category' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function saveCategory(params: { category: TransactionCategory, isEdit: boolean, clientSessionId: string }): Promise<TransactionCategory> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let promise: ApiResponsePromise<TransactionCategoryInfoResponse>;
|
||||
|
||||
if (!params.isEdit) {
|
||||
promise = services.addTransactionCategory(params.category.toCreateRequest(params.clientSessionId));
|
||||
} else {
|
||||
promise = services.modifyTransactionCategory(params.category.toModifyRequest());
|
||||
}
|
||||
|
||||
promise.then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
if (!params.isEdit) {
|
||||
reject({ message: 'Unable to add category' });
|
||||
} else {
|
||||
reject({ message: 'Unable to save category' });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const transactionCategory = TransactionCategory.of(data.result);
|
||||
|
||||
if (!params.isEdit) {
|
||||
addCategoryToTransactionCategoryList(transactionCategory);
|
||||
} else {
|
||||
const result = updateCategoryInTransactionCategoryList(transactionCategory, allTransactionCategoriesMap.value[params.category.id]);
|
||||
|
||||
if (!result && !transactionCategoryListStateInvalid.value) {
|
||||
updateTransactionCategoryListInvalidState(true);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(transactionCategory);
|
||||
}).catch(error => {
|
||||
logger.error('failed to save category', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
if (!params.isEdit) {
|
||||
reject({ message: 'Unable to add category' });
|
||||
} else {
|
||||
reject({ message: 'Unable to save category' });
|
||||
}
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function addCategories(req: TransactionCategoryCreateBatchRequest): Promise<Record<number, TransactionCategory[]>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.addTransactionCategoryBatch(req).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to add preset categories' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!transactionCategoryListStateInvalid.value) {
|
||||
updateTransactionCategoryListInvalidState(true);
|
||||
}
|
||||
|
||||
const transactionCategories = TransactionCategory.ofMap(data.result);
|
||||
|
||||
resolve(transactionCategories);
|
||||
}).catch(error => {
|
||||
logger.error('failed to add preset categories', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to add preset categories' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function changeCategoryDisplayOrder(params: { categoryId: string, from: number, to: number }): Promise<void> {
|
||||
const category = allTransactionCategoriesMap.value[params.categoryId];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!category) {
|
||||
reject({ message: 'Unable to move category' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!category.parentId || category.parentId === '0') {
|
||||
if (!allTransactionCategories.value[category.type] ||
|
||||
!allTransactionCategories.value[category.type][params.to]) {
|
||||
reject({ message: 'Unable to move category' });
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const subCategoryList = allTransactionCategoriesMap.value[category.parentId].secondaryCategories;
|
||||
|
||||
if (!subCategoryList || !subCategoryList[params.to]) {
|
||||
reject({ message: 'Unable to move category' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!transactionCategoryListStateInvalid.value) {
|
||||
updateTransactionCategoryListInvalidState(true);
|
||||
}
|
||||
|
||||
updateCategoryDisplayOrderInCategoryList({
|
||||
category: category,
|
||||
from: params.from,
|
||||
to: params.to
|
||||
});
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function updateCategoryDisplayOrders(params: { type: CategoryType, parentId: string }): Promise<boolean> {
|
||||
const newDisplayOrders: TransactionCategoryNewDisplayOrderRequest[] = [];
|
||||
|
||||
let categoryList: TransactionCategory[] | undefined = undefined;
|
||||
|
||||
if (!params.parentId || params.parentId === '0') {
|
||||
categoryList = allTransactionCategories.value[params.type];
|
||||
} else if (allTransactionCategoriesMap.value[params.parentId]) {
|
||||
categoryList = allTransactionCategoriesMap.value[params.parentId].secondaryCategories;
|
||||
}
|
||||
|
||||
if (categoryList) {
|
||||
for (let i = 0; i < categoryList.length; i++) {
|
||||
newDisplayOrders.push({
|
||||
id: categoryList[i].id,
|
||||
displayOrder: i + 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
services.moveTransactionCategory({
|
||||
newDisplayOrders: newDisplayOrders
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to move category' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (transactionCategoryListStateInvalid.value) {
|
||||
updateTransactionCategoryListInvalidState(false);
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to save categories display order', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to move category' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function hideCategory(params: { category: TransactionCategory, hidden: boolean }): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.hideTransactionCategory({
|
||||
id: params.category.id,
|
||||
hidden: params.hidden
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
if (params.hidden) {
|
||||
reject({ message: 'Unable to hide this category' });
|
||||
} else {
|
||||
reject({ message: 'Unable to unhide this category' });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateCategoryVisibilityInTransactionCategoryList({
|
||||
category: params.category,
|
||||
hidden: params.hidden
|
||||
});
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to change category visibility', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
if (params.hidden) {
|
||||
reject({ message: 'Unable to hide this category' });
|
||||
} else {
|
||||
reject({ message: 'Unable to unhide this category' });
|
||||
}
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCategory(params: { category: TransactionCategory, beforeResolve: BeforeResolveFunction }): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.deleteTransactionCategory({
|
||||
id: params.category.id
|
||||
}).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to delete this category' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.beforeResolve) {
|
||||
params.beforeResolve(() => {
|
||||
removeCategoryFromTransactionCategoryList(params.category);
|
||||
});
|
||||
} else {
|
||||
removeCategoryFromTransactionCategoryList(params.category);
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to delete category', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to delete this category' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
// states
|
||||
allTransactionCategories,
|
||||
allTransactionCategoriesMap,
|
||||
transactionCategoryListStateInvalid,
|
||||
// functions
|
||||
updateTransactionCategoryListInvalidState,
|
||||
resetTransactionCategories,
|
||||
loadAllCategories,
|
||||
getCategory,
|
||||
saveCategory,
|
||||
addCategories,
|
||||
changeCategoryDisplayOrder,
|
||||
updateCategoryDisplayOrders,
|
||||
hideCategory,
|
||||
deleteCategory
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user