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:
@@ -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()
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -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[]>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user