use pinia to replace vuex, code refactor

This commit is contained in:
MaysWind
2023-06-10 23:13:31 +08:00
parent 0d84f2857f
commit 46d85e92cd
80 changed files with 4972 additions and 4859 deletions
+797
View File
@@ -0,0 +1,797 @@
import { defineStore } from 'pinia';
import { useUserStore } from './user.js';
import { useExchangeRatesStore } from './exchangeRates.js';
import accountConstants from '@/consts/account.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isNumber } from '@/lib/common.js';
import { getCategorizedAccounts, getAllFilteredAccountsBalance } from '@/lib/account.js';
function loadAccountList(state, accounts) {
state.allAccounts = accounts;
state.allAccountsMap = {};
for (let i = 0; i < accounts.length; i++) {
const account = accounts[i];
state.allAccountsMap[account.id] = account;
if (account.subAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
state.allAccountsMap[subAccount.id] = subAccount;
}
}
}
state.allCategorizedAccounts = getCategorizedAccounts(accounts);
}
function addAccountToAccountList(state, account) {
let insertIndexToAllList = 0;
for (let i = 0; i < state.allAccounts.length; i++) {
if (state.allAccounts[i].category > account.category) {
insertIndexToAllList = i;
break;
}
}
state.allAccounts.splice(insertIndexToAllList, 0, account);
state.allAccountsMap[account.id] = account;
if (account.subAccounts) {
for (let i = 0; i < account.subAccounts.length; i++) {
const subAccount = account.subAccounts[i];
state.allAccountsMap[subAccount.id] = subAccount;
}
}
if (state.allCategorizedAccounts[account.category]) {
const accountList = state.allCategorizedAccounts[account.category].accounts;
accountList.push(account);
} else {
state.allCategorizedAccounts = getCategorizedAccounts(state.allAccounts);
}
}
function updateAccountToAccountList(state, account) {
for (let i = 0; i < state.allAccounts.length; i++) {
if (state.allAccounts[i].id === account.id) {
state.allAccounts.splice(i, 1, account);
break;
}
}
state.allAccountsMap[account.id] = account;
if (account.subAccounts) {
for (let i = 0; i < account.subAccounts.length; i++) {
const subAccount = account.subAccounts[i];
state.allAccountsMap[subAccount.id] = subAccount;
}
}
if (state.allCategorizedAccounts[account.category]) {
const accountList = state.allCategorizedAccounts[account.category].accounts;
for (let i = 0; i < accountList.length; i++) {
if (accountList[i].id === account.id) {
accountList.splice(i, 1, account);
break;
}
}
}
}
function updateAccountDisplayOrderInAccountList(state, { account, from, to }) {
let fromAccount = null;
let toAccount = null;
if (state.allCategorizedAccounts[account.category]) {
const accountList = state.allCategorizedAccounts[account.category].accounts;
fromAccount = accountList[from];
toAccount = accountList[to];
accountList.splice(to, 0, accountList.splice(from, 1)[0]);
}
if (fromAccount && toAccount) {
let globalFromIndex = -1;
let globalToIndex = -1;
for (let i = 0; i < state.allAccounts.length; i++) {
if (state.allAccounts[i].id === fromAccount.id) {
globalFromIndex = i;
} else if (state.allAccounts[i].id === toAccount.id) {
globalToIndex = i;
}
}
if (globalFromIndex >= 0 && globalToIndex >= 0) {
state.allAccounts.splice(globalToIndex, 0, state.allAccounts.splice(globalFromIndex, 1)[0]);
}
}
}
function updateAccountVisibilityInAccountList(state, { account, hidden }) {
if (state.allAccountsMap[account.id]) {
state.allAccountsMap[account.id].hidden = hidden;
}
}
function removeAccountFromAccountList(state, account) {
for (let i = 0; i < state.allAccounts.length; i++) {
if (state.allAccounts[i].id === account.id) {
state.allAccounts.splice(i, 1);
break;
}
}
if (state.allAccountsMap[account.id] && state.allAccountsMap[account.id].subAccounts) {
const subAccounts = state.allAccountsMap[account.id].subAccounts;
for (let i = 0; i < subAccounts.length; i++) {
const subAccount = subAccounts[i];
if (state.allAccountsMap[subAccount.id]) {
delete state.allAccountsMap[subAccount.id];
}
}
}
if (state.allAccountsMap[account.id]) {
delete state.allAccountsMap[account.id];
}
if (state.allCategorizedAccounts[account.category]) {
const accountList = state.allCategorizedAccounts[account.category].accounts;
for (let i = 0; i < accountList.length; i++) {
if (accountList[i].id === account.id) {
accountList.splice(i, 1);
break;
}
}
}
}
export const useAccountsStore = defineStore('accounts', {
state: () => ({
allAccounts: [],
allAccountsMap: {},
allCategorizedAccounts: {},
accountListStateInvalid: true,
}),
getters: {
allPlainAccounts(state) {
const allAccounts = [];
for (let i = 0; i < state.allAccounts.length; i++) {
const account = state.allAccounts[i];
if (account.type === accountConstants.allAccountTypes.SingleAccount) {
allAccounts.push(account);
} else if (account.type === accountConstants.allAccountTypes.MultiSubAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
allAccounts.push(subAccount);
}
}
}
return allAccounts;
},
allVisiblePlainAccounts(state) {
const allVisibleAccounts = [];
for (let i = 0; i < state.allAccounts.length; i++) {
const account = state.allAccounts[i];
if (account.hidden) {
continue;
}
if (account.type === accountConstants.allAccountTypes.SingleAccount) {
allVisibleAccounts.push(account);
} else if (account.type === accountConstants.allAccountTypes.MultiSubAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
allVisibleAccounts.push(subAccount);
}
}
}
return allVisibleAccounts;
},
allAvailableAccountsCount(state) {
let allAccountCount = 0;
for (let category in state.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(state.allCategorizedAccounts, category)) {
continue;
}
allAccountCount += state.allCategorizedAccounts[category].accounts.length;
}
return allAccountCount;
},
allVisibleAccountsCount(state) {
let shownAccountCount = 0;
for (let category in state.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(state.allCategorizedAccounts, category)) {
continue;
}
const accountList = state.allCategorizedAccounts[category].accounts;
for (let i = 0; i < accountList.length; i++) {
if (!accountList[i].hidden) {
shownAccountCount++;
}
}
}
return shownAccountCount;
}
},
actions: {
updateAccountListInvalidState(invalidState) {
this.accountListStateInvalid = invalidState;
},
resetAccounts() {
this.allAccounts = [];
this.allAccountsMap = {};
this.allCategorizedAccounts = {};
this.accountListStateInvalid = true;
},
getFirstShowingIds(showHidden) {
const ret = {
accounts: {},
subAccounts: {}
};
for (let category in this.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(this.allCategorizedAccounts, category)) {
continue;
}
if (!this.allCategorizedAccounts[category] || !this.allCategorizedAccounts[category].accounts) {
continue;
}
const accounts = this.allCategorizedAccounts[category].accounts;
for (let i = 0; i < accounts.length; i++) {
const account = accounts[i];
if (account.type === accountConstants.allAccountTypes.MultiSubAccounts && account.subAccounts) {
for (let j = 0; j < account.subAccounts.length; j++) {
const subAccount = account.subAccounts[j];
if (showHidden || !subAccount.hidden) {
ret.subAccounts[account.id] = subAccount.id;
break;
}
}
}
if (showHidden || !account.hidden) {
ret.accounts[category] = account.id;
break;
}
}
}
return ret;
},
getLastShowingIds(showHidden) {
const ret = {
accounts: {},
subAccounts: {}
};
for (let category in this.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(this.allCategorizedAccounts, category)) {
continue;
}
if (!this.allCategorizedAccounts[category] || !this.allCategorizedAccounts[category].accounts) {
continue;
}
const accounts = this.allCategorizedAccounts[category].accounts;
for (let i = accounts.length - 1; i >= 0; i--) {
const account = accounts[i];
if (account.type === accountConstants.allAccountTypes.MultiSubAccounts && account.subAccounts) {
for (let j = account.subAccounts.length - 1; j >= 0; j--) {
const subAccount = account.subAccounts[j];
if (showHidden || !subAccount.hidden) {
ret.subAccounts[account.id] = subAccount.id;
break;
}
}
}
if (showHidden || !account.hidden) {
ret.accounts[category] = account.id;
break;
}
}
}
return ret;
},
getNetAssets(showAccountBalance) {
if (!showAccountBalance) {
return '***';
}
const userStore = useUserStore();
const exchangeRatesStore = useExchangeRatesStore();
const accountsBalance = getAllFilteredAccountsBalance(this.allCategorizedAccounts, () => true);
let netAssets = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === userStore.currentUserDefaultCurrency) {
netAssets += accountsBalance[i].balance;
} else {
const balance = exchangeRatesStore.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, userStore.currentUserDefaultCurrency);
if (!isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
netAssets += Math.floor(balance);
}
}
if (hasUnCalculatedAmount) {
return netAssets + '+';
} else {
return netAssets;
}
},
getTotalAssets(showAccountBalance) {
if (!showAccountBalance) {
return '***';
}
const userStore = useUserStore();
const exchangeRatesStore = useExchangeRatesStore();
const accountsBalance = getAllFilteredAccountsBalance(this.allCategorizedAccounts, account => account.isAsset);
let totalAssets = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === userStore.currentUserDefaultCurrency) {
totalAssets += accountsBalance[i].balance;
} else {
const balance = exchangeRatesStore.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, userStore.currentUserDefaultCurrency);
if (!isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
totalAssets += Math.floor(balance);
}
}
if (hasUnCalculatedAmount) {
return totalAssets + '+';
} else {
return totalAssets;
}
},
getTotalLiabilities(showAccountBalance) {
if (!showAccountBalance) {
return '***';
}
const userStore = useUserStore();
const exchangeRatesStore = useExchangeRatesStore();
const accountsBalance = getAllFilteredAccountsBalance(this.allCategorizedAccounts, account => account.isLiability);
let totalLiabilities = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === userStore.currentUserDefaultCurrency) {
totalLiabilities -= accountsBalance[i].balance;
} else {
const balance = exchangeRatesStore.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, userStore.currentUserDefaultCurrency);
if (!isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
totalLiabilities -= Math.floor(balance);
}
}
if (hasUnCalculatedAmount) {
return totalLiabilities + '+';
} else {
return totalLiabilities;
}
},
getAccountCategoryTotalBalance(showAccountBalance, accountCategory) {
if (!showAccountBalance) {
return '***';
}
const userStore = useUserStore();
const exchangeRatesStore = useExchangeRatesStore();
const accountsBalance = getAllFilteredAccountsBalance(this.allCategorizedAccounts, account => account.category === accountCategory.id);
let totalBalance = 0;
let hasUnCalculatedAmount = false;
for (let i = 0; i < accountsBalance.length; i++) {
if (accountsBalance[i].currency === userStore.currentUserDefaultCurrency) {
if (accountsBalance[i].isAsset) {
totalBalance += accountsBalance[i].balance;
} else if (accountsBalance[i].isLiability) {
totalBalance -= accountsBalance[i].balance;
} else {
totalBalance += accountsBalance[i].balance;
}
} else {
const balance = exchangeRatesStore.getExchangedAmount(accountsBalance[i].balance, accountsBalance[i].currency, userStore.currentUserDefaultCurrency);
if (!isNumber(balance)) {
hasUnCalculatedAmount = true;
continue;
}
if (accountsBalance[i].isAsset) {
totalBalance += Math.floor(balance);
} else if (accountsBalance[i].isLiability) {
totalBalance -= Math.floor(balance);
} else {
totalBalance += Math.floor(balance);
}
}
}
if (hasUnCalculatedAmount) {
return totalBalance + '+';
} else {
return totalBalance;
}
},
getAccountBalance(showAccountBalance, account) {
if (account.type !== accountConstants.allAccountTypes.SingleAccount) {
return null;
}
if (showAccountBalance) {
if (account.isAsset) {
return account.balance;
} else if (account.isLiability) {
return -account.balance;
} else {
return account.balance;
}
} else {
return '***';
}
},
hasAccount(accountCategory, visibleOnly) {
if (!this.allCategorizedAccounts[accountCategory.id] ||
!this.allCategorizedAccounts[accountCategory.id].accounts ||
!this.allCategorizedAccounts[accountCategory.id].accounts.length) {
return false;
}
let shownCount = 0;
for (let i = 0; i < this.allCategorizedAccounts[accountCategory.id].accounts.length; i++) {
const account = this.allCategorizedAccounts[accountCategory.id].accounts[i];
if (!visibleOnly || !account.hidden) {
shownCount++;
}
}
return shownCount > 0;
},
hasVisibleSubAccount(showHidden, account) {
if (!account || account.type !== accountConstants.allAccountTypes.MultiSubAccounts || !account.subAccounts) {
return false;
}
for (let i = 0; i < account.subAccounts.length; i++) {
if (showHidden || !account.subAccounts[i].hidden) {
return true;
}
}
return false;
},
loadAllAccounts({ force }) {
const self = this;
if (!force && !self.accountListStateInvalid) {
return new Promise((resolve) => {
resolve(self.allAccounts);
});
}
return new Promise((resolve, reject) => {
services.getAllAccounts({
visibleOnly: false
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get account list' });
return;
}
loadAccountList(self, data.result);
if (self.accountListStateInvalid) {
self.updateAccountListInvalidState(false);
}
resolve(data.result);
}).catch(error => {
if (force) {
logger.error('failed to force load account list', error);
} else {
logger.error('failed to load account 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 get account list' });
} else {
reject(error);
}
});
});
},
getAccount({ accountId }) {
return new Promise((resolve, reject) => {
services.getAccount({
id: accountId
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get account' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load account 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 get account' });
} else {
reject(error);
}
});
});
},
saveAccount({ account }) {
const self = this;
const oldAccount = account.id ? self.allAccountsMap[account.id] : null;
return new Promise((resolve, reject) => {
let promise = null;
if (!account.id) {
promise = services.addAccount(account);
} else {
promise = services.modifyAccount(account);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!account.id) {
reject({ message: 'Unable to add account' });
} else {
reject({ message: 'Unable to save account' });
}
return;
}
if (!account.id) {
addAccountToAccountList(self, data.result);
} else {
if (oldAccount && oldAccount.category === data.result.category) {
updateAccountToAccountList(self, data.result);
} else {
self.updateAccountListInvalidState(true);
}
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save account', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!account.id) {
reject({ message: 'Unable to add account' });
} else {
reject({ message: 'Unable to save account' });
}
} else {
reject(error);
}
});
});
},
changeAccountDisplayOrder({ accountId, from, to }) {
const self = this;
const account = self.allAccountsMap[accountId];
return new Promise((resolve, reject) => {
if (!account ||
!self.allCategorizedAccounts[account.category] ||
!self.allCategorizedAccounts[account.category].accounts ||
!self.allCategorizedAccounts[account.category].accounts[to]) {
reject({ message: 'Unable to move account' });
return;
}
if (!self.accountListStateInvalid) {
self.updateAccountListInvalidState(true);
}
updateAccountDisplayOrderInAccountList(self, {
account: account,
from: from,
to: to
});
resolve();
});
},
updateAccountDisplayOrders() {
const self = this;
const newDisplayOrders = [];
for (let category in self.allCategorizedAccounts) {
if (!Object.prototype.hasOwnProperty.call(self.allCategorizedAccounts, category)) {
continue;
}
const accountList = self.allCategorizedAccounts[category].accounts;
for (let i = 0; i < accountList.length; i++) {
newDisplayOrders.push({
id: accountList[i].id,
displayOrder: i + 1
});
}
}
return new Promise((resolve, reject) => {
services.moveAccount({
newDisplayOrders: newDisplayOrders
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to move account' });
return;
}
if (self.accountListStateInvalid) {
self.updateAccountListInvalidState(false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save accounts 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 account' });
} else {
reject(error);
}
});
});
},
hideAccount({ account, hidden }) {
const self = this;
return new Promise((resolve, reject) => {
services.hideAccount({
id: account.id,
hidden: hidden
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (hidden) {
reject({ message: 'Unable to hide this account' });
} else {
reject({ message: 'Unable to unhide this account' });
}
return;
}
updateAccountVisibilityInAccountList(self, {
account: account,
hidden: hidden
});
resolve(data.result);
}).catch(error => {
logger.error('failed to change account 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 account' });
} else {
reject({ message: 'Unable to unhide this account' });
}
} else {
reject(error);
}
});
});
},
deleteAccount({ account, beforeResolve }) {
const self = this;
return new Promise((resolve, reject) => {
services.deleteAccount({
id: account.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this account' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
removeAccountFromAccountList(self, account);
});
} else {
removeAccountFromAccountList(self, account);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete account', 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 account' });
} else {
reject(error);
}
});
});
}
}
});
+132
View File
@@ -0,0 +1,132 @@
import { defineStore } from 'pinia';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isEquals } from '@/lib/common.js';
import { getCurrentUnixTime, formatUnixTime } from '@/lib/datetime.js';
import { getExchangedAmount } from '@/lib/currency.js';
const exchangeRatesLocalStorageKey = 'ebk_app_exchange_rates';
function getExchangeRatesFromLocalStorage() {
const storageData = localStorage.getItem(exchangeRatesLocalStorageKey) || '{}';
return JSON.parse(storageData);
}
function setExchangeRatesToLocalStorage(value) {
const storageData = JSON.stringify(value);
localStorage.setItem(exchangeRatesLocalStorageKey, storageData);
}
function clearExchangeRatesFromLocalStorage() {
localStorage.removeItem(exchangeRatesLocalStorageKey);
}
export const useExchangeRatesStore = defineStore('exchangeRates', {
state: () => ({
latestExchangeRates: getExchangeRatesFromLocalStorage()
}),
getters: {
exchangeRatesLastUpdateTime(state) {
const exchangeRates = state.latestExchangeRates || {};
return exchangeRates && exchangeRates.data ? exchangeRates.data.updateTime : null;
},
latestExchangeRateMap(state) {
const exchangeRateMap = {};
if (!state.latestExchangeRates || !state.latestExchangeRates.data || !state.latestExchangeRates.data.exchangeRates) {
return exchangeRateMap;
}
for (let i = 0; i < state.latestExchangeRates.data.exchangeRates.length; i++) {
const exchangeRate = state.latestExchangeRates.data.exchangeRates[i];
exchangeRateMap[exchangeRate.currency] = exchangeRate;
}
return exchangeRateMap;
}
},
actions: {
resetLatestExchangeRates() {
this.latestExchangeRates = {};
clearExchangeRatesFromLocalStorage();
},
getLatestExchangeRates({ silent, force }) {
const self = this;
const currentExchangeRateData = self.latestExchangeRates;
const now = getCurrentUnixTime();
if (!force) {
if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data &&
formatUnixTime(currentExchangeRateData.data.updateTime, 'YYYY-MM-DD') === formatUnixTime(now, 'YYYY-MM-DD')) {
return currentExchangeRateData.data;
}
if (currentExchangeRateData && currentExchangeRateData.time && currentExchangeRateData.data &&
formatUnixTime(currentExchangeRateData.time, 'YYYY-MM-DD HH') === formatUnixTime(now, 'YYYY-MM-DD HH')) {
return currentExchangeRateData.data;
}
}
return new Promise((resolve, reject) => {
services.getLatestExchangeRates({
ignoreError: silent
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get exchange rates data' });
return;
}
const currentData = getExchangeRatesFromLocalStorage();
if (currentData && currentData.data && isEquals(currentData.data, data.result)) {
reject({ message: 'Exchange rates data is up to date' });
return;
}
this.latestExchangeRates = {
time: now,
data: data.result
};
setExchangeRatesToLocalStorage(this.latestExchangeRates);
resolve(data.result);
}).catch(error => {
logger.error('failed to get latest exchange rates data', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to get exchange rates data' });
}
});
});
},
getExchangedAmount(amount, fromCurrency, toCurrency) {
if (!this.latestExchangeRates || !this.latestExchangeRates.data || !this.latestExchangeRates.data.exchangeRates) {
return null;
}
const exchangeRates = this.latestExchangeRates.data.exchangeRates;
const exchangeRateMap = {};
for (let i = 0; i < exchangeRates.length; i++) {
const exchangeRate = exchangeRates[i];
exchangeRateMap[exchangeRate.currency] = exchangeRate;
}
const fromCurrencyExchangeRate = exchangeRateMap[fromCurrency];
const toCurrencyExchangeRate = exchangeRateMap[toCurrency];
if (!fromCurrencyExchangeRate || !toCurrencyExchangeRate) {
return null;
}
return getExchangedAmount(amount, fromCurrencyExchangeRate.rate, toCurrencyExchangeRate.rate);
}
}
});
+350
View File
@@ -0,0 +1,350 @@
import { defineStore } from 'pinia';
import { useUserStore } from './user.js';
import { useAccountsStore } from './account.js';
import { useTransactionCategoriesStore } from './transactionCategory.js';
import { useTransactionTagsStore } from './transactionTag.js';
import { useTransactionsStore } from './transaction.js';
import { useOverviewStore } from './overview.js';
import { useStatisticsStore } from './statistics.js';
import { useExchangeRatesStore } from './exchangeRates.js';
import userState from '@/lib/userstate.js';
import services from '@/lib/services.js';
import settings from '@/lib/settings.js';
import logger from '@/lib/logger.js';
import { isObject, isString } from '@/lib/common.js';
export const useRootStore = defineStore('root', {
actions: {
resetAllStates() {
const exchangeRatesStore = useExchangeRatesStore();
exchangeRatesStore.resetLatestExchangeRates();
const statisticsStore = useStatisticsStore();
statisticsStore.resetTransactionStatistics();
const overviewStore = useOverviewStore();
overviewStore.resetTransactionOverview();
const transactionsStore = useTransactionsStore();
transactionsStore.resetTransactions();
const transactionTagsStore = useTransactionTagsStore();
transactionTagsStore.resetTransactionTags();
const transactionCategoriesStore = useTransactionCategoriesStore();
transactionCategoriesStore.resetTransactionCategories();
const accountsStore = useAccountsStore();
accountsStore.resetAccounts();
const userStore = useUserStore();
userStore.resetUserInfo();
},
authorize({ loginName, password }) {
return new Promise((resolve, reject) => {
services.authorize({
loginName: loginName,
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.token) {
reject({ message: 'Unable to login' });
return;
}
if (data.result.need2FA) {
resolve(data.result);
return;
}
if (settings.isEnableApplicationLock() || userState.getUserAppLockState()) {
const appLockState = userState.getUserAppLockState();
if (!appLockState || appLockState.username !== data.result.user.username) {
userState.clearTokenAndUserInfo(true);
settings.setEnableApplicationLock(false);
settings.setEnableApplicationLockWebAuthn(false);
userState.clearWebAuthnConfig();
}
}
userState.updateToken(data.result.token);
if (data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserInfo(data.result.user);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to login', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to login' });
}
});
});
},
authorize2FA({ token, passcode, recoveryCode }) {
return new Promise((resolve, reject) => {
let promise = null;
if (passcode) {
promise = services.authorize2FA({
passcode: passcode,
token: token
});
} else if (recoveryCode) {
promise = services.authorize2FAByBackupCode({
recoveryCode: recoveryCode,
token: token
});
} else {
reject({ message: 'An error has occurred' });
return;
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.token) {
reject({ message: 'Unable to verify' });
return;
}
if (settings.isEnableApplicationLock() || userState.getUserAppLockState()) {
const appLockState = userState.getUserAppLockState();
if (!appLockState || appLockState.username !== data.result.user.username) {
userState.clearTokenAndUserInfo(true);
settings.setEnableApplicationLock(false);
settings.setEnableApplicationLockWebAuthn(false);
userState.clearWebAuthnConfig();
}
}
userState.updateToken(data.result.token);
if (data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserInfo(data.result.user);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to verify 2fa', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to verify' });
}
});
});
},
register({ user }) {
return new Promise((resolve, reject) => {
services.register({
username: user.username,
password: user.password,
email: user.email,
nickname: user.nickname,
language: user.language,
defaultCurrency: user.defaultCurrency,
firstDayOfWeek: user.firstDayOfWeek
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to sign up' });
return;
}
if (settings.isEnableApplicationLock()) {
settings.setEnableApplicationLock(false);
settings.setEnableApplicationLockWebAuthn(false);
userState.clearWebAuthnConfig();
}
if (data.result.token && isString(data.result.token)) {
userState.updateToken(data.result.token);
}
if (data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserInfo(data.result.user);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to sign up', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to sign up' });
} else {
reject(error);
}
});
});
},
logout() {
const self = this;
return new Promise((resolve, reject) => {
services.logout().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to logout' });
return;
}
userState.clearTokenAndUserInfo(true);
userState.clearWebAuthnConfig();
self.resetAllStates();
resolve(data.result);
}).catch(error => {
logger.error('failed to log out', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to logout' });
}
});
});
},
forceLogout() {
userState.clearTokenAndUserInfo(true);
userState.clearWebAuthnConfig();
this.resetAllStates();
},
updateUserProfile({ profile, currentPassword }) {
return new Promise((resolve, reject) => {
services.updateProfile({
password: profile.password,
oldPassword: currentPassword,
email: profile.email,
nickname: profile.nickname,
defaultAccountId: profile.defaultAccountId,
transactionEditScope: profile.transactionEditScope,
language: profile.language,
defaultCurrency: profile.defaultCurrency,
firstDayOfWeek: profile.firstDayOfWeek,
longDateFormat: profile.longDateFormat,
shortDateFormat: profile.shortDateFormat,
longTimeFormat: profile.longTimeFormat,
shortTimeFormat: profile.shortTimeFormat
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to update user profile' });
return;
}
if (data.result.newToken && isString(data.result.newToken)) {
userState.updateToken(data.result.newToken);
}
if (data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserInfo(data.result.user);
}
const accountsStore = useAccountsStore();
if (!accountsStore.accountListStateInvalid) {
accountsStore.updateAccountListInvalidState(true);
}
const overviewStore = useOverviewStore();
if (!overviewStore.transactionOverviewStateInvalid) {
overviewStore.updateTransactionOverviewInvalidState(true);
}
const statisticsStore = useStatisticsStore();
if (!statisticsStore.transactionStatisticsStateInvalid) {
statisticsStore.updateTransactionStatisticsInvalidState(true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save user profile', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to update user profile' });
} else {
reject(error);
}
});
});
},
clearUserData({ password }) {
return new Promise((resolve, reject) => {
services.clearData({
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to clear user data' });
return;
}
const accountsStore = useAccountsStore();
if (!accountsStore.accountListStateInvalid) {
accountsStore.updateAccountListInvalidState(true);
}
const transactionCategoriesStore = useTransactionCategoriesStore();
if (!transactionCategoriesStore.transactionCategoryListStateInvalid) {
transactionCategoriesStore.updateTransactionCategoryListInvalidState(true);
}
const transactionTagsStore = useTransactionTagsStore();
if (!transactionTagsStore.transactionTagListStateInvalid) {
transactionTagsStore.updateTransactionTagListInvalidState(true);
}
const overviewStore = useOverviewStore();
if (!overviewStore.transactionOverviewStateInvalid) {
overviewStore.updateTransactionOverviewInvalidState(true);
}
const statisticsStore = useStatisticsStore();
if (!statisticsStore.transactionStatisticsStateInvalid) {
statisticsStore.updateTransactionStatisticsInvalidState(true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to clear user data', error);
if (error && error.processed) {
reject(error);
} else if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else {
reject({ message: 'Unable to clear user data' });
}
});
});
}
}
});
+119
View File
@@ -0,0 +1,119 @@
import { defineStore } from 'pinia';
import { useExchangeRatesStore } from './exchangeRates.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isNumber } from '@/lib/common.js';
export const useOverviewStore = defineStore('overview', {
state: () => ({
transactionOverview: {},
transactionOverviewStateInvalid: true
}),
actions: {
updateTransactionOverviewInvalidState(invalidState) {
this.transactionOverviewStateInvalid = invalidState;
},
resetTransactionOverview() {
this.transactionOverview = {};
this.transactionOverviewStateInvalid = true;
},
loadTransactionOverview({ defaultCurrency, dateRange, force }) {
const self = this;
const exchangeRatesStore = useExchangeRatesStore();
if (!force && !self.transactionOverviewStateInvalid) {
return new Promise((resolve) => {
resolve(self.transactionOverview);
});
}
return new Promise((resolve, reject) => {
services.getTransactionAmounts({
today: dateRange.today,
thisWeek: dateRange.thisWeek,
thisMonth: dateRange.thisMonth,
thisYear: dateRange.thisYear
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get transaction overview' });
return;
}
const overview = data.result;
for (let field in overview) {
if (!Object.prototype.hasOwnProperty.call(overview, field)) {
continue;
}
const item = overview[field];
if (!item.amounts || !item.amounts.length) {
item.amounts = [];
}
let totalIncomeAmount = 0;
let totalExpenseAmount = 0;
let hasUnCalculatedTotalIncome = false;
let hasUnCalculatedTotalExpense = false;
for (let i = 0; i < item.amounts.length; i++) {
const amount = item.amounts[i];
if (amount.currency !== defaultCurrency) {
const incomeAmount = exchangeRatesStore.getExchangedAmount(amount.incomeAmount, amount.currency, defaultCurrency);
const expenseAmount = exchangeRatesStore.getExchangedAmount(amount.expenseAmount, amount.currency, defaultCurrency);
if (isNumber(incomeAmount)) {
totalIncomeAmount += Math.floor(incomeAmount);
} else {
hasUnCalculatedTotalIncome = true;
}
if (isNumber(expenseAmount)) {
totalExpenseAmount += Math.floor(expenseAmount);
} else {
hasUnCalculatedTotalExpense = true;
}
} else {
totalIncomeAmount += amount.incomeAmount;
totalExpenseAmount += amount.expenseAmount;
}
}
item.incomeAmount = totalIncomeAmount;
item.expenseAmount = totalExpenseAmount;
item.incompleteIncomeAmount = hasUnCalculatedTotalIncome;
item.incompleteExpenseAmount = hasUnCalculatedTotalExpense;
}
self.transactionOverview = overview;
if (self.transactionOverviewStateInvalid) {
self.updateTransactionOverviewInvalidState(false);
}
resolve(overview);
}).catch(error => {
if (force) {
logger.error('failed to force load transaction overview', error);
} else {
logger.error('failed to load transaction overview', error);
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transaction overview' });
} else {
reject(error);
}
});
});
}
}
});
+24
View File
@@ -0,0 +1,24 @@
import { defineStore } from 'pinia';
import currencyConstants from '@/consts/currency.js';
import datetimeConstants from '@/consts/datetime.js';
export const useSettingsStore = defineStore('settings', {
state: () => ({
defaultSetting: {
language: '',
currency: currencyConstants.defaultCurrency,
firstDayOfWeek: datetimeConstants.defaultFirstDayOfWeek,
longDateFormat: 0,
shortDateFormat: 0,
longTimeFormat: 0,
shortTimeFormat: 0
}
}),
actions: {
updateLocalizedDefaultSettings({ defaultCurrency, defaultFirstDayOfWeek }) {
this.defaultSetting.currency = defaultCurrency;
this.defaultSetting.firstDayOfWeek = defaultFirstDayOfWeek;
}
}
});
+423
View File
@@ -0,0 +1,423 @@
import { defineStore } from 'pinia';
import { useUserStore } from './user.js';
import { useAccountsStore } from './account.js';
import { useTransactionCategoriesStore } from './transactionCategory.js';
import { useExchangeRatesStore } from './exchangeRates.js';
import statisticsConstants from '@/consts/statistics.js';
import categoryConstants from '@/consts/category.js';
import iconConstants from '@/consts/icon.js';
import colorConstants from '@/consts/color.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isNumber, isObject } from '@/lib/common.js';
function loadTransactionStatistics(state, { statistics, defaultCurrency }) {
if (statistics && statistics.items && statistics.items.length) {
const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
const exchangeRatesStore = useExchangeRatesStore();
for (let i = 0; i < statistics.items.length; i++) {
const item = statistics.items[i];
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.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.floor(amount);
}
} else if (item.account && item.account.currency === defaultCurrency) {
item.amountInDefaultCurrency = item.amount;
} else {
item.amountInDefaultCurrency = null;
}
}
}
state.transactionStatistics = statistics;
}
export const useStatisticsStore = defineStore('statistics', {
state: () => ({
transactionStatisticsFilter: {
dateType: statisticsConstants.defaultDataRangeType,
startTime: 0,
endTime: 0,
chartType: statisticsConstants.defaultChartType,
chartDataType: statisticsConstants.defaultChartDataType,
filterAccountIds: {},
filterCategoryIds: {}
},
transactionStatistics: [],
transactionStatisticsStateInvalid: true
}),
getters: {
statisticsItemsByTransactionStatisticsData(state) {
if (!state.transactionStatistics || !state.transactionStatistics.items) {
return null;
}
const allDataItems = {};
let totalAmount = 0;
let totalNonNegativeAmount = 0;
for (let i = 0; i < state.transactionStatistics.items.length; i++) {
const item = state.transactionStatistics.items[i];
if (!item.primaryAccount || !item.account || !item.primaryCategory || !item.category) {
continue;
}
if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByAccount.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByPrimaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseBySecondaryCategory.type) {
if (item.category.type !== categoryConstants.allCategoryTypes.Expense) {
continue;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeByAccount.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeByPrimaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.IncomeBySecondaryCategory.type) {
if (item.category.type !== categoryConstants.allCategoryTypes.Income) {
continue;
}
} else {
continue;
}
if (state.transactionStatisticsFilter.filterAccountIds && state.transactionStatisticsFilter.filterAccountIds[item.account.id]) {
continue;
}
if (state.transactionStatisticsFilter.filterCategoryIds && state.transactionStatisticsFilter.filterCategoryIds[item.category.id]) {
continue;
}
if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByAccount.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.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 || iconConstants.defaultAccountIcon.icon,
color: item.account.color || colorConstants.defaultAccountColor,
hidden: item.primaryAccount.hidden || item.account.hidden,
displayOrders: [item.primaryAccount.category, item.primaryAccount.displayOrder, item.account.displayOrder],
totalAmount: item.amountInDefaultCurrency
}
}
totalAmount += item.amountInDefaultCurrency;
if (item.amountInDefaultCurrency > 0) {
totalNonNegativeAmount += item.amountInDefaultCurrency;
}
allDataItems[item.account.id] = data;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseByPrimaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.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 || iconConstants.defaultCategoryIcon.icon,
color: item.primaryCategory.color || colorConstants.defaultCategoryColor,
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 (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.ExpenseBySecondaryCategory.type ||
state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.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 || iconConstants.defaultCategoryIcon.icon,
color: item.category.color || colorConstants.defaultCategoryColor,
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;
}
}
}
return {
totalAmount: totalAmount,
totalNonNegativeAmount: totalNonNegativeAmount,
items: allDataItems
}
},
statisticsItemsByAccountsData(state) {
const userStore = useUserStore();
const accountsStore = useAccountsStore();
const exchangeRatesStore = useExchangeRatesStore();
if (!accountsStore.allPlainAccounts) {
return null;
}
const allDataItems = {};
let totalAmount = 0;
let totalNonNegativeAmount = 0;
for (let i = 0; i < accountsStore.allPlainAccounts.length; i++) {
const account = accountsStore.allPlainAccounts[i];
if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.AccountTotalAssets.type) {
if (!account.isAsset) {
continue;
}
} else if (state.transactionStatisticsFilter.chartDataType === statisticsConstants.allChartDataTypes.AccountTotalLiabilities.type) {
if (!account.isLiability) {
continue;
}
}
if (state.transactionStatisticsFilter.filterAccountIds && state.transactionStatisticsFilter.filterAccountIds[account.id]) {
continue;
}
let primaryAccount = accountsStore.allAccountsMap[account.parentId];
if (!primaryAccount) {
primaryAccount = account;
}
let amount = account.balance;
if (account.currency !== userStore.currentUserDefaultCurrency) {
amount = Math.floor(exchangeRatesStore.getExchangedAmount(amount, account.currency, userStore.currentUserDefaultCurrency));
if (!isNumber(amount)) {
continue;
}
}
if (account.isLiability) {
amount = -amount;
}
const data = {
name: account.name,
type: 'account',
id: account.id,
icon: account.icon || iconConstants.defaultAccountIcon.icon,
color: account.color || colorConstants.defaultAccountColor,
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
}
}
},
actions: {
updateTransactionStatisticsInvalidState(invalidState) {
this.transactionStatisticsStateInvalid = invalidState;
},
resetTransactionStatistics() {
this.transactionStatisticsFilter.dateType = statisticsConstants.defaultDataRangeType;
this.transactionStatisticsFilter.startTime = 0;
this.transactionStatisticsFilter.endTime = 0;
this.transactionStatisticsFilter.chartType = statisticsConstants.defaultChartType;
this.transactionStatisticsFilter.chartDataType = statisticsConstants.defaultChartDataType;
this.transactionStatisticsFilter.filterAccountIds = {};
this.transactionStatisticsFilter.filterCategoryIds = {};
this.transactionStatistics = {};
this.transactionStatisticsStateInvalid = true;
},
initTransactionStatisticsFilter(filter) {
if (filter && isNumber(filter.dateType)) {
this.transactionStatisticsFilter.dateType = filter.dateType;
} else {
this.transactionStatisticsFilter.dateType = statisticsConstants.defaultDataRangeType;
}
if (filter && isNumber(filter.startTime)) {
this.transactionStatisticsFilter.startTime = filter.startTime;
} else {
this.transactionStatisticsFilter.startTime = 0;
}
if (filter && isNumber(filter.endTime)) {
this.transactionStatisticsFilter.endTime = filter.endTime;
} else {
this.transactionStatisticsFilter.endTime = 0;
}
if (filter && isNumber(filter.chartType)) {
this.transactionStatisticsFilter.chartType = filter.chartType;
} else {
this.transactionStatisticsFilter.chartType = statisticsConstants.defaultChartType;
}
if (filter && isNumber(filter.chartDataType)) {
this.transactionStatisticsFilter.chartDataType = filter.chartDataType;
} else {
this.transactionStatisticsFilter.chartDataType = statisticsConstants.defaultChartDataType;
}
if (filter && isObject(filter.filterAccountIds)) {
this.transactionStatisticsFilter.filterAccountIds = filter.filterAccountIds;
} else {
this.transactionStatisticsFilter.filterAccountIds = {};
}
if (filter && isObject(filter.filterCategoryIds)) {
this.transactionStatisticsFilter.filterCategoryIds = filter.filterCategoryIds;
} else {
this.transactionStatisticsFilter.filterCategoryIds = {};
}
if (filter && isNumber(filter.sortingType)) {
this.transactionStatisticsFilter.sortingType = filter.sortingType;
} else {
this.transactionStatisticsFilter.sortingType = statisticsConstants.defaultSortingType;
}
},
updateTransactionStatisticsFilter(filter) {
if (filter && isNumber(filter.dateType)) {
this.transactionStatisticsFilter.dateType = filter.dateType;
}
if (filter && isNumber(filter.startTime)) {
this.transactionStatisticsFilter.startTime = filter.startTime;
}
if (filter && isNumber(filter.endTime)) {
this.transactionStatisticsFilter.endTime = filter.endTime;
}
if (filter && isNumber(filter.chartType)) {
this.transactionStatisticsFilter.chartType = filter.chartType;
}
if (filter && isNumber(filter.chartDataType)) {
this.transactionStatisticsFilter.chartDataType = filter.chartDataType;
}
if (filter && isObject(filter.filterAccountIds)) {
this.transactionStatisticsFilter.filterAccountIds = filter.filterAccountIds;
}
if (filter && isObject(filter.filterCategoryIds)) {
this.transactionStatisticsFilter.filterCategoryIds = filter.filterCategoryIds;
}
if (filter && isNumber(filter.sortingType)) {
this.transactionStatisticsFilter.sortingType = filter.sortingType;
}
},
loadTransactionStatistics({ defaultCurrency }) {
const self = this;
return new Promise((resolve, reject) => {
services.getTransactionStatistics({
startTime: self.transactionStatisticsFilter.startTime,
endTime: self.transactionStatisticsFilter.endTime
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get transaction statistics' });
return;
}
loadTransactionStatistics(self, {
statistics: data.result,
defaultCurrency: defaultCurrency
});
if (self.transactionStatisticsStateInvalid) {
self.updateTransactionStatisticsInvalidState(false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get 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 get transaction statistics' });
} else {
reject(error);
}
});
});
},
}
});
+115
View File
@@ -0,0 +1,115 @@
import { defineStore } from 'pinia';
import { useUserStore } from './user.js';
import userState from '@/lib/userstate.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isObject } from '@/lib/common.js';
export const useTokensStore = defineStore('tokens', {
actions: {
getAllTokens() {
return new Promise((resolve, reject) => {
services.getTokens().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get session list' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load token 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 get session list' });
} else {
reject(error);
}
});
});
},
refreshTokenAndRevokeOldToken() {
const self = this;
return new Promise((resolve) => {
services.refreshToken().then(response => {
const data = response.data;
if (data && data.success && data.result && data.result.newToken) {
userState.updateToken(data.result.newToken);
if (data.result.user && isObject(data.result.user)) {
const userStore = useUserStore();
userStore.storeUserInfo(data.result.user);
}
if (data.result.oldTokenId) {
self.revokeToken({
tokenId: data.result.oldTokenId,
ignoreError: true
});
}
}
resolve(data.result);
});
});
},
revokeToken({ tokenId, ignoreError }) {
return new Promise((resolve, reject) => {
services.revokeToken({
tokenId: tokenId,
ignoreError: !!ignoreError
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to logout from this session' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to revoke token', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to logout from this session' });
} else {
reject(error);
}
});
});
},
revokeAllTokens() {
return new Promise((resolve, reject) => {
services.revokeAllTokens().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to logout all other sessions' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to revoke all tokens', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to logout all other sessions' });
} else {
reject(error);
}
});
});
}
}
});
+618
View File
@@ -0,0 +1,618 @@
import { defineStore } from 'pinia';
import { useAccountsStore } from './account.js';
import { useTransactionCategoriesStore } from './transactionCategory.js';
import { useOverviewStore } from './overview.js';
import { useStatisticsStore } from './statistics.js';
import { useExchangeRatesStore } from './exchangeRates.js';
import datetimeConstants from '@/consts/datetime.js';
import transactionConstants from '@/consts/transaction.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isNumber, isString } from '@/lib/common.js';
import {
getTimezoneOffsetMinutes,
parseDateFromUnixTime,
getYear,
getMonth,
getYearAndMonth,
getDay,
getDayOfWeekName
} from '@/lib/datetime.js';
const emptyTransactionResult = {
items: [],
transactionsNextTimeId: 0
};
function loadTransactionList(state, exchangeRatesStore, { transactions, reload, autoExpand, defaultCurrency }) {
if (reload) {
state.transactions = [];
}
if (transactions.items && transactions.items.length) {
const currentUtcOffset = getTimezoneOffsetMinutes();
let currentMonthListIndex = -1;
let currentMonthList = null;
for (let i = 0; i < transactions.items.length; i++) {
const item = transactions.items[i];
fillTransactionObject(state, item, currentUtcOffset);
const transactionTime = parseDateFromUnixTime(item.time, item.utcOffset, currentUtcOffset);
const transactionYear = getYear(transactionTime);
const transactionMonth = getMonth(transactionTime);
const transactionYearMonth = getYearAndMonth(transactionTime);
if (currentMonthList && currentMonthList.year === transactionYear && currentMonthList.month === transactionMonth) {
currentMonthList.items.push(Object.freeze(item));
continue;
}
for (let j = currentMonthListIndex + 1; j < state.transactions.length; j++) {
if (state.transactions[j].year === transactionYear && state.transactions[j].month === transactionMonth) {
currentMonthListIndex = j;
currentMonthList = state.transactions[j];
break;
}
}
if (!currentMonthList || currentMonthList.year !== transactionYear || currentMonthList.month !== transactionMonth) {
calculateMonthTotalAmount(state, exchangeRatesStore, currentMonthList, defaultCurrency, state.transactionsFilter.accountId, false);
state.transactions.push({
year: transactionYear,
month: transactionMonth,
yearMonth: transactionYearMonth,
opened: autoExpand,
items: []
});
currentMonthListIndex = state.transactions.length - 1;
currentMonthList = state.transactions[state.transactions.length - 1];
}
currentMonthList.items.push(Object.freeze(item));
calculateMonthTotalAmount(state, exchangeRatesStore, currentMonthList, defaultCurrency, state.transactionsFilter.accountId, true);
}
}
if (transactions.nextTimeSequenceId) {
state.transactionsNextTimeId = transactions.nextTimeSequenceId;
} else {
calculateMonthTotalAmount(state, exchangeRatesStore, state.transactions[state.transactions.length - 1], defaultCurrency, state.transactionsFilter.accountId, false);
state.transactionsNextTimeId = -1;
}
}
function updateTransactionInTransactionList(state, exchangeRatesStore, { transaction, defaultCurrency }) {
const currentUtcOffset = getTimezoneOffsetMinutes();
const transactionTime = parseDateFromUnixTime(transaction.time, transaction.utcOffset, currentUtcOffset);
const transactionYear = getYear(transactionTime);
const transactionMonth = getMonth(transactionTime);
for (let i = 0; i < state.transactions.length; i++) {
const transactionMonthList = state.transactions[i];
if (!transactionMonthList.items) {
continue;
}
for (let j = 0; j < transactionMonthList.items.length; j++) {
if (transactionMonthList.items[j].id === transaction.id) {
fillTransactionObject(state, transaction, currentUtcOffset);
if (transactionYear !== transactionMonthList.year ||
transactionMonth !== transactionMonthList.month ||
transaction.day !== transactionMonthList.items[j].day) {
state.transactionListStateInvalid = true;
return;
}
if ((state.transactionsFilter.categoryId && state.transactionsFilter.categoryId !== '0' && state.transactionsFilter.categoryId !== transaction.categoryId) ||
(state.transactionsFilter.accountId && state.transactionsFilter.accountId !== '0' &&
state.transactionsFilter.accountId !== transaction.sourceAccountId &&
state.transactionsFilter.accountId !== transaction.destinationAccountId &&
(!transaction.sourceAccount || state.transactionsFilter.accountId !== transaction.sourceAccount.parentId) &&
(!transaction.destinationAccount || state.transactionsFilter.accountId !== transaction.destinationAccount.parentId)
)
) {
transactionMonthList.items.splice(j, 1);
} else {
transactionMonthList.items.splice(j, 1, transaction);
}
if (transactionMonthList.items.length < 1) {
state.transactions.splice(i, 1);
} else {
calculateMonthTotalAmount(state, exchangeRatesStore, transactionMonthList, defaultCurrency, state.transactionsFilter.accountId, i >= state.transactions.length - 1 && state.transactionsNextTimeId > 0);
}
return;
}
}
}
}
function removeTransactionFromTransactionList(state, exchangeRatesStore, { transaction, defaultCurrency }) {
for (let i = 0; i < state.transactions.length; i++) {
const transactionMonthList = state.transactions[i];
if (!transactionMonthList.items ||
transactionMonthList.items[0].time < transaction.time ||
transactionMonthList.items[transactionMonthList.items.length - 1].time > transaction.time) {
continue;
}
for (let j = 0; j < transactionMonthList.items.length; j++) {
if (transactionMonthList.items[j].id === transaction.id) {
transactionMonthList.items.splice(j, 1);
}
}
if (transactionMonthList.items.length < 1) {
state.transactions.splice(i, 1);
} else {
calculateMonthTotalAmount(state, exchangeRatesStore, transactionMonthList, defaultCurrency, state.transactionsFilter.accountId, i >= state.transactions.length - 1 && state.transactionsNextTimeId > 0);
}
}
}
function calculateMonthTotalAmount(state, exchangeRatesStore, transactionMonthList, defaultCurrency, accountId, incomplete) {
if (!transactionMonthList) {
return;
}
let totalExpense = 0;
let totalIncome = 0;
let hasUnCalculatedTotalExpense = false;
let hasUnCalculatedTotalIncome = false;
for (let i = 0; i < transactionMonthList.items.length; i++) {
const transaction = transactionMonthList.items[i];
let amount = transaction.sourceAmount;
let account = transaction.sourceAccount;
if (accountId && transaction.destinationAccount && (transaction.destinationAccount.id === accountId || transaction.destinationAccount.parentId === accountId)) {
amount = transaction.destinationAmount;
account = transaction.destinationAccount;
}
if (!account) {
continue;
}
if (account.currency !== defaultCurrency) {
const balance = exchangeRatesStore.getExchangedAmount(amount, account.currency, defaultCurrency);
if (!isNumber(balance)) {
if (transaction.type === transactionConstants.allTransactionTypes.Expense) {
hasUnCalculatedTotalExpense = true;
} else if (transaction.type === transactionConstants.allTransactionTypes.Income) {
hasUnCalculatedTotalIncome = true;
}
continue;
}
amount = Math.floor(balance);
}
if (transaction.type === transactionConstants.allTransactionTypes.Expense) {
totalExpense += amount;
} else if (transaction.type === transactionConstants.allTransactionTypes.Income) {
totalIncome += amount;
} else if (transaction.type === transactionConstants.allTransactionTypes.Transfer && accountId) {
if (accountId === transaction.sourceAccountId) {
totalExpense += amount;
} else if (accountId === transaction.destinationAccountId) {
totalIncome += amount;
} else if (transaction.sourceAccount && accountId === transaction.sourceAccount.parentId &&
transaction.destinationAccount && accountId === transaction.destinationAccount.parentId) {
// Do Nothing
} else if (transaction.sourceAccount && accountId === transaction.sourceAccount.parentId) {
totalExpense += amount;
} else if (transaction.destinationAccount && accountId === transaction.destinationAccount.parentId) {
totalIncome += amount;
}
}
}
transactionMonthList.totalAmount = {
expense: totalExpense,
incompleteExpense: incomplete || hasUnCalculatedTotalExpense,
income: totalIncome,
incompleteIncome: incomplete || hasUnCalculatedTotalIncome
};
}
function fillTransactionObject(state, transaction, currentUtcOffset) {
if (!transaction) {
return;
}
const accountsStore = useAccountsStore();
const transactionCategoriesStore = useTransactionCategoriesStore();
const transactionTime = parseDateFromUnixTime(transaction.time, transaction.utcOffset, currentUtcOffset);
transaction.day = getDay(transactionTime);
transaction.dayOfWeek = getDayOfWeekName(transactionTime);
if (transaction.sourceAccountId) {
transaction.sourceAccount = accountsStore.allAccountsMap[transaction.sourceAccountId];
}
if (transaction.destinationAccountId) {
transaction.destinationAccount = accountsStore.allAccountsMap[transaction.destinationAccountId];
}
if (transaction.categoryId) {
transaction.category = transactionCategoriesStore.allTransactionCategoriesMap[transaction.categoryId];
}
return transaction;
}
export const useTransactionsStore = defineStore('transactions', {
state: () => ({
transactionsFilter: {
dateType: datetimeConstants.allDateRanges.All.type,
maxTime: 0,
minTime: 0,
type: 0,
categoryId: '0',
accountId: '0',
keyword: ''
},
transactions: [],
transactionsNextTimeId: 0,
transactionListStateInvalid: true,
}),
getters: {
noTransaction(state) {
for (let i = 0; i < state.transactions.length; i++) {
const transactionMonthList = state.transactions[i];
for (let j = 0; j < transactionMonthList.items.length; j++) {
if (transactionMonthList.items[j]) {
return false;
}
}
}
return true;
},
hasMoreTransaction(state) {
return state.transactionsNextTimeId > 0;
}
},
actions: {
updateTransactionListInvalidState(invalidState) {
this.transactionListStateInvalid = invalidState;
},
resetTransactions() {
this.transactionsFilter.dateType = datetimeConstants.allDateRanges.All.type;
this.transactionsFilter.maxTime = 0;
this.transactionsFilter.minTime = 0;
this.transactionsFilter.type = 0;
this.transactionsFilter.categoryId = '0';
this.transactionsFilter.accountId = '0';
this.transactionsFilter.keyword = '';
this.transactions = [];
this.transactionsNextTimeId = 0;
this.transactionListStateInvalid = true;
},
initTransactionListFilter(filter) {
if (filter && isNumber(filter.dateType)) {
this.transactionsFilter.dateType = filter.dateType;
} else {
this.transactionsFilter.dateType = datetimeConstants.allDateRanges.All.type;
}
if (filter && isNumber(filter.maxTime)) {
this.transactionsFilter.maxTime = filter.maxTime;
} else {
this.transactionsFilter.maxTime = 0;
}
if (filter && isNumber(filter.minTime)) {
this.transactionsFilter.minTime = filter.minTime;
} else {
this.transactionsFilter.minTime = 0;
}
if (filter && isNumber(filter.type)) {
this.transactionsFilter.type = filter.type;
} else {
this.transactionsFilter.type = 0;
}
if (filter && isString(filter.categoryId)) {
this.transactionsFilter.categoryId = filter.categoryId;
} else {
this.transactionsFilter.categoryId = '0';
}
if (filter && isString(filter.accountId)) {
this.transactionsFilter.accountId = filter.accountId;
} else {
this.transactionsFilter.accountId = '0';
}
if (filter && isString(filter.keyword)) {
this.transactionsFilter.keyword = filter.keyword;
} else {
this.transactionsFilter.keyword = '';
}
},
updateTransactionListFilter(filter) {
if (filter && isNumber(filter.dateType)) {
this.transactionsFilter.dateType = filter.dateType;
}
if (filter && isNumber(filter.maxTime)) {
this.transactionsFilter.maxTime = filter.maxTime;
}
if (filter && isNumber(filter.minTime)) {
this.transactionsFilter.minTime = filter.minTime;
}
if (filter && isNumber(filter.type)) {
this.transactionsFilter.type = filter.type;
}
if (filter && isString(filter.categoryId)) {
this.transactionsFilter.categoryId = filter.categoryId;
}
if (filter && isString(filter.accountId)) {
this.transactionsFilter.accountId = filter.accountId;
}
if (filter && isString(filter.keyword)) {
this.transactionsFilter.keyword = filter.keyword;
}
},
loadTransactions({ reload, autoExpand, defaultCurrency }) {
const self = this;
const exchangeRatesStore = useExchangeRatesStore();
let actualMaxTime = self.transactionsNextTimeId;
if (reload && self.transactionsFilter.maxTime > 0) {
actualMaxTime = self.transactionsFilter.maxTime * 1000 + 999;
} else if (reload && self.transactionsFilter.maxTime <= 0) {
actualMaxTime = 0;
}
return new Promise((resolve, reject) => {
services.getTransactions({
maxTime: actualMaxTime,
minTime: self.transactionsFilter.minTime * 1000,
type: self.transactionsFilter.type,
categoryId: self.transactionsFilter.categoryId,
accountId: self.transactionsFilter.accountId,
keyword: self.transactionsFilter.keyword
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (reload) {
loadTransactionList(self, exchangeRatesStore, {
transactions: emptyTransactionResult,
reload: reload,
autoExpand: autoExpand,
defaultCurrency: defaultCurrency
});
if (!self.transactionListStateInvalid) {
self.updateTransactionListInvalidState(true);
}
}
reject({ message: 'Unable to get transaction list' });
return;
}
loadTransactionList(self, exchangeRatesStore, {
transactions: data.result,
reload: reload,
autoExpand: autoExpand,
defaultCurrency: defaultCurrency
});
if (reload) {
if (self.transactionListStateInvalid) {
self.updateTransactionListInvalidState(false);
}
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load transaction list', error);
if (reload) {
loadTransactionList(self, exchangeRatesStore, {
transactions: emptyTransactionResult,
reload: reload,
autoExpand: autoExpand,
defaultCurrency: defaultCurrency
});
if (!self.transactionListStateInvalid) {
self.updateTransactionListInvalidState(true);
}
}
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get transaction list' });
} else {
reject(error);
}
});
});
},
getTransaction({ transactionId }) {
return new Promise((resolve, reject) => {
services.getTransaction({
id: transactionId
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get transaction' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to load transaction 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 get transaction' });
} else {
reject(error);
}
});
});
},
saveTransaction({ transaction, defaultCurrency }) {
const self = this;
const exchangeRatesStore = useExchangeRatesStore();
return new Promise((resolve, reject) => {
let promise = null;
if (!transaction.id) {
promise = services.addTransaction(transaction);
} else {
promise = services.modifyTransaction(transaction);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!transaction.id) {
reject({ message: 'Unable to add transaction' });
} else {
reject({ message: 'Unable to save transaction' });
}
return;
}
if (!transaction.id) {
if (!self.transactionListStateInvalid) {
self.updateTransactionListInvalidState(true);
}
} else {
updateTransactionInTransactionList(self, exchangeRatesStore, {
transaction: data.result,
defaultCurrency: defaultCurrency
});
}
const accountsStore = useAccountsStore();
if (!accountsStore.accountListStateInvalid) {
accountsStore.updateAccountListInvalidState(true);
}
const overviewStore = useOverviewStore();
if (!overviewStore.transactionOverviewStateInvalid) {
overviewStore.updateTransactionOverviewInvalidState(true);
}
const statisticsStore = useStatisticsStore();
if (!statisticsStore.transactionStatisticsStateInvalid) {
statisticsStore.updateTransactionStatisticsInvalidState(true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save transaction', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!transaction.id) {
reject({ message: 'Unable to add transaction' });
} else {
reject({ message: 'Unable to save transaction' });
}
} else {
reject(error);
}
});
});
},
deleteTransaction({ transaction, defaultCurrency, beforeResolve }) {
const self = this;
const exchangeRatesStore = useExchangeRatesStore();
return new Promise((resolve, reject) => {
services.deleteTransaction({
id: transaction.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this transaction' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
removeTransactionFromTransactionList(self, exchangeRatesStore, {
transaction: transaction,
defaultCurrency: defaultCurrency
});
});
} else {
removeTransactionFromTransactionList(self, exchangeRatesStore, {
transaction: transaction,
defaultCurrency: defaultCurrency
});
}
const accountsStore = useAccountsStore();
if (!accountsStore.accountListStateInvalid) {
accountsStore.updateAccountListInvalidState(true);
}
const overviewStore = useOverviewStore();
if (!overviewStore.transactionOverviewStateInvalid) {
overviewStore.updateTransactionOverviewInvalidState(true);
}
const statisticsStore = useStatisticsStore();
if (!statisticsStore.transactionStatisticsStateInvalid) {
statisticsStore.updateTransactionStatisticsInvalidState(true);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete transaction', 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 transaction' });
} else {
reject(error);
}
});
});
},
collapseMonthInTransactionList({ month, collapse }) {
if (month) {
month.opened = !collapse;
}
}
}
});
+483
View File
@@ -0,0 +1,483 @@
import { defineStore } from 'pinia';
import categoryConstants from '@/consts/category.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
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) {
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, category);
break;
}
}
}
state.allTransactionCategoriesMap[category.id] = category;
}
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: {
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 get category list' });
return;
}
if (!data.result[categoryConstants.allCategoryTypes.Income]) {
data.result[categoryConstants.allCategoryTypes.Income] = [];
}
if (!data.result[categoryConstants.allCategoryTypes.Expense]) {
data.result[categoryConstants.allCategoryTypes.Expense] = [];
}
if (!data.result[categoryConstants.allCategoryTypes.Transfer]) {
data.result[categoryConstants.allCategoryTypes.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 = [];
}
}
}
loadTransactionCategoryList(self, data.result);
if (self.transactionCategoryListStateInvalid) {
self.updateTransactionCategoryListInvalidState(false);
}
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 get 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 get 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 get category' });
} else {
reject(error);
}
});
});
},
saveCategory({ category }) {
const self = this;
return new Promise((resolve, reject) => {
let promise = null;
if (!category.id) {
promise = services.addTransactionCategory(category);
} else {
promise = services.modifyTransactionCategory(category);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!category.id) {
reject({ message: 'Unable to add category' });
} else {
reject({ message: 'Unable to save category' });
}
return;
}
if (!data.result.subCategories) {
data.result.subCategories = [];
}
if (!category.id) {
addCategoryToTransactionCategoryList(self, data.result);
} else {
updateCategoryInTransactionCategoryList(self, data.result);
}
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 (!category.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);
}
});
});
}
}
});
+311
View File
@@ -0,0 +1,311 @@
import { defineStore } from 'pinia';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
function loadTransactionTagList(state, tags) {
state.allTransactionTags = tags;
state.allTransactionTagsMap = {};
for (let i = 0; i < tags.length; i++) {
const tag = tags[i];
state.allTransactionTagsMap[tag.id] = tag;
}
}
function addTagToTransactionTagList(state, tag) {
state.allTransactionTags.push(tag);
state.allTransactionTagsMap[tag.id] = tag;
}
function updateTagInTransactionTagList(state, tag) {
for (let i = 0; i < state.allTransactionTags.length; i++) {
if (state.allTransactionTags[i].id === tag.id) {
state.allTransactionTags.splice(i, 1, tag);
break;
}
}
state.allTransactionTagsMap[tag.id] = tag;
}
function updateTagDisplayOrderInTransactionTagList(state, { from, to }) {
state.allTransactionTags.splice(to, 0, state.allTransactionTags.splice(from, 1)[0]);
}
function updateTagVisibilityInTransactionTagList(state, { tag, hidden }) {
if (state.allTransactionTagsMap[tag.id]) {
state.allTransactionTagsMap[tag.id].hidden = hidden;
}
}
function removeTagFromTransactionTagList(state, tag) {
for (let i = 0; i < state.allTransactionTags.length; i++) {
if (state.allTransactionTags[i].id === tag.id) {
state.allTransactionTags.splice(i, 1);
break;
}
}
if (state.allTransactionTagsMap[tag.id]) {
delete state.allTransactionTagsMap[tag.id];
}
}
export const useTransactionTagsStore = defineStore('transactionTags', {
state: () => ({
allTransactionTags: [],
allTransactionTagsMap: {},
transactionTagListStateInvalid: true,
}),
actions: {
updateTransactionTagListInvalidState(invalidState) {
this.transactionTagListStateInvalid = invalidState;
},
resetTransactionTags() {
this.allTransactionTags = [];
this.allTransactionTagsMap = {};
this.transactionTagListStateInvalid = true;
},
loadAllTags({ force }) {
const self = this;
if (!force && !self.transactionTagListStateInvalid) {
return new Promise((resolve) => {
resolve(self.allTransactionTags);
});
}
return new Promise((resolve, reject) => {
services.getAllTransactionTags().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get tag list' });
return;
}
loadTransactionTagList(self, data.result);
if (self.transactionTagListStateInvalid) {
self.updateTransactionTagListInvalidState(false);
}
resolve(data.result);
}).catch(error => {
if (force) {
logger.error('failed to force load tag list', error);
} else {
logger.error('failed to load tag 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 get tag list' });
} else {
reject(error);
}
});
});
},
saveTag({ tag }) {
const self = this;
return new Promise((resolve, reject) => {
let promise = null;
if (!tag.id) {
promise = services.addTransactionTag(tag);
} else {
promise = services.modifyTransactionTag(tag);
}
promise.then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (!tag.id) {
reject({ message: 'Unable to add tag' });
} else {
reject({ message: 'Unable to save tag' });
}
return;
}
if (!tag.id) {
addTagToTransactionTagList(self, data.result);
} else {
updateTagInTransactionTagList(self, data.result);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save tag', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
if (!tag.id) {
reject({ message: 'Unable to add tag' });
} else {
reject({ message: 'Unable to save tag' });
}
} else {
reject(error);
}
});
});
},
changeTagDisplayOrder({ tagId, from, to }) {
const self = this;
return new Promise((resolve, reject) => {
let tag = null;
for (let i = 0; i < self.allTransactionTags.length; i++) {
if (self.allTransactionTags[i].id === tagId) {
tag = self.allTransactionTags[i];
break;
}
}
if (!tag || !self.allTransactionTags[to]) {
reject({ message: 'Unable to move tag' });
return;
}
if (!self.transactionTagListStateInvalid) {
self.updateTransactionTagListInvalidState(true);
}
updateTagDisplayOrderInTransactionTagList(self, {
tag: tag,
from: from,
to: to
});
resolve();
});
},
updateTagDisplayOrders() {
const self = this;
const newDisplayOrders = [];
for (let i = 0; i < self.allTransactionTags.length; i++) {
newDisplayOrders.push({
id: self.allTransactionTags[i].id,
displayOrder: i + 1
});
}
return new Promise((resolve, reject) => {
services.moveTransactionTag({
newDisplayOrders: newDisplayOrders
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to move tag' });
return;
}
if (self.transactionTagListStateInvalid) {
self.updateTransactionTagListInvalidState(false);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to save tags 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 tag' });
} else {
reject(error);
}
});
});
},
hideTag({ tag, hidden }) {
const self = this;
return new Promise((resolve, reject) => {
services.hideTransactionTag({
id: tag.id,
hidden: hidden
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
if (hidden) {
reject({ message: 'Unable to hide this tag' });
} else {
reject({ message: 'Unable to unhide this tag' });
}
return;
}
updateTagVisibilityInTransactionTagList(self, {
tag: tag,
hidden: hidden
});
resolve(data.result);
}).catch(error => {
logger.error('failed to change tag 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 tag' });
} else {
reject({ message: 'Unable to unhide this tag' });
}
} else {
reject(error);
}
});
});
},
deleteTag({ tag, beforeResolve }) {
const self = this;
return new Promise((resolve, reject) => {
services.deleteTransactionTag({
id: tag.id
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to delete this tag' });
return;
}
if (beforeResolve) {
beforeResolve(() => {
removeTagFromTransactionTagList(self, tag);
});
} else {
removeTagFromTransactionTagList(self, tag);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to delete tag', 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 tag' });
} else {
reject(error);
}
});
});
}
}
});
+142
View File
@@ -0,0 +1,142 @@
import { defineStore } from 'pinia';
import userState from '@/lib/userstate.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isBoolean } from '@/lib/common.js';
export const useTwoFactorAuthStore = defineStore('twoFactorAuth', {
actions: {
get2FAStatus() {
return new Promise((resolve, reject) => {
services.get2FAStatus().then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !isBoolean(data.result.enable)) {
reject({ message: 'Unable to get current two factor authentication status' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get 2fa status', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get current two factor authentication status' });
} else {
reject(error);
}
});
});
},
enable2FA() {
return new Promise((resolve, reject) => {
services.enable2FA().then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.qrcode || !data.result.secret) {
reject({ message: 'Unable to enable two factor authentication' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to request to enable 2fa', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to enable two factor authentication' });
} else {
reject(error);
}
});
});
},
confirmEnable2FA({ secret, passcode }) {
return new Promise((resolve, reject) => {
services.confirmEnable2FA({
secret: secret,
passcode: passcode
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.token) {
reject({ message: 'Unable to enable two factor authentication' });
return;
}
if (data.result.token) {
userState.updateToken(data.result.token);
}
resolve(data.result);
}).catch(error => {
logger.error('failed to confirm to enable 2fa', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to enable two factor authentication' });
} else {
reject(error);
}
});
});
},
disable2FA({ password }) {
return new Promise((resolve, reject) => {
services.disable2FA({
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to disable two factor authentication' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to disable 2fa', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to disable two factor authentication' });
} else {
reject(error);
}
});
});
},
regenerate2FARecoveryCode({ password }) {
return new Promise((resolve, reject) => {
services.regenerate2FARecoveryCode({
password: password
}).then(response => {
const data = response.data;
if (!data || !data.success || !data.result || !data.result.recoveryCodes || !data.result.recoveryCodes.length) {
reject({ message: 'Unable to regenerate two factor authentication backup codes' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to regenerate 2fa recovery code', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to regenerate two factor authentication backup codes' });
} else {
reject(error);
}
});
});
}
}
});
+140
View File
@@ -0,0 +1,140 @@
import { defineStore } from 'pinia';
import { useSettingsStore } from './setting.js';
import userState from '@/lib/userstate.js';
import services from '@/lib/services.js';
import logger from '@/lib/logger.js';
import { isNumber } from '@/lib/common.js';
export const useUserStore = defineStore('user', {
state: () => ({
currentUserInfo: userState.getUserInfo()
}),
getters: {
currentUserNickname(state) {
const userInfo = state.currentUserInfo || {};
return userInfo.nickname || userInfo.username || null;
},
currentUserDefaultAccountId(state) {
const userInfo = state.currentUserInfo || {};
return userInfo.defaultAccountId || '';
},
currentUserLanguage(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return userInfo.language || settingsStore.language;
},
currentUserDefaultCurrency(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return userInfo.defaultCurrency || settingsStore.currency;
},
currentUserFirstDayOfWeek(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return isNumber(userInfo.firstDayOfWeek) ? userInfo.firstDayOfWeek : settingsStore.firstDayOfWeek;
},
currentUserLongDateFormat(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return isNumber(userInfo.longDateFormat) ? userInfo.longDateFormat : settingsStore.longDateFormat;
},
currentUserShortDateFormat(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return isNumber(userInfo.shortDateFormat) ? userInfo.shortDateFormat : settingsStore.shortDateFormat;
},
currentUserLongTimeFormat(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return isNumber(userInfo.longTimeFormat) ? userInfo.longTimeFormat : settingsStore.longTimeFormat;
},
currentUserShortTimeFormat(state) {
const settingsStore = useSettingsStore();
const userInfo = state.currentUserInfo || {};
return isNumber(userInfo.shortTimeFormat) ? userInfo.shortTimeFormat : settingsStore.shortTimeFormat;
}
},
actions: {
storeUserInfo(userInfo) {
this.currentUserInfo = userInfo;
userState.updateUserInfo(userInfo);
},
resetUserInfo() {
this.currentUserInfo = null;
userState.clearUserInfo();
},
getCurrentUserProfile() {
return new Promise((resolve, reject) => {
services.getProfile().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get user profile' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get user profile', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get user profile' });
} else {
reject(error);
}
});
});
},
getUserDataStatistics() {
return new Promise((resolve, reject) => {
services.getUserDataStatistics().then(response => {
const data = response.data;
if (!data || !data.success || !data.result) {
reject({ message: 'Unable to get user statistics data' });
return;
}
resolve(data.result);
}).catch(error => {
logger.error('failed to get user statistics data', error);
if (error.response && error.response.data && error.response.data.errorMessage) {
reject({ error: error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get user statistics data' });
} else {
reject(error);
}
});
});
},
getExportedUserData() {
return new Promise((resolve, reject) => {
services.getExportedUserData().then(response => {
if (response && response.headers && response.headers['content-type'] !== 'text/csv') {
reject({ message: 'Unable to get exported user data' });
return;
}
const blob = new Blob([response.data], { type: response.headers['content-type'] });
resolve(blob);
}).catch(error => {
logger.error('failed to get user statistics data', error);
if (error.response && error.response.headers['content-type'] === 'text/text' && error.response && error.response.data) {
reject({ message: 'error.' + error.response.data });
} else if (!error.processed) {
reject({ message: 'Unable to get exported user data' });
} else {
reject(error);
}
});
});
},
}
});