mirror of
https://github.com/acedanger/finance.git
synced 2025-12-05 22:50:12 -08:00
transitioned from astro to react
This commit is contained in:
283
server/data/db.service.ts
Normal file
283
server/data/db.service.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
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;
|
||||
});
|
||||
},
|
||||
};
|
||||
13
server/data/prisma.ts
Normal file
13
server/data/prisma.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
// Prevent multiple instances of Prisma Client in development
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var prismaClient: PrismaClient | undefined;
|
||||
}
|
||||
|
||||
export const prisma = global.prismaClient || new PrismaClient();
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
global.prismaClient = prisma;
|
||||
}
|
||||
39
server/index.ts
Normal file
39
server/index.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import cors from 'cors';
|
||||
import { config } from 'dotenv';
|
||||
import express from 'express';
|
||||
|
||||
// Load environment variables
|
||||
config();
|
||||
|
||||
// Import API routes
|
||||
import accountsRouter from './routes/accounts.js';
|
||||
import transactionsRouter from './routes/transactions.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// API routes
|
||||
app.use('/api/accounts', accountsRouter);
|
||||
app.use('/api/transactions', transactionsRouter);
|
||||
|
||||
// Serve static files in production
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
app.use(express.static(join(__dirname, '../client')));
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(join(__dirname, '../client/index.html'));
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
98
server/routes/accounts.ts
Normal file
98
server/routes/accounts.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Router } from 'express';
|
||||
import { AccountStatus, AccountType, accountService } from '../data/db.service.js';
|
||||
import type { Account } from '../types.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// GET /api/accounts - Get all accounts
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Express handler types require any for req/res
|
||||
router.get('/', async (req: any, res: any) => {
|
||||
try {
|
||||
console.log('GET /api/accounts - Fetching all accounts');
|
||||
const accounts = await accountService.getAll();
|
||||
console.log('GET /api/accounts - Found accounts:', accounts?.length ?? 0);
|
||||
|
||||
res.json(accounts || []);
|
||||
} catch (error) {
|
||||
console.error('Error fetching accounts:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to fetch accounts',
|
||||
details: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/accounts - Create new account
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Express handler types require any for req/res
|
||||
router.post('/', async (req: any, res: any) => {
|
||||
try {
|
||||
const accountData = req.body as Omit<Account, 'id' | 'createdAt' | 'updatedAt'>;
|
||||
|
||||
// Validate required fields
|
||||
if (!accountData.name || !accountData.bankName || !accountData.accountNumber) {
|
||||
return res.status(400).json({
|
||||
error: 'Missing required fields: name, bankName, and accountNumber are required',
|
||||
});
|
||||
}
|
||||
|
||||
// Set default values and ensure proper type casting
|
||||
const data = {
|
||||
...accountData,
|
||||
balance: accountData.balance ? Number(accountData.balance) : 0,
|
||||
type: (accountData.type as AccountType) || AccountType.CHECKING,
|
||||
status: (accountData.status as AccountStatus) || AccountStatus.ACTIVE,
|
||||
notes: accountData.notes || undefined,
|
||||
};
|
||||
|
||||
// Create the account
|
||||
const newAccount = await accountService.create(data);
|
||||
|
||||
res.status(201).json(newAccount);
|
||||
} catch (error) {
|
||||
console.error('Error creating account:', error);
|
||||
res.status(500).json({ error: 'Failed to create account' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/accounts/:id - Get single account
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Express handler types require any for req/res
|
||||
router.get('/:id', async (req: any, res: any) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id) {
|
||||
return res.status(400).json({ error: 'Account ID is required' });
|
||||
}
|
||||
|
||||
const account = await accountService.getById(id);
|
||||
|
||||
if (!account) {
|
||||
return res.status(404).json({ error: 'Account not found' });
|
||||
}
|
||||
|
||||
res.json(account);
|
||||
} catch (error) {
|
||||
console.error('Error fetching account:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch account' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/accounts/:id/transactions - Get transactions for account
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Express handler types require any for req/res
|
||||
router.get('/:id/transactions', async (req: any, res: any) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id) {
|
||||
return res.status(400).json({ error: 'Account ID is required' });
|
||||
}
|
||||
|
||||
const transactions = await accountService.getTransactions(id);
|
||||
res.json(transactions || []);
|
||||
} catch (error) {
|
||||
console.error('Error fetching account transactions:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch account transactions' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
189
server/routes/transactions.ts
Normal file
189
server/routes/transactions.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { Router } from 'express';
|
||||
import { accountService, transactionService } from '../data/db.service.js';
|
||||
import type { Transaction, TransactionStatus, TransactionType } from '../types.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Helper function to transform update data
|
||||
function transformUpdateData(updateData: Partial<Omit<Transaction, 'id'>>) {
|
||||
const typedUpdateData: {
|
||||
accountId?: string;
|
||||
date?: Date;
|
||||
description?: string;
|
||||
amount?: number;
|
||||
category?: string;
|
||||
status?: TransactionStatus;
|
||||
type?: TransactionType;
|
||||
notes?: string;
|
||||
tags?: string;
|
||||
} = {};
|
||||
|
||||
// Convert string date to Date object if provided
|
||||
if (updateData.date) {
|
||||
typedUpdateData.date =
|
||||
typeof updateData.date === 'string' ? new Date(updateData.date) : updateData.date;
|
||||
}
|
||||
|
||||
// Convert amount to number if provided
|
||||
if (updateData.amount !== undefined) {
|
||||
typedUpdateData.amount = Number(updateData.amount);
|
||||
}
|
||||
|
||||
// Copy other fields
|
||||
if (updateData.accountId) typedUpdateData.accountId = updateData.accountId;
|
||||
if (updateData.description) typedUpdateData.description = updateData.description;
|
||||
if (updateData.category !== undefined)
|
||||
typedUpdateData.category = updateData.category || undefined;
|
||||
if (updateData.status) typedUpdateData.status = updateData.status as TransactionStatus;
|
||||
if (updateData.type) typedUpdateData.type = updateData.type as TransactionType;
|
||||
if (updateData.notes !== undefined) typedUpdateData.notes = updateData.notes || undefined;
|
||||
if (updateData.tags !== undefined) typedUpdateData.tags = updateData.tags || undefined;
|
||||
|
||||
return typedUpdateData;
|
||||
}
|
||||
|
||||
// POST /api/transactions - Create new transaction
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Express handler types require any for req/res
|
||||
router.post('/', async (req: any, res: any) => {
|
||||
try {
|
||||
const transaction = req.body as Omit<Transaction, 'id'>;
|
||||
|
||||
// Validate required fields
|
||||
if (
|
||||
!transaction.accountId ||
|
||||
!transaction.date ||
|
||||
!transaction.description ||
|
||||
transaction.amount === undefined
|
||||
) {
|
||||
return res.status(400).json({ error: 'Missing required fields' });
|
||||
}
|
||||
|
||||
// Validate account exists
|
||||
const account = await accountService.getById(transaction.accountId);
|
||||
if (!account) {
|
||||
return res.status(404).json({ error: 'Account not found' });
|
||||
}
|
||||
|
||||
// Convert string date to Date object if needed
|
||||
const transactionDate =
|
||||
typeof transaction.date === 'string' ? new Date(transaction.date) : transaction.date;
|
||||
|
||||
// Create new transaction with database service
|
||||
const newTransaction = await transactionService.create({
|
||||
accountId: transaction.accountId,
|
||||
date: transactionDate,
|
||||
description: transaction.description,
|
||||
amount: Number(transaction.amount),
|
||||
category: transaction.category || undefined,
|
||||
status: transaction.status as TransactionStatus,
|
||||
type: transaction.type as TransactionType,
|
||||
notes: transaction.notes || undefined,
|
||||
tags: transaction.tags || undefined,
|
||||
});
|
||||
|
||||
// Convert Decimal to number for response
|
||||
const response = {
|
||||
...newTransaction,
|
||||
amount: Number(newTransaction.amount),
|
||||
};
|
||||
|
||||
res.status(201).json(response);
|
||||
} catch (error) {
|
||||
console.error('Error creating transaction:', error);
|
||||
res.status(500).json({ error: 'Failed to create transaction' });
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/transactions/:id - Get single transaction
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Express handler types require any for req/res
|
||||
router.get('/:id', async (req: any, res: any) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id) {
|
||||
return res.status(400).json({ error: 'Transaction ID is required' });
|
||||
}
|
||||
|
||||
const transaction = await transactionService.getById(id);
|
||||
|
||||
if (!transaction) {
|
||||
return res.status(404).json({ error: 'Transaction not found' });
|
||||
}
|
||||
|
||||
// Convert Decimal to number for response
|
||||
const response = {
|
||||
...transaction,
|
||||
amount: Number(transaction.amount),
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
console.error('Error fetching transaction:', error);
|
||||
res.status(500).json({ error: 'Failed to fetch transaction' });
|
||||
}
|
||||
});
|
||||
|
||||
// PUT /api/transactions/:id - Update transaction
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Express handler types require any for req/res
|
||||
router.put('/:id', async (req: any, res: any) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id) {
|
||||
return res.status(400).json({ error: 'Transaction ID is required' });
|
||||
}
|
||||
|
||||
// Check if transaction exists
|
||||
const existingTransaction = await transactionService.getById(id);
|
||||
if (!existingTransaction) {
|
||||
return res.status(404).json({ error: 'Transaction not found' });
|
||||
}
|
||||
|
||||
const updateData = req.body as Partial<Omit<Transaction, 'id'>>;
|
||||
const typedUpdateData = transformUpdateData(updateData);
|
||||
|
||||
const updatedTransaction = await transactionService.update(id, typedUpdateData);
|
||||
|
||||
if (!updatedTransaction) {
|
||||
return res.status(404).json({ error: 'Transaction not found or could not be updated' });
|
||||
}
|
||||
|
||||
// Convert Decimal to number for response
|
||||
const response = {
|
||||
...updatedTransaction,
|
||||
amount: Number(updatedTransaction.amount),
|
||||
};
|
||||
|
||||
res.json(response);
|
||||
} catch (error) {
|
||||
console.error('Error updating transaction:', error);
|
||||
res.status(500).json({ error: 'Failed to update transaction' });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/transactions/:id - Delete transaction
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Express handler types require any for req/res
|
||||
router.delete('/:id', async (req: any, res: any) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!id) {
|
||||
return res.status(400).json({ error: 'Transaction ID is required' });
|
||||
}
|
||||
|
||||
// Check if transaction exists
|
||||
const existingTransaction = await transactionService.getById(id);
|
||||
if (!existingTransaction) {
|
||||
return res.status(404).json({ error: 'Transaction not found' });
|
||||
}
|
||||
|
||||
await transactionService.delete(id);
|
||||
|
||||
res.status(204).send();
|
||||
} catch (error) {
|
||||
console.error('Error deleting transaction:', error);
|
||||
res.status(500).json({ error: 'Failed to delete transaction' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
30
server/tsconfig.json
Normal file
30
server/tsconfig.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "../dist/server",
|
||||
"rootDir": ".",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"include": [
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"../dist",
|
||||
"../src"
|
||||
]
|
||||
}
|
||||
56
server/types.ts
Normal file
56
server/types.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { Decimal } from '@prisma/client/runtime/library';
|
||||
|
||||
export interface Account {
|
||||
id: string;
|
||||
bankName: string;
|
||||
accountNumber: string; // Last 6 digits
|
||||
name: string; // Friendly name
|
||||
type?: string; // CHECKING, SAVINGS, etc.
|
||||
status?: string; // ACTIVE, CLOSED
|
||||
currency?: string; // Default: USD
|
||||
balance: number | Decimal; // Current balance - can be Prisma Decimal or number
|
||||
notes?: string | null; // Optional notes - accepts null for Prisma compatibility
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
id: string;
|
||||
accountId: string;
|
||||
date: string | Date; // ISO date string or Date object
|
||||
description: string;
|
||||
amount: number | Decimal; // Amount - can be Prisma Decimal or number
|
||||
category?: string | null; // Optional category - accepts null for Prisma compatibility
|
||||
status?: string; // PENDING, CLEARED
|
||||
type?: string; // DEPOSIT, WITHDRAWAL, TRANSFER
|
||||
notes?: string | null; // Optional notes - accepts null for Prisma compatibility
|
||||
tags?: string | null; // Optional comma-separated tags - accepts null for Prisma compatibility
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
// Type definitions for Transaction status and type enums to match db.service.ts
|
||||
export enum TransactionStatus {
|
||||
PENDING = 'PENDING',
|
||||
CLEARED = 'CLEARED',
|
||||
}
|
||||
|
||||
export enum TransactionType {
|
||||
DEPOSIT = 'DEPOSIT',
|
||||
WITHDRAWAL = 'WITHDRAWAL',
|
||||
TRANSFER = 'TRANSFER',
|
||||
UNSPECIFIED = 'UNSPECIFIED',
|
||||
}
|
||||
|
||||
export enum AccountType {
|
||||
CHECKING = 'CHECKING',
|
||||
SAVINGS = 'SAVINGS',
|
||||
CREDIT_CARD = 'CREDIT_CARD',
|
||||
INVESTMENT = 'INVESTMENT',
|
||||
OTHER = 'OTHER',
|
||||
}
|
||||
|
||||
export enum AccountStatus {
|
||||
ACTIVE = 'ACTIVE',
|
||||
CLOSED = 'CLOSED',
|
||||
}
|
||||
Reference in New Issue
Block a user