diff --git a/src/lib/account.ts b/src/lib/account.ts index 4735ea8b..4b5ba753 100644 --- a/src/lib/account.ts +++ b/src/lib/account.ts @@ -196,7 +196,7 @@ export function getCategorizedAccountsWithVisibleCount(categorizedAccountsMap: R return ret; } -export function getAllFilteredAccountsBalance(categorizedAccounts: CategorizedAccount[], accountFilter: (account: Account) => boolean): AccountBalance[] { +export function getAllFilteredAccountsBalance(categorizedAccounts: Record, accountFilter: (account: Account) => boolean): AccountBalance[] { const allAccountCategories = AccountCategory.values(); const ret: AccountBalance[] = []; diff --git a/src/models/account.ts b/src/models/account.ts index 7a558a42..212bf4ef 100644 --- a/src/models/account.ts +++ b/src/models/account.ts @@ -288,6 +288,11 @@ export interface AccountBalance { readonly currency: string; } +export interface AccountDisplayBalance { + readonly balance: string; + readonly currency: string; +} + export interface CategorizedAccount { readonly category: number; readonly name: string; @@ -306,3 +311,9 @@ export interface AccountCategoriesWithVisibleCount { readonly allVisibleSubAccountCounts: Record; readonly allFirstVisibleSubAccountIndexes: Record; } + +export interface AccountShowingIds { + readonly accounts: Record; + readonly subAccounts: Record; + +} diff --git a/src/stores/account.js b/src/stores/account.js deleted file mode 100644 index ddda0cff..00000000 --- a/src/stores/account.js +++ /dev/null @@ -1,980 +0,0 @@ -import { defineStore } from 'pinia'; - -import { useUserStore } from './user.ts'; -import { useExchangeRatesStore } from './exchangeRates.ts'; - -import { AccountType, AccountCategory } from '@/core/account.ts'; -import { PARENT_ACCOUNT_CURRENCY_PLACEHOLDER } from '@/consts/currency.ts'; -import { Account } from '@/models/account.ts'; - -import services from '@/lib/services.ts'; -import logger from '@/lib/logger.ts'; -import { isNumber, isEquals } from '@/lib/common.ts'; -import { getCurrentUnixTime } from '@/lib/datetime.ts'; -import { getCategorizedAccountsMap, getAllFilteredAccountsBalance } from '@/lib/account.ts'; - -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.allCategorizedAccountsMap = getCategorizedAccountsMap(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.allCategorizedAccountsMap[account.category]) { - const accountList = state.allCategorizedAccountsMap[account.category].accounts; - accountList.push(account); - } else { - state.allCategorizedAccountsMap = getCategorizedAccountsMap(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.allCategorizedAccountsMap[account.category]) { - const accountList = state.allCategorizedAccountsMap[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, updateListOrder, updateGlobalListOrder }) { - let fromAccount = null; - let toAccount = null; - - if (state.allCategorizedAccountsMap[account.category]) { - const accountList = state.allCategorizedAccountsMap[account.category].accounts; - - if (updateListOrder) { - fromAccount = accountList[from]; - toAccount = accountList[to]; - accountList.splice(to, 0, accountList.splice(from, 1)[0]); - } else { - fromAccount = accountList[to]; - - if (from < to) { - toAccount = accountList[to - 1]; - } else if (from > to) { - toAccount = accountList[to + 1]; - } - } - } - - if (updateGlobalListOrder && 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].visible = !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.allCategorizedAccountsMap[account.category]) { - const accountList = state.allCategorizedAccountsMap[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: {}, - allCategorizedAccountsMap: {}, - accountListStateInvalid: true, - }), - getters: { - allPlainAccounts(state) { - const allAccounts = []; - - for (let i = 0; i < state.allAccounts.length; i++) { - const account = state.allAccounts[i]; - - if (account.type === AccountType.SingleAccount.type) { - allAccounts.push(account); - } else if (account.type === AccountType.MultiSubAccounts.type) { - if (account.subAccounts) { - 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 === AccountType.SingleAccount.type) { - allVisibleAccounts.push(account); - } else if (account.type === AccountType.MultiSubAccounts.type) { - if (account.subAccounts) { - 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.allCategorizedAccountsMap) { - if (!Object.prototype.hasOwnProperty.call(state.allCategorizedAccountsMap, category)) { - continue; - } - - allAccountCount += state.allCategorizedAccountsMap[category].accounts.length; - } - - return allAccountCount; - }, - allVisibleAccountsCount(state) { - let shownAccountCount = 0; - - for (let category in state.allCategorizedAccountsMap) { - if (!Object.prototype.hasOwnProperty.call(state.allCategorizedAccountsMap, category)) { - continue; - } - - const accountList = state.allCategorizedAccountsMap[category].accounts; - - for (let i = 0; i < accountList.length; i++) { - if (!accountList[i].hidden) { - shownAccountCount++; - } - } - } - - return shownAccountCount; - } - }, - actions: { - generateNewAccountModel() { - const userStore = useUserStore(); - const now = getCurrentUnixTime(); - return Account.createNewAccount(userStore.currentUserDefaultCurrency, now); - }, - generateNewSubAccountModel(parentAccount) { - const userStore = useUserStore(); - const now = getCurrentUnixTime(); - return parentAccount.createNewSubAccount(userStore.currentUserDefaultCurrency, now); - }, - updateAccountListInvalidState(invalidState) { - this.accountListStateInvalid = invalidState; - }, - resetAccounts() { - this.allAccounts = []; - this.allAccountsMap = {}; - this.allCategorizedAccountsMap = {}; - this.accountListStateInvalid = true; - }, - getFirstShowingIds(showHidden) { - const ret = { - accounts: {}, - subAccounts: {} - }; - - for (let category in this.allCategorizedAccountsMap) { - if (!Object.prototype.hasOwnProperty.call(this.allCategorizedAccountsMap, category)) { - continue; - } - - if (!this.allCategorizedAccountsMap[category] || !this.allCategorizedAccountsMap[category].accounts) { - continue; - } - - const accounts = this.allCategorizedAccountsMap[category].accounts; - - for (let i = 0; i < accounts.length; i++) { - const account = accounts[i]; - - if (account.type === AccountType.MultiSubAccounts.type && 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.allCategorizedAccountsMap) { - if (!Object.prototype.hasOwnProperty.call(this.allCategorizedAccountsMap, category)) { - continue; - } - - if (!this.allCategorizedAccountsMap[category] || !this.allCategorizedAccountsMap[category].accounts) { - continue; - } - - const accounts = this.allCategorizedAccountsMap[category].accounts; - - for (let i = accounts.length - 1; i >= 0; i--) { - const account = accounts[i]; - - if (account.type === AccountType.MultiSubAccounts.type && 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; - }, - getAccountStatementDate(accountId) { - if (!accountId) { - return null; - } - - const accountIds = accountId.split(','); - let mainAccount = null; - - for (let i = 0; i < accountIds.length; i++) { - const id = accountIds[i]; - let account = this.allAccountsMap[id]; - - if (!account) { - return null; - } - - if (account.parentId !== '0') { - account = this.allAccountsMap[account.parentId]; - } - - if (mainAccount !== null) { - if (mainAccount.id !== account.id) { - return null; - } else { - continue; - } - } - - mainAccount = account; - } - - if (!mainAccount) { - return null; - } - - if (mainAccount.category === AccountCategory.CreditCard.type) { - return mainAccount.creditCardStatementDate; - } - - return null; - }, - getNetAssets(showAccountBalance) { - if (!showAccountBalance) { - return '***'; - } - - const userStore = useUserStore(); - const exchangeRatesStore = useExchangeRatesStore(); - const accountsBalance = getAllFilteredAccountsBalance(this.allCategorizedAccountsMap, () => 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.allCategorizedAccountsMap, 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.allCategorizedAccountsMap, 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.allCategorizedAccountsMap, account => account.category === accountCategory.type); - 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 !== AccountType.SingleAccount.type) { - return null; - } - - if (showAccountBalance) { - if (account.isAsset) { - return account.balance; - } else if (account.isLiability) { - return -account.balance; - } else { - return account.balance; - } - } else { - return '***'; - } - }, - getAccountSubAccountBalance(showAccountBalance, showHidden, account, subAccountId) { - if (account.type !== AccountType.MultiSubAccounts.type) { - return null; - } - - const userStore = useUserStore(); - const exchangeRatesStore = useExchangeRatesStore(); - let resultCurrency = userStore.currentUserDefaultCurrency; - - if (!account.subAccounts || !account.subAccounts.length) { - return { - balance: showAccountBalance ? 0 : '***', - currency: resultCurrency - }; - } - - const allSubAccountCurrenciesMap = {}; - const allSubAccountCurrencies = []; - let totalBalance = 0; - - for (let i = 0; i < account.subAccounts.length; i++) { - const subAccount = account.subAccounts[i]; - - if (!showHidden && subAccount.hidden) { - continue; - } - - if (!allSubAccountCurrenciesMap[subAccount.currency]) { - allSubAccountCurrenciesMap[subAccount.currency] = true; - allSubAccountCurrencies.push(subAccount.currency); - } - } - - if (allSubAccountCurrencies.length === 0) { - return { - balance: showAccountBalance ? 0 : '***', - currency: resultCurrency - }; - } - - if (allSubAccountCurrencies.length === 1) { - resultCurrency = allSubAccountCurrencies[0]; - } - - let hasUnCalculatedAmount = false; - - for (let i = 0; i < account.subAccounts.length; i++) { - const subAccount = account.subAccounts[i]; - - if (!showHidden && subAccount.hidden) { - continue; - } - - if (subAccountId) { - if (subAccountId === subAccount.id) { - return { - balance: showAccountBalance ? this.getAccountBalance(showAccountBalance, subAccount) : '***', - currency: subAccount.currency - }; - } - } - - if (subAccount === resultCurrency) { - if (subAccount.isAsset) { - totalBalance += subAccount.balance; - } else if (subAccount.isLiability) { - totalBalance -= subAccount.balance; - } else { - totalBalance += subAccount.balance; - } - } else { - const balance = exchangeRatesStore.getExchangedAmount(subAccount.balance, subAccount.currency, resultCurrency); - - if (!isNumber(balance)) { - hasUnCalculatedAmount = true; - continue; - } - - if (subAccount.isAsset) { - totalBalance += Math.floor(balance); - } else if (subAccount.isLiability) { - totalBalance -= Math.floor(balance); - } else { - totalBalance += Math.floor(balance); - } - } - } - - if (subAccountId) { // not found specified id in sub accounts - return null; - } - - if (hasUnCalculatedAmount) { - totalBalance += '+'; - } - - return { - balance: showAccountBalance ? totalBalance : '***', - currency: resultCurrency - }; - }, - hasAccount(accountCategory, visibleOnly) { - if (!this.allCategorizedAccountsMap[accountCategory.type] || - !this.allCategorizedAccountsMap[accountCategory.type].accounts || - !this.allCategorizedAccountsMap[accountCategory.type].accounts.length) { - return false; - } - - let shownCount = 0; - - for (let i = 0; i < this.allCategorizedAccountsMap[accountCategory.type].accounts.length; i++) { - const account = this.allCategorizedAccountsMap[accountCategory.type].accounts[i]; - - if (!visibleOnly || !account.hidden) { - shownCount++; - } - } - - return shownCount > 0; - }, - hasVisibleSubAccount(showHidden, account) { - if (!account || account.type !== AccountType.MultiSubAccounts.type || !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 retrieve account list' }); - return; - } - - if (self.accountListStateInvalid) { - self.updateAccountListInvalidState(false); - } - - const accounts = Account.ofMany(data.result); - - if (force && data.result && isEquals(self.allAccounts, accounts)) { - reject({ message: 'Account list is up to date' }); - return; - } - - loadAccountList(self, accounts); - - resolve(accounts); - }).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 retrieve 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 retrieve account' }); - return; - } - - const account = Account.of(data.result); - - resolve(account); - }).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 retrieve account' }); - } else { - reject(error); - } - }); - }); - }, - saveAccount({ account, subAccounts, isEdit, clientSessionId }) { - const self = this; - - return new Promise((resolve, reject) => { - const oldAccount = isEdit ? self.allAccountsMap[account.id] : null; - let promise = null; - - if (!isEdit) { - promise = services.addAccount(account.toCreateRequest(clientSessionId, subAccounts)); - } else { - promise = services.modifyAccount(account.toModifyRequest(subAccounts)); - } - - promise.then(response => { - const data = response.data; - - if (!data || !data.success || !data.result) { - if (!isEdit) { - reject({ message: 'Unable to add account' }); - } else { - reject({ message: 'Unable to save account' }); - } - return; - } - - const newAccount = Account.of(data.result); - - if (!isEdit) { - addAccountToAccountList(self, newAccount); - } else { - if (oldAccount && oldAccount.category === newAccount.category) { - updateAccountToAccountList(self, newAccount); - } else { - self.updateAccountListInvalidState(true); - } - } - - resolve(newAccount); - }).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 (!isEdit) { - reject({ message: 'Unable to add account' }); - } else { - reject({ message: 'Unable to save account' }); - } - } else { - reject(error); - } - }); - }); - }, - changeAccountDisplayOrder({ accountId, from, to, updateListOrder, updateGlobalListOrder }) { - const self = this; - const account = self.allAccountsMap[accountId]; - - return new Promise((resolve, reject) => { - if (!account || - !self.allCategorizedAccountsMap[account.category] || - !self.allCategorizedAccountsMap[account.category].accounts || - !self.allCategorizedAccountsMap[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, - updateListOrder: updateListOrder, - updateGlobalListOrder: updateGlobalListOrder - }); - - resolve(); - }); - }, - updateAccountDisplayOrders() { - const self = this; - const newDisplayOrders = []; - - for (let category in self.allCategorizedAccountsMap) { - if (!Object.prototype.hasOwnProperty.call(self.allCategorizedAccountsMap, category)) { - continue; - } - - const accountList = self.allCategorizedAccountsMap[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); - } - }); - }); - } - } -}); diff --git a/src/stores/account.ts b/src/stores/account.ts new file mode 100644 index 00000000..c66bd67e --- /dev/null +++ b/src/stores/account.ts @@ -0,0 +1,1016 @@ +import { ref, computed } from 'vue'; +import { defineStore } from 'pinia'; + +import { useUserStore } from './user.ts'; +import { useExchangeRatesStore } from './exchangeRates.ts'; + +import type { BeforeResolveFunction } from '@/core/base.ts'; + +import { AccountType, AccountCategory } from '@/core/account.ts'; +import { + type AccountNewDisplayOrderRequest, + type AccountDisplayBalance, + type CategorizedAccount, + type AccountShowingIds, + Account +} from '@/models/account.ts'; + +import { isNumber, isEquals } from '@/lib/common.ts'; +import { getCurrentUnixTime } from '@/lib/datetime.ts'; +import { getCategorizedAccountsMap, getAllFilteredAccountsBalance } from '@/lib/account.ts'; +import services from '@/lib/services.ts'; +import logger from '@/lib/logger.ts'; + +export const useAccountsStore = defineStore('accounts', () => { + const userStore = useUserStore(); + const exchangeRatesStore = useExchangeRatesStore(); + + const allAccounts = ref([]); + const allAccountsMap = ref>({}); + const allCategorizedAccountsMap = ref>({}); + const accountListStateInvalid = ref(true); + + function loadAccountList(accounts: Account[]): void { + allAccounts.value = accounts; + allAccountsMap.value = {}; + + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i]; + allAccountsMap.value[account.id] = account; + + if (account.childrenAccounts) { + for (let j = 0; j < account.childrenAccounts.length; j++) { + const subAccount = account.childrenAccounts[j]; + allAccountsMap.value[subAccount.id] = subAccount; + } + } + } + + allCategorizedAccountsMap.value = getCategorizedAccountsMap(accounts); + } + + function addAccountToAccountList(account: Account): void { + let insertIndexToAllList = 0; + + for (let i = 0; i < allAccounts.value.length; i++) { + if (allAccounts.value[i].category > account.category) { + insertIndexToAllList = i; + break; + } + } + + allAccounts.value.splice(insertIndexToAllList, 0, account); + + allAccountsMap.value[account.id] = account; + + if (account.childrenAccounts) { + for (let i = 0; i < account.childrenAccounts.length; i++) { + const subAccount = account.childrenAccounts[i]; + allAccountsMap.value[subAccount.id] = subAccount; + } + } + + if (allCategorizedAccountsMap.value[account.category]) { + const accountList = allCategorizedAccountsMap.value[account.category].accounts; + accountList.push(account); + } else { + allCategorizedAccountsMap.value = getCategorizedAccountsMap(allAccounts.value); + } + } + + function updateAccountToAccountList(account: Account): void { + for (let i = 0; i < allAccounts.value.length; i++) { + if (allAccounts.value[i].id === account.id) { + allAccounts.value.splice(i, 1, account); + break; + } + } + + allAccountsMap.value[account.id] = account; + + if (account.childrenAccounts) { + for (let i = 0; i < account.childrenAccounts.length; i++) { + const subAccount = account.childrenAccounts[i]; + allAccountsMap.value[subAccount.id] = subAccount; + } + } + + if (allCategorizedAccountsMap.value[account.category]) { + const accountList = allCategorizedAccountsMap.value[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({ account, from, to, updateListOrder, updateGlobalListOrder }: { account: Account, from: number, to: number, updateListOrder: boolean, updateGlobalListOrder: boolean }): void { + let fromAccount = null; + let toAccount = null; + + if (allCategorizedAccountsMap.value[account.category]) { + const accountList = allCategorizedAccountsMap.value[account.category].accounts; + + if (updateListOrder) { + fromAccount = accountList[from]; + toAccount = accountList[to]; + accountList.splice(to, 0, accountList.splice(from, 1)[0]); + } else { + fromAccount = accountList[to]; + + if (from < to) { + toAccount = accountList[to - 1]; + } else if (from > to) { + toAccount = accountList[to + 1]; + } + } + } + + if (updateGlobalListOrder && fromAccount && toAccount) { + let globalFromIndex = -1; + let globalToIndex = -1; + + for (let i = 0; i < allAccounts.value.length; i++) { + if (allAccounts.value[i].id === fromAccount.id) { + globalFromIndex = i; + } else if (allAccounts.value[i].id === toAccount.id) { + globalToIndex = i; + } + } + + if (globalFromIndex >= 0 && globalToIndex >= 0) { + allAccounts.value.splice(globalToIndex, 0, allAccounts.value.splice(globalFromIndex, 1)[0]); + } + } + } + + function updateAccountVisibilityInAccountList({ account, hidden }: { account: Account, hidden: boolean }): void { + if (allAccountsMap.value[account.id]) { + allAccountsMap.value[account.id].visible = !hidden; + } + } + + function removeAccountFromAccountList(account: Account): void { + for (let i = 0; i < allAccounts.value.length; i++) { + if (allAccounts.value[i].id === account.id) { + allAccounts.value.splice(i, 1); + break; + } + } + + if (allAccountsMap.value[account.id] && allAccountsMap.value[account.id].childrenAccounts) { + const subAccounts = allAccountsMap.value[account.id].childrenAccounts as Account[]; + + for (let i = 0; i < subAccounts.length; i++) { + const subAccount = subAccounts[i]; + if (allAccountsMap.value[subAccount.id]) { + delete allAccountsMap.value[subAccount.id]; + } + } + } + + if (allAccountsMap.value[account.id]) { + delete allAccountsMap.value[account.id]; + } + + if (allCategorizedAccountsMap.value[account.category]) { + const accountList = allCategorizedAccountsMap.value[account.category].accounts; + + for (let i = 0; i < accountList.length; i++) { + if (accountList[i].id === account.id) { + accountList.splice(i, 1); + break; + } + } + } + } + + const allPlainAccounts = computed(() => { + const allAccountsList = []; + + for (let i = 0; i < allAccounts.value.length; i++) { + const account = allAccounts.value[i]; + + if (account.type === AccountType.SingleAccount.type) { + allAccountsList.push(account); + } else if (account.type === AccountType.MultiSubAccounts.type) { + if (account.childrenAccounts) { + for (let j = 0; j < account.childrenAccounts.length; j++) { + const subAccount = account.childrenAccounts[j]; + allAccountsList.push(subAccount); + } + } + } + } + + return allAccountsList; + }); + + const allVisiblePlainAccounts = computed(() => { + const allVisibleAccounts = []; + + for (let i = 0; i < allAccounts.value.length; i++) { + const account = allAccounts.value[i]; + + if (account.hidden) { + continue; + } + + if (account.type === AccountType.SingleAccount.type) { + allVisibleAccounts.push(account); + } else if (account.type === AccountType.MultiSubAccounts.type) { + if (account.childrenAccounts) { + for (let j = 0; j < account.childrenAccounts.length; j++) { + const subAccount = account.childrenAccounts[j]; + allVisibleAccounts.push(subAccount); + } + } + } + } + + return allVisibleAccounts; + }); + + const allAvailableAccountsCount = computed(() => { + let allAccountCount = 0; + + for (const category in allCategorizedAccountsMap.value) { + if (!Object.prototype.hasOwnProperty.call(allCategorizedAccountsMap.value, category)) { + continue; + } + + allAccountCount += allCategorizedAccountsMap.value[category].accounts.length; + } + + return allAccountCount; + }); + + const allVisibleAccountsCount = computed(() => { + let shownAccountCount = 0; + + for (const category in allCategorizedAccountsMap.value) { + if (!Object.prototype.hasOwnProperty.call(allCategorizedAccountsMap.value, category)) { + continue; + } + + const accountList = allCategorizedAccountsMap.value[category].accounts; + + for (let i = 0; i < accountList.length; i++) { + if (!accountList[i].hidden) { + shownAccountCount++; + } + } + } + + return shownAccountCount; + }); + + function generateNewAccountModel(): Account { + return Account.createNewAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTime()); + } + + function generateNewSubAccountModel(parentAccount: Account): Account { + return parentAccount.createNewSubAccount(userStore.currentUserDefaultCurrency, getCurrentUnixTime()); + } + + function updateAccountListInvalidState(invalidState: boolean): void { + accountListStateInvalid.value = invalidState; + } + + function resetAccounts(): void { + allAccounts.value = []; + allAccountsMap.value = {}; + allCategorizedAccountsMap.value = {}; + accountListStateInvalid.value = true; + } + + function getFirstShowingIds(showHidden: boolean): AccountShowingIds { + const ret: AccountShowingIds = { + accounts: {}, + subAccounts: {} + }; + + for (const category in allCategorizedAccountsMap.value) { + if (!Object.prototype.hasOwnProperty.call(allCategorizedAccountsMap.value, category)) { + continue; + } + + if (!allCategorizedAccountsMap.value[category] || !allCategorizedAccountsMap.value[category].accounts) { + continue; + } + + const accounts = allCategorizedAccountsMap.value[category].accounts; + + for (let i = 0; i < accounts.length; i++) { + const account = accounts[i]; + + if (account.type === AccountType.MultiSubAccounts.type && account.childrenAccounts) { + for (let j = 0; j < account.childrenAccounts.length; j++) { + const subAccount = account.childrenAccounts[j]; + + if (showHidden || !subAccount.hidden) { + ret.subAccounts[account.id] = subAccount.id; + break; + } + } + } + + if (showHidden || !account.hidden) { + ret.accounts[category] = account.id; + break; + } + } + } + + return ret; + } + + function getLastShowingIds(showHidden: boolean): AccountShowingIds { + const ret: AccountShowingIds = { + accounts: {}, + subAccounts: {} + }; + + for (const category in allCategorizedAccountsMap.value) { + if (!Object.prototype.hasOwnProperty.call(allCategorizedAccountsMap.value, category)) { + continue; + } + + if (!allCategorizedAccountsMap.value[category] || !allCategorizedAccountsMap.value[category].accounts) { + continue; + } + + const accounts = allCategorizedAccountsMap.value[category].accounts; + + for (let i = accounts.length - 1; i >= 0; i--) { + const account = accounts[i]; + + if (account.type === AccountType.MultiSubAccounts.type && account.childrenAccounts) { + for (let j = account.childrenAccounts.length - 1; j >= 0; j--) { + const subAccount = account.childrenAccounts[j]; + + if (showHidden || !subAccount.hidden) { + ret.subAccounts[account.id] = subAccount.id; + break; + } + } + } + + if (showHidden || !account.hidden) { + ret.accounts[category] = account.id; + break; + } + } + } + + return ret; + } + + function getAccountStatementDate(accountId?: string): number | undefined | null { + if (!accountId) { + return null; + } + + const accountIds = accountId.split(','); + let mainAccount = null; + + for (let i = 0; i < accountIds.length; i++) { + const id = accountIds[i]; + let account = allAccountsMap.value[id]; + + if (!account) { + return null; + } + + if (account.parentId !== '0') { + account = allAccountsMap.value[account.parentId]; + } + + if (mainAccount !== null) { + if (mainAccount.id !== account.id) { + return null; + } else { + continue; + } + } + + mainAccount = account; + } + + if (!mainAccount) { + return null; + } + + if (mainAccount.category === AccountCategory.CreditCard.type) { + return mainAccount.creditCardStatementDate; + } + + return null; + } + + function getNetAssets(showAccountBalance: boolean): string { + if (!showAccountBalance) { + return '***'; + } + + const accountsBalance = getAllFilteredAccountsBalance(allCategorizedAccountsMap.value, () => 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.toString(); + } + } + + function getTotalAssets(showAccountBalance: boolean): string { + if (!showAccountBalance) { + return '***'; + } + + const accountsBalance = getAllFilteredAccountsBalance(allCategorizedAccountsMap.value, account => account.isAsset || false); + 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.toString(); + } + } + + function getTotalLiabilities(showAccountBalance: boolean): string { + if (!showAccountBalance) { + return '***'; + } + + const accountsBalance = getAllFilteredAccountsBalance(allCategorizedAccountsMap.value, account => account.isLiability || false); + 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.toString(); + } + } + + function getAccountCategoryTotalBalance(showAccountBalance: boolean, accountCategory: AccountCategory): string { + if (!showAccountBalance) { + return '***'; + } + + const accountsBalance = getAllFilteredAccountsBalance(allCategorizedAccountsMap.value, account => account.category === accountCategory.type); + 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.toString(); + } + } + + function getAccountBalance(showAccountBalance: boolean, account: Account): string | null { + if (account.type !== AccountType.SingleAccount.type) { + return null; + } + + if (showAccountBalance) { + if (account.isAsset) { + return account.balance.toString(); + } else if (account.isLiability) { + return (-account.balance).toString(); + } else { + return account.balance.toString(); + } + } else { + return '***'; + } + } + + function getAccountSubAccountBalance(showAccountBalance: boolean, showHidden: boolean, account: Account, subAccountId: string): AccountDisplayBalance | null { + if (account.type !== AccountType.MultiSubAccounts.type) { + return null; + } + + let resultCurrency = userStore.currentUserDefaultCurrency; + + if (!account.childrenAccounts || !account.childrenAccounts.length) { + return { + balance: showAccountBalance ? '0' : '***', + currency: resultCurrency + }; + } + + const allSubAccountCurrenciesMap: Record = {}; + const allSubAccountCurrencies: string[] = []; + let totalBalance = 0; + + for (let i = 0; i < account.childrenAccounts.length; i++) { + const subAccount = account.childrenAccounts[i]; + + if (!showHidden && subAccount.hidden) { + continue; + } + + if (!allSubAccountCurrenciesMap[subAccount.currency]) { + allSubAccountCurrenciesMap[subAccount.currency] = true; + allSubAccountCurrencies.push(subAccount.currency); + } + } + + if (allSubAccountCurrencies.length === 0) { + return { + balance: showAccountBalance ? '0' : '***', + currency: resultCurrency + }; + } + + if (allSubAccountCurrencies.length === 1) { + resultCurrency = allSubAccountCurrencies[0]; + } + + let hasUnCalculatedAmount = false; + + for (let i = 0; i < account.childrenAccounts.length; i++) { + const subAccount = account.childrenAccounts[i]; + + if (!showHidden && subAccount.hidden) { + continue; + } + + if (subAccountId) { + if (subAccountId === subAccount.id) { + return { + balance: showAccountBalance ? getAccountBalance(showAccountBalance, subAccount) as string : '***', + currency: subAccount.currency + }; + } + } + + if (subAccount.currency === resultCurrency) { + if (subAccount.isAsset) { + totalBalance += subAccount.balance; + } else if (subAccount.isLiability) { + totalBalance -= subAccount.balance; + } else { + totalBalance += subAccount.balance; + } + } else { + const balance = exchangeRatesStore.getExchangedAmount(subAccount.balance, subAccount.currency, resultCurrency); + + if (!isNumber(balance)) { + hasUnCalculatedAmount = true; + continue; + } + + if (subAccount.isAsset) { + totalBalance += Math.floor(balance); + } else if (subAccount.isLiability) { + totalBalance -= Math.floor(balance); + } else { + totalBalance += Math.floor(balance); + } + } + } + + if (subAccountId) { // not found specified id in sub accounts + return null; + } + + let displayTotalBalance = totalBalance.toString(); + + if (hasUnCalculatedAmount) { + displayTotalBalance += '+'; + } + + return { + balance: showAccountBalance ? displayTotalBalance : '***', + currency: resultCurrency + }; + } + + function hasAccount(accountCategory: AccountCategory, visibleOnly: boolean): boolean { + if (!allCategorizedAccountsMap.value[accountCategory.type] || + !allCategorizedAccountsMap.value[accountCategory.type].accounts || + !allCategorizedAccountsMap.value[accountCategory.type].accounts.length) { + return false; + } + + let shownCount = 0; + + for (let i = 0; i < allCategorizedAccountsMap.value[accountCategory.type].accounts.length; i++) { + const account = allCategorizedAccountsMap.value[accountCategory.type].accounts[i]; + + if (!visibleOnly || !account.hidden) { + shownCount++; + } + } + + return shownCount > 0; + } + + function hasVisibleSubAccount(showHidden: boolean, account: Account): boolean { + if (!account || account.type !== AccountType.MultiSubAccounts.type || !account.childrenAccounts) { + return false; + } + + for (let i = 0; i < account.childrenAccounts.length; i++) { + if (showHidden || !account.childrenAccounts[i].hidden) { + return true; + } + } + + return false; + } + + function loadAllAccounts({ force }: { force: boolean }): Promise { + if (!force && !accountListStateInvalid.value) { + return new Promise((resolve) => { + resolve(allAccounts.value); + }); + } + + 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 retrieve account list' }); + return; + } + + if (accountListStateInvalid.value) { + updateAccountListInvalidState(false); + } + + const accounts = Account.ofMany(data.result); + + if (force && data.result && isEquals(allAccounts.value, accounts)) { + reject({ message: 'Account list is up to date' }); + return; + } + + loadAccountList(accounts); + + resolve(accounts); + }).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 retrieve account list' }); + } else { + reject(error); + } + }); + }); + } + + function getAccount({ accountId }: { accountId: string }): Promise { + 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 retrieve account' }); + return; + } + + const account = Account.of(data.result); + + resolve(account); + }).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 retrieve account' }); + } else { + reject(error); + } + }); + }); + } + + function saveAccount({ account, subAccounts, isEdit, clientSessionId }: { account: Account, subAccounts: Account[], isEdit: boolean, clientSessionId: string }): Promise { + return new Promise((resolve, reject) => { + const oldAccount = isEdit ? allAccountsMap.value[account.id] : null; + let promise = null; + + if (!isEdit) { + promise = services.addAccount(account.toCreateRequest(clientSessionId, subAccounts)); + } else { + promise = services.modifyAccount(account.toModifyRequest(subAccounts)); + } + + promise.then(response => { + const data = response.data; + + if (!data || !data.success || !data.result) { + if (!isEdit) { + reject({ message: 'Unable to add account' }); + } else { + reject({ message: 'Unable to save account' }); + } + return; + } + + const newAccount = Account.of(data.result); + + if (!isEdit) { + addAccountToAccountList(newAccount); + } else { + if (oldAccount && oldAccount.category === newAccount.category) { + updateAccountToAccountList(newAccount); + } else { + updateAccountListInvalidState(true); + } + } + + resolve(newAccount); + }).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 (!isEdit) { + reject({ message: 'Unable to add account' }); + } else { + reject({ message: 'Unable to save account' }); + } + } else { + reject(error); + } + }); + }); + } + + function changeAccountDisplayOrder({ accountId, from, to, updateListOrder, updateGlobalListOrder }: { accountId: string, from: number, to: number, updateListOrder: boolean, updateGlobalListOrder: boolean }): Promise { + const account = allAccountsMap.value[accountId]; + + return new Promise((resolve, reject) => { + if (!account || + !allCategorizedAccountsMap.value[account.category] || + !allCategorizedAccountsMap.value[account.category].accounts || + !allCategorizedAccountsMap.value[account.category].accounts[to]) { + reject({ message: 'Unable to move account' }); + return; + } + + if (!accountListStateInvalid.value) { + updateAccountListInvalidState(true); + } + + updateAccountDisplayOrderInAccountList({ account, from, to, updateListOrder, updateGlobalListOrder }); + + resolve(); + }); + } + + function updateAccountDisplayOrders(): Promise { + const newDisplayOrders: AccountNewDisplayOrderRequest[] = []; + + for (const category in allCategorizedAccountsMap.value) { + if (!Object.prototype.hasOwnProperty.call(allCategorizedAccountsMap.value, category)) { + continue; + } + + const accountList = allCategorizedAccountsMap.value[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 (accountListStateInvalid.value) { + 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); + } + }); + }); + } + + function hideAccount({ account, hidden }: { account: Account, hidden: boolean }): Promise { + 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({ account, 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); + } + }); + }); + } + + function deleteAccount({ account, beforeResolve }: { account: Account, beforeResolve: BeforeResolveFunction }): Promise { + 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(account); + }); + } else { + removeAccountFromAccountList(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); + } + }); + }); + } + + return { + // states + allAccounts, + allAccountsMap, + allCategorizedAccountsMap, + accountListStateInvalid, + // computed states + allPlainAccounts, + allVisiblePlainAccounts, + allAvailableAccountsCount, + allVisibleAccountsCount, + // functions + generateNewAccountModel, + generateNewSubAccountModel, + updateAccountListInvalidState, + resetAccounts, + getFirstShowingIds, + getLastShowingIds, + getAccountStatementDate, + getNetAssets, + getTotalAssets, + getTotalLiabilities, + getAccountCategoryTotalBalance, + getAccountBalance, + getAccountSubAccountBalance, + hasAccount, + hasVisibleSubAccount, + loadAllAccounts, + getAccount, + saveAccount, + changeAccountDisplayOrder, + updateAccountDisplayOrders, + hideAccount, + deleteAccount + } +}); diff --git a/src/stores/index.js b/src/stores/index.js index 8e0186b4..a5b797a0 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -2,7 +2,7 @@ import { defineStore } from 'pinia'; import { useSettingsStore } from './setting.ts'; import { useUserStore } from './user.ts'; -import { useAccountsStore } from './account.js'; +import { useAccountsStore } from './account.ts'; import { useTransactionCategoriesStore } from './transactionCategory.ts'; import { useTransactionTagsStore } from './transactionTag.ts'; import { useTransactionTemplatesStore } from './transactionTemplate.js'; diff --git a/src/stores/statistics.js b/src/stores/statistics.js index 28a4ee7a..d175ba4b 100644 --- a/src/stores/statistics.js +++ b/src/stores/statistics.js @@ -2,7 +2,7 @@ import { defineStore } from 'pinia'; import { useSettingsStore } from './setting.ts'; import { useUserStore } from './user.ts'; -import { useAccountsStore } from './account.js'; +import { useAccountsStore } from './account.ts'; import { useTransactionCategoriesStore } from './transactionCategory.ts'; import { useExchangeRatesStore } from './exchangeRates.ts'; diff --git a/src/stores/transaction.js b/src/stores/transaction.js index 97a7ce84..fa7ffebf 100644 --- a/src/stores/transaction.js +++ b/src/stores/transaction.js @@ -2,7 +2,7 @@ import { defineStore } from 'pinia'; import { useSettingsStore } from './setting.ts'; import { useUserStore } from './user.ts'; -import { useAccountsStore } from './account.js'; +import { useAccountsStore } from './account.ts'; import { useTransactionCategoriesStore } from './transactionCategory.ts'; import { useOverviewStore } from './overview.ts'; import { useStatisticsStore } from './statistics.js'; diff --git a/src/views/desktop/HomePage.vue b/src/views/desktop/HomePage.vue index cea5b500..dbf7aab1 100644 --- a/src/views/desktop/HomePage.vue +++ b/src/views/desktop/HomePage.vue @@ -193,7 +193,7 @@ import MonthlyIncomeAndExpenseCard from './overview/cards/MonthlyIncomeAndExpens import { mapStores } from 'pinia'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from '@/stores/user.ts'; -import { useAccountsStore } from '@/stores/account.js'; +import { useAccountsStore } from '@/stores/account.ts'; import { useOverviewStore } from '@/stores/overview.ts'; import { DateRange } from '@/core/datetime.ts'; diff --git a/src/views/desktop/accounts/ListPage.vue b/src/views/desktop/accounts/ListPage.vue index 00ae1a1e..bd11e4e8 100644 --- a/src/views/desktop/accounts/ListPage.vue +++ b/src/views/desktop/accounts/ListPage.vue @@ -262,7 +262,7 @@ import { useDisplay } from 'vuetify'; import { mapStores } from 'pinia'; import { useSettingsStore } from '@/stores/setting.ts'; import { useUserStore } from '@/stores/user.ts'; -import { useAccountsStore } from '@/stores/account.js'; +import { useAccountsStore } from '@/stores/account.ts'; import { useExchangeRatesStore } from '@/stores/exchangeRates.ts'; import { AccountType, AccountCategory } from '@/core/account.ts'; diff --git a/src/views/desktop/accounts/list/dialogs/EditDialog.vue b/src/views/desktop/accounts/list/dialogs/EditDialog.vue index 7e8abea2..3a2ef86a 100644 --- a/src/views/desktop/accounts/list/dialogs/EditDialog.vue +++ b/src/views/desktop/accounts/list/dialogs/EditDialog.vue @@ -198,7 +198,7 @@