finnow/web-app/src/api/account.ts

228 lines
5.9 KiB
TypeScript

import { type RouteLocation } from 'vue-router'
import { ApiClient } from './base'
import type { Currency } from './data'
import type { Page, PageRequest } from './pagination'
import { getSelectedProfile } from './profile'
import type { Attachment } from './attachment'
export interface AccountType {
id: string
name: string
debitsPositive: boolean
emoji: string
}
export abstract class AccountTypes {
public static readonly CHECKING: AccountType = {
id: 'CHECKING',
name: 'Checking',
debitsPositive: true,
emoji: '💵',
}
public static readonly SAVINGS: AccountType = {
id: 'SAVINGS',
name: 'Savings',
debitsPositive: true,
emoji: '💰',
}
public static readonly CREDIT_CARD: AccountType = {
id: 'CREDIT_CARD',
name: 'Credit Card',
debitsPositive: false,
emoji: '💳',
}
public static readonly BROKERAGE: AccountType = {
id: 'BROKERAGE',
name: 'Brokerage',
debitsPositive: true,
emoji: '📈',
}
public static of(id: string): AccountType {
if (id === 'CHECKING') return AccountTypes.CHECKING
if (id === 'SAVINGS') return AccountTypes.SAVINGS
if (id === 'CREDIT_CARD') return AccountTypes.CREDIT_CARD
if (id === 'BROKERAGE') return AccountTypes.BROKERAGE
throw new Error('Unknown account type: ' + id)
}
}
export interface Account {
id: number
createdAt: string
archived: boolean
type: string
numberSuffix: string
name: string
currency: Currency
description: string
currentBalance: number | null
}
export interface SimpleAccountResponse {
id: number
name: string
type: string
numberSuffix: string
}
export interface AccountCreationPayload {
type: string
numberSuffix: string
name: string
currency: string
description: string
}
export enum AccountJournalEntryType {
CREDIT = 'CREDIT',
DEBIT = 'DEBIT',
}
export interface AccountJournalEntry {
id: number
timestamp: string
accountId: number
transactionId: number
amount: number
type: AccountJournalEntryType
currency: Currency
}
export enum AccountValueRecordType {
BALANCE = 'BALANCE',
}
export interface AccountValueRecord {
id: number
timestamp: string
accountId: number
type: AccountValueRecordType
value: number
currency: Currency
attachments: Attachment[]
}
export interface AccountValueRecordCreationPayload {
timestamp: string
type: AccountValueRecordType
value: number
}
// History:
export enum AccountHistoryItemType {
TEXT = 'TEXT',
PROPERTY_CHANGE = 'PROPERTY_CHANGE',
VALUE_RECORD = 'VALUE_RECORD',
JOURNAL_ENTRY = 'JOURNAL_ENTRY',
}
export function accountHistoryItemTypeToDisplayName(type: AccountHistoryItemType) {
if (type === AccountHistoryItemType.TEXT) return 'Text'
if (type === AccountHistoryItemType.PROPERTY_CHANGE) return 'Property Change'
if (type === AccountHistoryItemType.VALUE_RECORD) return 'Value Record'
if (type === AccountHistoryItemType.JOURNAL_ENTRY) return 'Journal Entry'
return 'Unknown'
}
export interface AccountHistoryItem {
timestamp: string
type: AccountHistoryItemType
}
export interface AccountHistoryValueRecordItem extends AccountHistoryItem {
valueRecordId: number
valueRecordType: AccountValueRecordType
value: number
currency: Currency
}
export interface AccountHistoryJournalEntryItem extends AccountHistoryItem {
journalEntryType: AccountJournalEntryType
amount: number
currency: Currency
transactionId: number
transactionDescription: string
}
export interface CurrencyBalance {
currency: Currency
balance: number
}
export class AccountApiClient extends ApiClient {
readonly path: string
readonly profileName: string
constructor(route: RouteLocation) {
super()
this.profileName = getSelectedProfile(route)
this.path = `/profiles/${getSelectedProfile(route)}/accounts`
}
getAccounts(): Promise<Account[]> {
return super.getJson(this.path)
}
getAccount(id: number): Promise<Account> {
return super.getJson(this.path + '/' + id)
}
createAccount(data: AccountCreationPayload): Promise<Account> {
return super.postJson(this.path, data)
}
updateAccount(id: number, data: AccountCreationPayload): Promise<Account> {
return super.putJson(this.path + '/' + id, data)
}
deleteAccount(id: number): Promise<void> {
return super.delete(this.path + '/' + id)
}
async getBalance(id: number, timestamp: Date): Promise<number | undefined> {
const result: { balance: number | null } = await super.getJson(
`${this.path}/${id}/balance?timestamp=${timestamp.toISOString()}`,
)
if (result.balance === null) return undefined
return result.balance
}
getHistory(id: number, pageRequest: PageRequest): Promise<Page<AccountHistoryItem>> {
return super.getJsonPage(`${this.path}/${id}/history`, pageRequest)
}
getTotalBalances(): Promise<CurrencyBalance[]> {
return super.getJson(`/profiles/${this.profileName}/account-balances`)
}
getValueRecords(accountId: number, pageRequest: PageRequest): Promise<Page<AccountValueRecord>> {
return super.getJsonPage(this.path + '/' + accountId + '/value-records', pageRequest)
}
getValueRecord(accountId: number, valueRecordId: number): Promise<AccountValueRecord> {
return super.getJson(this.path + '/' + accountId + '/value-records/' + valueRecordId)
}
createValueRecord(
accountId: number,
payload: AccountValueRecordCreationPayload,
attachments: File[],
): Promise<AccountValueRecord> {
const formData = new FormData()
formData.append('payload', JSON.stringify(payload))
for (const file of attachments) {
formData.append('file', file)
}
return super.postFormData(`${this.path}/${accountId}/value-records`, formData)
}
deleteValueRecord(accountId: number, valueRecordId: number): Promise<void> {
return super.delete(this.path + '/' + accountId + '/value-records/' + valueRecordId)
}
downloadAccountsExport(): Promise<void> {
return super.getFile(this.path + '/export')
}
}