From 51074d02a9532bf663c8ab04ea1ef1dcceabb350 Mon Sep 17 00:00:00 2001 From: Alexander Zinn Date: Thu, 11 Dec 2025 02:22:48 -0500 Subject: [PATCH] Refactor liability schemas and enhance asset/invoice repositories - Updated LiabilityController and LiabilityService to consolidate liability properties into a single 'balance' field for improved clarity. - Added new methods in AssetRepository to retrieve assets grouped by type. - Introduced a method in InvoiceRepository to calculate user-specific invoice statistics, including total, paid, outstanding, and overdue invoices. - Adjusted DashboardService to reflect changes in asset value calculations. --- .../src/controllers/LiabilityController.ts | 20 ++--------- .../src/repositories/AssetRepository.ts | 20 +++++++++++ .../src/repositories/InvoiceRepository.ts | 27 ++++++++++++++ backend-api/src/services/DashboardService.ts | 2 +- backend-api/src/services/LiabilityService.ts | 35 ++++--------------- 5 files changed, 56 insertions(+), 48 deletions(-) diff --git a/backend-api/src/controllers/LiabilityController.ts b/backend-api/src/controllers/LiabilityController.ts index f406b08..b244796 100644 --- a/backend-api/src/controllers/LiabilityController.ts +++ b/backend-api/src/controllers/LiabilityController.ts @@ -6,29 +6,13 @@ import {z} from 'zod'; const createLiabilitySchema = z.object({ name: z.string().min(1).max(255), type: z.string().min(1), - currentBalance: z.number().min(0), - interestRate: z.number().min(0).max(100).optional(), - minimumPayment: z.number().min(0).optional(), - dueDate: z - .string() - .transform(str => new Date(str)) - .optional(), - creditor: z.string().max(255).optional(), - notes: z.string().optional() + balance: z.number().min(0) }); const updateLiabilitySchema = z.object({ name: z.string().min(1).max(255).optional(), type: z.string().min(1).optional(), - currentBalance: z.number().min(0).optional(), - interestRate: z.number().min(0).max(100).optional(), - minimumPayment: z.number().min(0).optional(), - dueDate: z - .string() - .transform(str => new Date(str)) - .optional(), - creditor: z.string().max(255).optional(), - notes: z.string().optional() + balance: z.number().min(0).optional() }); /** diff --git a/backend-api/src/repositories/AssetRepository.ts b/backend-api/src/repositories/AssetRepository.ts index 73c1d3c..838f0e8 100644 --- a/backend-api/src/repositories/AssetRepository.ts +++ b/backend-api/src/repositories/AssetRepository.ts @@ -46,4 +46,24 @@ export class AssetRepository { return result._sum.value || 0; } + + /** + * Get assets grouped by type + */ + async getByType(userId: string): Promise> { + const assets = await this.findAllByUser(userId); + + return assets.reduce( + (acc, asset) => { + const type = asset.type; + if (!acc[type]) { + acc[type] = []; + } + acc[type].push(asset); + + return acc; + }, + {} as Record + ); + } } diff --git a/backend-api/src/repositories/InvoiceRepository.ts b/backend-api/src/repositories/InvoiceRepository.ts index 00e5105..7b176f1 100644 --- a/backend-api/src/repositories/InvoiceRepository.ts +++ b/backend-api/src/repositories/InvoiceRepository.ts @@ -76,4 +76,31 @@ export class InvoiceRepository implements IUserScopedRepository { return `INV-${year}-${String(count + 1).padStart(3, '0')}`; } + + /** + * Get invoice statistics for a user + */ + async getStats(userId: string): Promise<{ + totalInvoices: number; + paidInvoices: number; + outstandingAmount: number; + overdueInvoices: number; + }> { + const invoices = await prisma.invoice.findMany({ + where: {userId}, + select: {status: true, total: true, dueDate: true} + }); + + const now = new Date(); + const totalInvoices = invoices.length; + const paidInvoices = invoices.filter(inv => inv.status === 'paid').length; + const outstandingAmount = invoices + .filter(inv => inv.status !== 'paid' && inv.status !== 'cancelled') + .reduce((sum, inv) => sum + inv.total, 0); + const overdueInvoices = invoices.filter( + inv => inv.status !== 'paid' && inv.status !== 'cancelled' && inv.dueDate < now + ).length; + + return {totalInvoices, paidInvoices, outstandingAmount, overdueInvoices}; + } } diff --git a/backend-api/src/services/DashboardService.ts b/backend-api/src/services/DashboardService.ts index 2554f59..3efb207 100644 --- a/backend-api/src/services/DashboardService.ts +++ b/backend-api/src/services/DashboardService.ts @@ -55,7 +55,7 @@ export class DashboardService { const assetAllocation = Object.entries(assetsByType).map(([type, assets]) => ({ type, count: assets.length, - totalValue: assets.reduce((sum, asset) => sum + asset.currentValue, 0) + totalValue: assets.reduce((sum, asset) => sum + asset.value, 0) })); return { diff --git a/backend-api/src/services/LiabilityService.ts b/backend-api/src/services/LiabilityService.ts index 214acff..ae0e118 100644 --- a/backend-api/src/services/LiabilityService.ts +++ b/backend-api/src/services/LiabilityService.ts @@ -5,23 +5,13 @@ import {NotFoundError, ValidationError, ForbiddenError} from '../utils/errors'; export interface CreateLiabilityDTO { name: string; type: string; - currentBalance: number; - interestRate?: number; - minimumPayment?: number; - dueDate?: Date; - creditor?: string; - notes?: string; + balance: number; } export interface UpdateLiabilityDTO { name?: string; type?: string; - currentBalance?: number; - interestRate?: number; - minimumPayment?: number; - dueDate?: Date; - creditor?: string; - notes?: string; + balance?: number; } /** @@ -41,12 +31,7 @@ export class LiabilityService { return this.liabilityRepository.create({ name: data.name, type: data.type, - currentBalance: data.currentBalance, - interestRate: data.interestRate, - minimumPayment: data.minimumPayment, - dueDate: data.dueDate, - creditor: data.creditor, - notes: data.notes, + balance: data.balance, user: { connect: {id: userId} } @@ -85,7 +70,7 @@ export class LiabilityService { // Verify ownership await this.getById(id, userId); - if (data.currentBalance !== undefined || data.interestRate !== undefined || data.minimumPayment !== undefined) { + if (data.balance !== undefined) { this.validateLiabilityData(data as CreateLiabilityDTO); } @@ -120,16 +105,8 @@ export class LiabilityService { * Validate liability data */ private validateLiabilityData(data: CreateLiabilityDTO | UpdateLiabilityDTO): void { - 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'); + if (data.balance !== undefined && data.balance < 0) { + throw new ValidationError('Balance cannot be negative'); } } }