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({
|
const createLiabilitySchema = z.object({
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
type: z.string().min(1),
|
type: z.string().min(1),
|
||||||
currentBalance: z.number().min(0),
|
balance: 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()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateLiabilitySchema = z.object({
|
const updateLiabilitySchema = z.object({
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
type: z.string().min(1).optional(),
|
type: z.string().min(1).optional(),
|
||||||
currentBalance: z.number().min(0).optional(),
|
balance: 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()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -46,4 +46,24 @@ export class AssetRepository {
|
|||||||
|
|
||||||
return result._sum.value || 0;
|
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')}`;
|
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]) => ({
|
const assetAllocation = Object.entries(assetsByType).map(([type, assets]) => ({
|
||||||
type,
|
type,
|
||||||
count: assets.length,
|
count: assets.length,
|
||||||
totalValue: assets.reduce((sum, asset) => sum + asset.currentValue, 0)
|
totalValue: assets.reduce((sum, asset) => sum + asset.value, 0)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,23 +5,13 @@ import {NotFoundError, ValidationError, ForbiddenError} from '../utils/errors';
|
|||||||
export interface CreateLiabilityDTO {
|
export interface CreateLiabilityDTO {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
currentBalance: number;
|
balance: number;
|
||||||
interestRate?: number;
|
|
||||||
minimumPayment?: number;
|
|
||||||
dueDate?: Date;
|
|
||||||
creditor?: string;
|
|
||||||
notes?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateLiabilityDTO {
|
export interface UpdateLiabilityDTO {
|
||||||
name?: string;
|
name?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
currentBalance?: number;
|
balance?: number;
|
||||||
interestRate?: number;
|
|
||||||
minimumPayment?: number;
|
|
||||||
dueDate?: Date;
|
|
||||||
creditor?: string;
|
|
||||||
notes?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,12 +31,7 @@ export class LiabilityService {
|
|||||||
return this.liabilityRepository.create({
|
return this.liabilityRepository.create({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
type: data.type,
|
type: data.type,
|
||||||
currentBalance: data.currentBalance,
|
balance: data.balance,
|
||||||
interestRate: data.interestRate,
|
|
||||||
minimumPayment: data.minimumPayment,
|
|
||||||
dueDate: data.dueDate,
|
|
||||||
creditor: data.creditor,
|
|
||||||
notes: data.notes,
|
|
||||||
user: {
|
user: {
|
||||||
connect: {id: userId}
|
connect: {id: userId}
|
||||||
}
|
}
|
||||||
@@ -85,7 +70,7 @@ export class LiabilityService {
|
|||||||
// Verify ownership
|
// Verify ownership
|
||||||
await this.getById(id, userId);
|
await this.getById(id, userId);
|
||||||
|
|
||||||
if (data.currentBalance !== undefined || data.interestRate !== undefined || data.minimumPayment !== undefined) {
|
if (data.balance !== undefined) {
|
||||||
this.validateLiabilityData(data as CreateLiabilityDTO);
|
this.validateLiabilityData(data as CreateLiabilityDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,16 +105,8 @@ export class LiabilityService {
|
|||||||
* Validate liability data
|
* Validate liability data
|
||||||
*/
|
*/
|
||||||
private validateLiabilityData(data: CreateLiabilityDTO | UpdateLiabilityDTO): void {
|
private validateLiabilityData(data: CreateLiabilityDTO | UpdateLiabilityDTO): void {
|
||||||
if (data.currentBalance !== undefined && data.currentBalance < 0) {
|
if (data.balance !== undefined && data.balance < 0) {
|
||||||
throw new ValidationError('Current balance cannot be negative');
|
throw new ValidationError('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');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user