import {DebtAccount} from '@prisma/client'; import {DebtAccountRepository} from '../repositories/DebtAccountRepository'; import {DebtCategoryRepository} from '../repositories/DebtCategoryRepository'; import {NotFoundError, ValidationError, ForbiddenError} from '../utils/errors'; export interface CreateDebtAccountDTO { categoryId: string; name: string; creditor: string; accountNumber?: string; originalBalance: number; currentBalance: number; interestRate?: number; minimumPayment?: number; dueDate?: Date; notes?: string; } export interface UpdateDebtAccountDTO { name?: string; creditor?: string; accountNumber?: string; currentBalance?: number; interestRate?: number; minimumPayment?: number; dueDate?: Date; notes?: string; } /** * Service for DebtAccount business logic * Implements Single Responsibility Principle - handles only business logic * Implements Dependency Inversion - depends on repository abstractions */ export class DebtAccountService { constructor( private accountRepository: DebtAccountRepository, private categoryRepository: DebtCategoryRepository ) {} /** * Create a new debt account */ async create(userId: string, data: CreateDebtAccountDTO): Promise { this.validateAccountData(data); // Verify category ownership const category = await this.categoryRepository.findById(data.categoryId); if (!category) { throw new NotFoundError('Debt category not found'); } if (category.userId !== userId) { throw new ForbiddenError('Access denied'); } return this.accountRepository.create({ name: data.name, creditor: data.creditor, accountNumber: data.accountNumber, originalBalance: data.originalBalance, currentBalance: data.currentBalance, interestRate: data.interestRate, minimumPayment: data.minimumPayment, dueDate: data.dueDate, notes: data.notes, category: { connect: {id: data.categoryId}, }, }); } /** * Get all debt accounts for a user */ async getAllByUser(userId: string): Promise { return this.accountRepository.findAllByUser(userId); } /** * Get debt accounts with statistics */ async getWithStats(userId: string): Promise { return this.accountRepository.getWithStats(userId); } /** * Get debt accounts by category */ async getByCategory(categoryId: string, userId: string): Promise { // Verify category ownership const category = await this.categoryRepository.findById(categoryId); if (!category) { throw new NotFoundError('Debt category not found'); } if (category.userId !== userId) { throw new ForbiddenError('Access denied'); } return this.accountRepository.findByCategory(categoryId); } /** * Get a single debt account by ID */ async getById(id: string, userId: string): Promise { const account = await this.accountRepository.findById(id); if (!account) { throw new NotFoundError('Debt account not found'); } // Verify ownership through category if (account.category.userId !== userId) { throw new ForbiddenError('Access denied'); } return account; } /** * Update a debt account */ async update(id: string, userId: string, data: UpdateDebtAccountDTO): Promise { await this.getById(id, userId); if (data.currentBalance !== undefined || data.interestRate !== undefined || data.minimumPayment !== undefined) { this.validateAccountData(data as CreateDebtAccountDTO); } return this.accountRepository.update(id, data); } /** * Delete a debt account */ async delete(id: string, userId: string): Promise { await this.getById(id, userId); await this.accountRepository.delete(id); } /** * Get total debt for a user */ async getTotalDebt(userId: string): Promise { return this.accountRepository.getTotalDebt(userId); } /** * Validate account data */ private validateAccountData(data: CreateDebtAccountDTO | UpdateDebtAccountDTO): void { if ('originalBalance' in data && data.originalBalance < 0) { throw new ValidationError('Original balance cannot be negative'); } if (data.currentBalance !== undefined && data.currentBalance < 0) { throw new ValidationError('Current balance cannot be negative'); } if (data.interestRate !== undefined && (data.interestRate < 0 || data.interestRate > 100)) { throw new ValidationError('Interest rate must be between 0 and 100'); } if (data.minimumPayment !== undefined && data.minimumPayment < 0) { throw new ValidationError('Minimum payment cannot be negative'); } } }