mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-15 07:27:33 +08:00
use pinia to replace vuex, code refactor
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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' });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user