import type { HiddenAmount, NumberWithSuffix } from '@/core/numeral.ts'; import type { ColorValue } from '@/core/color.ts'; import { AccountType, AccountCategory } from '@/core/account.ts'; import { PARENT_ACCOUNT_CURRENCY_PLACEHOLDER } from '@/consts/currency.ts'; import { DEFAULT_ACCOUNT_ICON_ID } from '@/consts/icon.ts'; import { DEFAULT_ACCOUNT_COLOR } from '@/consts/color.ts'; export class Account implements AccountInfoResponse { public id: string; public name: string; public parentId: string; public category: number; public type: number; public icon: string; public color: ColorValue; public currency: string; public balance: number; public balanceTime?: number; public comment: string; public creditCardStatementDate?: number; public displayOrder: number; public visible: boolean; public subAccounts?: Account[]; private readonly _isAsset?: boolean; private readonly _isLiability?: boolean; protected constructor(id: string, name: string, parentId: string, category: number, type: number, icon: string, color: string, currency: string, balance: number, comment: string, displayOrder: number, visible: boolean, balanceTime?: number, creditCardStatementDate?: number, isAsset?: boolean, isLiability?: boolean, subAccounts?: Account[]) { this.id = id; this.name = name; this.parentId = parentId; this.category = category; this.type = type; this.icon = icon; this.color = color; this.currency = currency; this.balance = balance; this.balanceTime = balanceTime; this.comment = comment; this.displayOrder = displayOrder; this.visible = visible; this.creditCardStatementDate = creditCardStatementDate; this._isAsset = isAsset; this._isLiability = isLiability; if (typeof(subAccounts) !== 'undefined') { this.subAccounts = subAccounts; } else { this.subAccounts = undefined; } } public get isAsset(): boolean { if (typeof(this._isAsset) !== 'undefined') { return this._isAsset; } const accountCategory = AccountCategory.valueOf(this.category); if (accountCategory) { return accountCategory.isAsset; } return false; } public get isLiability(): boolean { if (typeof(this._isLiability) !== 'undefined') { return this._isLiability; } const accountCategory = AccountCategory.valueOf(this.category); if (accountCategory) { return accountCategory.isLiability; } return false; } public get hidden(): boolean { return !this.visible; } public equals(other: Account): boolean { const isEqual = this.id === other.id && this.name === other.name && this.parentId === other.parentId && this.category === other.category && this.type === other.type && this.icon === other.icon && this.color === other.color && this.currency === other.currency && this.balance === other.balance && this.balanceTime === other.balanceTime && this.comment === other.comment && this.displayOrder === other.displayOrder && this.visible === other.visible && this.creditCardStatementDate === other.creditCardStatementDate; if (!isEqual) { return false; } if (this.subAccounts && other.subAccounts) { if (this.subAccounts.length !== other.subAccounts.length) { return false; } for (let i = 0; i < this.subAccounts.length; i++) { if (!(this.subAccounts[i] as Account).equals(other.subAccounts[i] as Account)) { return false; } } } else if ((this.subAccounts && this.subAccounts.length) || (other.subAccounts && other.subAccounts.length)) { return false; } return true; } public fillFrom(other: Account): void { this.id = other.id; this.category = other.category; this.type = other.type; this.name = other.name; this.icon = other.icon; this.color = other.color; this.currency = other.currency; this.balance = other.balance; this.balanceTime = other.balanceTime; this.comment = other.comment; this.creditCardStatementDate = other.creditCardStatementDate; this.visible = other.visible; } public setSuitableIcon(oldCategory: number, newCategory: number): void { const allCategories = AccountCategory.values(); for (const category of allCategories) { if (category.type === oldCategory) { if (this.icon !== category.defaultAccountIconId) { return; } else { break; } } } for (const category of allCategories) { if (category.type === newCategory) { this.icon = category.defaultAccountIconId; } } } public toCreateRequest(clientSessionId: string, subAccounts?: Account[], parentAccount?: Account): AccountCreateRequest { let subAccountCreateRequests: AccountCreateRequest[] | undefined = undefined; if (this.type === AccountType.MultiSubAccounts.type) { subAccountCreateRequests = []; if (!subAccounts) { subAccounts = this.subAccounts; } if (subAccounts) { for (const subAccount of subAccounts) { subAccountCreateRequests.push(subAccount.toCreateRequest(clientSessionId, undefined, this)); } } } return { name: this.name, category: parentAccount ? parentAccount.category : this.category, type: parentAccount ? AccountType.SingleAccount.type : this.type, icon: this.icon, color: this.color, currency: parentAccount || this.type === AccountType.SingleAccount.type ? this.currency : PARENT_ACCOUNT_CURRENCY_PLACEHOLDER, balance: parentAccount || this.type === AccountType.SingleAccount.type ? this.balance : 0, balanceTime: (parentAccount || this.type === AccountType.SingleAccount.type) && this.balanceTime ? this.balanceTime : 0, comment: this.comment, creditCardStatementDate: !parentAccount && this.category === AccountCategory.CreditCard.type ? this.creditCardStatementDate : undefined, subAccounts: !parentAccount ? subAccountCreateRequests : undefined, clientSessionId: !parentAccount ? clientSessionId : undefined }; } public toModifyRequest(clientSessionId: string, subAccounts?: Account[], parentAccount?: Account): AccountModifyRequest { let subAccountModifyRequests: AccountModifyRequest[] | undefined = undefined; if (this.type === AccountType.MultiSubAccounts.type) { subAccountModifyRequests = []; if (!subAccounts) { subAccounts = this.subAccounts; } if (subAccounts) { for (const subAccount of subAccounts) { subAccountModifyRequests.push(subAccount.toModifyRequest(clientSessionId, undefined, this)); } } } return { id: this.id || '0', name: this.name, category: parentAccount ? parentAccount.category : this.category, icon: this.icon, color: this.color, currency: parentAccount && (!this.id || this.id === '0') ? this.currency : undefined, balance: parentAccount && (!this.id || this.id === '0') ? this.balance : undefined, balanceTime: parentAccount && (!this.id || this.id === '0') ? this.balanceTime : undefined, comment: this.comment, creditCardStatementDate: !parentAccount && this.category === AccountCategory.CreditCard.type ? this.creditCardStatementDate : undefined, hidden: !this.visible, subAccounts: !parentAccount ? subAccountModifyRequests : undefined, clientSessionId: !parentAccount ? clientSessionId : undefined }; } public getAccountOrSubAccountId(subAccountId?: string): string | null { if (this.type === AccountType.SingleAccount.type) { return this.id; } else if (this.type === AccountType.MultiSubAccounts.type && !subAccountId) { return this.id; } else if (this.type === AccountType.MultiSubAccounts.type && subAccountId) { if (!this.subAccounts || !this.subAccounts.length) { return null; } for (const subAccount of this.subAccounts) { if (subAccountId && subAccountId === subAccount.id) { return subAccount.id; } } return null; } else { return null; } } public isAccountOrSubAccountHidden(subAccountId?: string): boolean { if (this.type === AccountType.SingleAccount.type) { return this.hidden; } else if (this.type === AccountType.MultiSubAccounts.type && !subAccountId) { return this.hidden; } else if (this.type === AccountType.MultiSubAccounts.type && subAccountId) { if (!this.subAccounts || !this.subAccounts.length) { return false; } for (const subAccount of this.subAccounts) { if (subAccountId && subAccountId === subAccount.id) { return subAccount.hidden; } } return false; } else { return false; } } public getAccountOrSubAccountComment(subAccountId?: string): string | null { if (this.type === AccountType.SingleAccount.type) { return this.comment; } else if (this.type === AccountType.MultiSubAccounts.type && !subAccountId) { return this.comment; } else if (this.type === AccountType.MultiSubAccounts.type && subAccountId) { if (!this.subAccounts || !this.subAccounts.length) { return null; } for (const subAccount of this.subAccounts) { if (subAccountId && subAccountId === subAccount.id) { return subAccount.comment; } } return null; } else { return null; } } public getAccountOrSubAccount(subAccountId?: string): Account | null { if (this.type === AccountType.SingleAccount.type) { return this; } else if (this.type === AccountType.MultiSubAccounts.type && !subAccountId) { return this; } else if (this.type === AccountType.MultiSubAccounts.type && subAccountId) { if (!this.subAccounts || !this.subAccounts.length) { return null; } for (const subAccount of this.subAccounts) { if (subAccountId && subAccountId === subAccount.id) { return subAccount; } } return null; } else { return null; } } public getSubAccount(subAccountId?: string): Account | null { if (!this.subAccounts || !this.subAccounts.length) { return null; } for (const subAccount of this.subAccounts) { if (subAccountId && subAccountId === subAccount.id) { return subAccount; } } return null; } public getSubAccountCurrencies(showHidden: boolean, subAccountId?: string): string[] { if (!this.subAccounts || !this.subAccounts.length) { return []; } const subAccountCurrenciesMap: Record = {}; const subAccountCurrencies: string[] = []; for (const subAccount of this.subAccounts) { if (!showHidden && subAccount.hidden) { continue; } if (subAccountId && subAccountId === subAccount.id) { return [subAccount.currency]; } else { if (!subAccountCurrenciesMap[subAccount.currency]) { subAccountCurrenciesMap[subAccount.currency] = true; subAccountCurrencies.push(subAccount.currency); } } } return subAccountCurrencies; } public cloneSelf(): Account { return new Account( this.id, this.name, this.parentId, this.category, this.type, this.icon, this.color, this.currency, this.balance, this.comment, this.displayOrder, this.visible, this.balanceTime, this.creditCardStatementDate, this.isAsset, this.isLiability ); } public clone(): Account { return new Account( this.id, this.name, this.parentId, this.category, this.type, this.icon, this.color, this.currency, this.balance, this.comment, this.displayOrder, this.visible, this.balanceTime, this.creditCardStatementDate, this.isAsset, this.isLiability, typeof(this.subAccounts) !== 'undefined' ? Account.cloneAccounts(this.subAccounts) : undefined); } public createNewSubAccount(currency: string, balanceTime: number): Account { return new Account( '', // id '', // name '', // parentId 0, // category 0, // type this.icon, // icon this.color, // color currency, // currency 0, // balance '', // comment 0, // displayOrder true, // visible balanceTime, // balanceTime 0 // creditCardStatementDate ); } public static createNewAccount(currency: string, balanceTime: number): Account { return new Account( '', // id '', // name '', // parentId AccountCategory.Cash.type, // category AccountType.SingleAccount.type, // type DEFAULT_ACCOUNT_ICON_ID, // icon DEFAULT_ACCOUNT_COLOR, // color currency, // currency 0, // balance '', // comment 0, // displayOrder true, // visible balanceTime, // balanceTime 0 // creditCardStatementDate ); } public static of(accountResponse: AccountInfoResponse): Account { return new Account( accountResponse.id, accountResponse.name, accountResponse.parentId, accountResponse.category, accountResponse.type, accountResponse.icon, accountResponse.color, accountResponse.currency, accountResponse.balance, accountResponse.comment, accountResponse.displayOrder, !accountResponse.hidden, undefined, accountResponse.creditCardStatementDate, accountResponse.isAsset, accountResponse.isLiability, accountResponse.subAccounts ? Account.ofMulti(accountResponse.subAccounts) : undefined ); } public static ofMulti(accountResponses: AccountInfoResponse[]): Account[] { const accounts: Account[] = []; for (const accountResponse of accountResponses) { accounts.push(Account.of(accountResponse)); } return accounts; } public static findAccountNameById(accounts: Account[], accountId: string, defaultName?: string): string | undefined { for (const account of accounts) { if (account.id === accountId) { return account.name; } } return defaultName; } public static cloneAccounts(accounts: Account[]): Account[] { const clonedAccounts: Account[] = []; for (const account of accounts) { clonedAccounts.push(account.clone()); } return clonedAccounts; } public static sortAccounts(accounts: Account[], allAccountsMap?: Record): Account[] { if (!accounts || !accounts.length) { return accounts; } return accounts.sort(function (account1, account2) { if (account1.category !== account2.category) { const account1Category = AccountCategory.valueOf(account1.category); const account2Category = AccountCategory.valueOf(account2.category); if (!account1Category) { return 1; } if (!account2Category) { return -1; } return account1Category.displayOrder - account2Category.displayOrder; } if (account1.parentId === account2.parentId) { return account1.displayOrder - account2.displayOrder; } if (account1.id === account2.parentId) { return -1; } else if (account2.id === account1.parentId) { return 1; } let account1DisplayOrder: number | null = account1.displayOrder; let account2DisplayOrder: number | null = account2.displayOrder; if (account1.parentId && account1.parentId !== '0') { if (allAccountsMap && allAccountsMap[account1.parentId]) { account1DisplayOrder = (allAccountsMap[account1.parentId] as Account).displayOrder; } else { account1DisplayOrder = null; } } if (account2.parentId && account2.parentId !== '0') { if (allAccountsMap && allAccountsMap[account2.parentId]) { account2DisplayOrder = (allAccountsMap[account2.parentId] as Account).displayOrder; } else { account2DisplayOrder = null; } } if (account1DisplayOrder !== null && account2DisplayOrder !== null) { return account1DisplayOrder - account2DisplayOrder; } else { return account1.id.localeCompare(account2.id); } }); } } export class AccountWithDisplayBalance extends Account { public displayBalance: string; private constructor(account: Account, displayBalance: string) { super( account.id, account.name, account.parentId, account.category, account.type, account.icon, account.color, account.currency, account.balance, account.comment, account.displayOrder, account.visible, account.balanceTime, account.creditCardStatementDate, account.isAsset, account.isLiability, account.subAccounts ); this.displayBalance = displayBalance; } public static fromAccount(account: Account, displayBalance: string): AccountWithDisplayBalance { return new AccountWithDisplayBalance(account, displayBalance); } } export interface AccountCreateRequest { readonly name: string; readonly category: number; readonly type: number; readonly icon: string; readonly color: string; readonly currency: string; readonly balance: number; readonly balanceTime: number; readonly comment: string; readonly creditCardStatementDate?: number; readonly subAccounts?: AccountCreateRequest[]; readonly clientSessionId?: string; } export interface AccountModifyRequest { readonly id: string; readonly name: string; readonly category: number; readonly icon: string; readonly color: string; readonly currency?: string; readonly balance?: number; readonly balanceTime?: number; readonly comment: string; readonly creditCardStatementDate?: number; readonly hidden: boolean; readonly subAccounts?: AccountModifyRequest[]; readonly clientSessionId?: string; } export interface AccountInfoResponse { readonly id: string; readonly name: string; readonly parentId: string; readonly category: number; readonly type: number; readonly icon: string; readonly color: string; readonly currency: string; readonly balance: number; readonly comment: string; readonly creditCardStatementDate?: number; readonly displayOrder: number; readonly isAsset?: boolean; readonly isLiability?: boolean; readonly hidden: boolean; readonly subAccounts?: AccountInfoResponse[]; } export interface AccountHideRequest { readonly id: string; readonly hidden: boolean; } export interface AccountMoveRequest { readonly newDisplayOrders: AccountNewDisplayOrderRequest[]; } export interface AccountNewDisplayOrderRequest { readonly id: string; readonly displayOrder: number; } export interface AccountDeleteRequest { readonly id: string; } export interface AccountBalance { readonly balance: number; readonly isAsset: boolean; readonly isLiability: boolean; readonly currency: string; } export interface AccountDisplayBalance { readonly balance: number | HiddenAmount | NumberWithSuffix; readonly currency: string; } export interface CategorizedAccount { readonly category: number; readonly name: string; readonly icon: string; readonly accounts: Account[]; } export class CategorizedAccountWithDisplayBalance { public category: number; public name: string; public icon: string; public accounts: AccountWithDisplayBalance[]; public displayBalance: string; private constructor(category: number, name: string, icon: string, accounts: AccountWithDisplayBalance[], displayBalance: string) { this.category = category; this.name = name; this.icon = icon; this.accounts = accounts; this.displayBalance = displayBalance; } public static of(categorizedAccount: CategorizedAccount, accounts: AccountWithDisplayBalance[], displayBalance: string): CategorizedAccountWithDisplayBalance { return new CategorizedAccountWithDisplayBalance(categorizedAccount.category, categorizedAccount.name, categorizedAccount.icon, accounts, displayBalance); } } export interface AccountShowingIds { readonly accounts: Record; readonly subAccounts: Record; }