Files
finance/server/data/db.service.ts
2025-06-04 21:03:32 -04:00

284 lines
6.5 KiB
TypeScript

import type { Account, Transaction } from '../types.js';
import { prisma } from './prisma';
// Define the enums ourselves since Prisma isn't exporting them
export enum AccountType {
CHECKING = 'CHECKING',
SAVINGS = 'SAVINGS',
CREDIT_CARD = 'CREDIT_CARD',
INVESTMENT = 'INVESTMENT',
OTHER = 'OTHER',
}
export enum AccountStatus {
ACTIVE = 'ACTIVE',
CLOSED = 'CLOSED',
}
export enum TransactionStatus {
PENDING = 'PENDING',
CLEARED = 'CLEARED',
}
export enum TransactionType {
DEPOSIT = 'DEPOSIT',
WITHDRAWAL = 'WITHDRAWAL',
TRANSFER = 'TRANSFER',
UNSPECIFIED = 'UNSPECIFIED',
}
// Account services
export const accountService = {
/**
* Get all accounts
*/
async getAll(): Promise<Account[]> {
return prisma.account.findMany({
orderBy: { name: 'asc' },
}) as Promise<Account[]>;
},
/**
* Get account by ID
*/
async getById(id: string): Promise<Account | null> {
return prisma.account.findUnique({
where: { id },
}) as Promise<Account | null>;
},
/**
* Create a new account
*/
async create(data: {
bankName: string;
accountNumber: string;
name: string;
type?: AccountType;
status?: AccountStatus;
currency?: string;
balance?: number;
notes?: string;
}): Promise<Account> {
return prisma.account.create({
data,
}) as Promise<Account>;
},
/**
* Update an account
*/
async update(
id: string,
data: {
bankName?: string;
accountNumber?: string;
name?: string;
type?: AccountType;
status?: AccountStatus;
currency?: string;
balance?: number;
notes?: string;
},
): Promise<Account | null> {
return prisma.account.update({
where: { id },
data,
}) as Promise<Account>;
},
/**
* Delete an account
*/
async delete(id: string): Promise<Account | null> {
return prisma.account.delete({
where: { id },
}) as Promise<Account>;
},
/**
* Update account balance
*/
async updateBalance(id: string, amount: number): Promise<Account | null> {
const account = await prisma.account.findUnique({
where: { id },
});
if (!account) return null;
return prisma.account.update({
where: { id },
data: {
balance: {
increment: amount,
},
},
}) as Promise<Account>;
},
/**
* Get transactions for an account
*/
async getTransactions(accountId: string): Promise<Transaction[]> {
return prisma.transaction.findMany({
where: { accountId },
orderBy: { date: 'desc' },
}) as Promise<Transaction[]>;
},
};
// Transaction services
export const transactionService = {
/**
* Get all transactions
*/
async getAll(): Promise<Transaction[]> {
return prisma.transaction.findMany({
orderBy: { date: 'desc' },
}) as Promise<Transaction[]>;
},
/**
* Get transactions by account ID
*/
async getByAccountId(accountId: string): Promise<Transaction[]> {
return prisma.transaction.findMany({
where: { accountId },
orderBy: { date: 'desc' },
}) as Promise<Transaction[]>;
},
/**
* Get transaction by ID
*/
async getById(id: string): Promise<Transaction | null> {
return prisma.transaction.findUnique({
where: { id },
}) as Promise<Transaction | null>;
},
/**
* Create a new transaction and update account balance
*/
async create(data: {
accountId: string;
date: Date;
description: string;
amount: number;
category?: string;
status?: TransactionStatus;
type?: TransactionType;
notes?: string;
tags?: string;
}): Promise<Transaction> {
// Use a transaction to ensure data consistency
return prisma.$transaction<Transaction>(async (tx) => {
// Create the transaction
const transaction = await tx.transaction.create({
data,
});
// Update the account balance
await tx.account.update({
where: { id: data.accountId },
data: {
balance: {
increment: data.amount,
},
},
});
return transaction as Transaction;
});
},
/**
* Update a transaction and adjust account balance
*/
async update(
id: string,
data: {
accountId?: string;
date?: Date;
description?: string;
amount?: number;
category?: string;
status?: TransactionStatus;
type?: TransactionType;
notes?: string;
tags?: string;
},
): Promise<Transaction | null> {
// If amount is changing, we need to adjust the account balance
if (typeof data.amount !== 'undefined') {
return prisma.$transaction<Transaction | null>(async (tx) => {
// Get the current transaction to calculate difference
const currentTxn = await tx.transaction.findUnique({
where: { id },
});
if (!currentTxn) return null;
// Amount is guaranteed to be defined at this point since we checked above
const amount = data.amount as number; // Use type assertion instead of non-null assertion
const amountDifference = amount - Number(currentTxn.amount);
// Update transaction
const updatedTxn = await tx.transaction.update({
where: { id },
data,
});
// Update account balance
await tx.account.update({
where: { id: data.accountId || currentTxn.accountId },
data: {
balance: {
increment: amountDifference,
},
},
});
return updatedTxn as Transaction;
});
}
// If amount isn't changing, just update the transaction
return prisma.transaction.update({
where: { id },
data,
}) as Promise<Transaction>;
},
/**
* Delete a transaction and adjust account balance
*/
async delete(id: string): Promise<Transaction | null> {
return prisma.$transaction<Transaction | null>(async (tx) => {
// Get transaction before deleting
const transaction = await tx.transaction.findUnique({
where: { id },
});
if (!transaction) return null;
// Delete the transaction
const deletedTxn = await tx.transaction.delete({
where: { id },
});
// Adjust the account balance (reverse the transaction amount)
await tx.account.update({
where: { id: transaction.accountId },
data: {
balance: {
decrement: Number(transaction.amount),
},
},
});
return deletedTxn as Transaction;
});
},
};