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.
This commit is contained in:
2025-12-11 02:22:48 -05:00
parent df2cf418ea
commit 51074d02a9
5 changed files with 56 additions and 48 deletions

View File

@@ -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()
});
/**

View File

@@ -46,4 +46,24 @@ export class AssetRepository {
return result._sum.value || 0;
}
/**
* Get assets grouped by type
*/
async getByType(userId: string): Promise<Record<string, Asset[]>> {
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<string, Asset[]>
);
}
}

View File

@@ -76,4 +76,31 @@ export class InvoiceRepository implements IUserScopedRepository<Invoice> {
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};
}
}

View File

@@ -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 {

View File

@@ -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');
}
}
}