Update backend API configuration and schema for improved functionality
- Modified TypeScript configuration to disable strict mode and allow importing TypeScript extensions. - Updated Prisma schema to enhance the User model with a new debtAccounts field and refined asset/liability types. - Adjusted environment variable parsing for PORT to use coercion for better type handling. - Refactored various controllers and repositories to utilize type imports for better clarity and maintainability. - Enhanced service layers with new methods for retrieving assets by type and calculating invoice statistics. - Introduced new types for invoice statuses and asset types to ensure consistency across the application.
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
import {Asset, AssetType} from '@prisma/client';
|
||||
import type {Asset} from '@prisma/client';
|
||||
import {AssetRepository} from '../repositories/AssetRepository';
|
||||
import {NotFoundError, ForbiddenError, ValidationError} from '../utils/errors';
|
||||
import {NotFoundError, ValidationError} from '../utils/errors';
|
||||
|
||||
type AssetType = 'cash' | 'investment' | 'property' | 'vehicle' | 'other';
|
||||
const VALID_ASSET_TYPES: AssetType[] = ['cash', 'investment', 'property', 'vehicle', 'other'];
|
||||
|
||||
interface CreateAssetDTO {
|
||||
name: string;
|
||||
@@ -54,7 +57,7 @@ export class AssetService {
|
||||
if (data.value !== undefined || data.name !== undefined || data.type !== undefined) {
|
||||
this.validateAssetData({
|
||||
name: data.name || asset.name,
|
||||
type: data.type || asset.type,
|
||||
type: (data.type || asset.type) as AssetType,
|
||||
value: data.value !== undefined ? data.value : asset.value,
|
||||
});
|
||||
}
|
||||
@@ -75,6 +78,18 @@ export class AssetService {
|
||||
return this.assetRepository.getTotalValue(userId);
|
||||
}
|
||||
|
||||
async getByType(userId: string): Promise<Record<string, Asset[]>> {
|
||||
const assets = await this.assetRepository.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[]>);
|
||||
}
|
||||
|
||||
private validateAssetData(data: CreateAssetDTO): void {
|
||||
if (!data.name || data.name.trim().length === 0) {
|
||||
throw new ValidationError('Asset name is required');
|
||||
@@ -84,7 +99,7 @@ export class AssetService {
|
||||
throw new ValidationError('Asset value cannot be negative');
|
||||
}
|
||||
|
||||
if (!Object.values(AssetType).includes(data.type)) {
|
||||
if (!VALID_ASSET_TYPES.includes(data.type)) {
|
||||
throw new ValidationError('Invalid asset type');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import {Invoice, InvoiceStatus, Prisma} from '@prisma/client';
|
||||
import type {Invoice, Prisma} from '@prisma/client';
|
||||
import {InvoiceRepository} from '../repositories/InvoiceRepository';
|
||||
import {NotFoundError, ValidationError} from '../utils/errors';
|
||||
|
||||
type InvoiceStatus = 'draft' | 'sent' | 'paid' | 'overdue' | 'cancelled';
|
||||
|
||||
interface InvoiceLineItemDTO {
|
||||
description: string;
|
||||
quantity: number;
|
||||
@@ -26,6 +28,17 @@ interface UpdateInvoiceDTO {
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
interface InvoiceStats {
|
||||
total: number;
|
||||
draft: number;
|
||||
sent: number;
|
||||
paid: number;
|
||||
overdue: number;
|
||||
totalAmount: number;
|
||||
paidAmount: number;
|
||||
outstandingAmount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoice Service
|
||||
* Handles invoice business logic including calculations
|
||||
@@ -37,6 +50,10 @@ export class InvoiceService {
|
||||
return this.invoiceRepository.findAllByUser(userId, filters) as unknown as Invoice[];
|
||||
}
|
||||
|
||||
async getAllByUser(userId: string, filters?: {status?: string; clientId?: string}): Promise<Invoice[]> {
|
||||
return this.invoiceRepository.findAllByUser(userId, filters) as unknown as Invoice[];
|
||||
}
|
||||
|
||||
async getById(id: string, userId: string): Promise<Invoice> {
|
||||
const invoice = await this.invoiceRepository.findByIdAndUser(id, userId);
|
||||
if (!invoice) {
|
||||
@@ -72,7 +89,7 @@ export class InvoiceService {
|
||||
|
||||
return this.invoiceRepository.create({
|
||||
invoiceNumber,
|
||||
status: data.status || InvoiceStatus.DRAFT,
|
||||
status: data.status || 'draft',
|
||||
issueDate: data.issueDate,
|
||||
dueDate: data.dueDate,
|
||||
subtotal,
|
||||
@@ -136,6 +153,50 @@ export class InvoiceService {
|
||||
await this.invoiceRepository.delete(id);
|
||||
}
|
||||
|
||||
async getStats(userId: string): Promise<InvoiceStats> {
|
||||
const invoices = await this.invoiceRepository.findAllByUser(userId);
|
||||
|
||||
const stats: InvoiceStats = {
|
||||
total: invoices.length,
|
||||
draft: 0,
|
||||
sent: 0,
|
||||
paid: 0,
|
||||
overdue: 0,
|
||||
totalAmount: 0,
|
||||
paidAmount: 0,
|
||||
outstandingAmount: 0,
|
||||
};
|
||||
|
||||
for (const inv of invoices) {
|
||||
stats.totalAmount += inv.total;
|
||||
|
||||
switch (inv.status) {
|
||||
case 'draft':
|
||||
stats.draft++;
|
||||
break;
|
||||
case 'sent':
|
||||
stats.sent++;
|
||||
stats.outstandingAmount += inv.total;
|
||||
break;
|
||||
case 'paid':
|
||||
stats.paid++;
|
||||
stats.paidAmount += inv.total;
|
||||
break;
|
||||
case 'overdue':
|
||||
stats.overdue++;
|
||||
stats.outstandingAmount += inv.total;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
async getOverdueInvoices(userId: string): Promise<Invoice[]> {
|
||||
const invoices = await this.invoiceRepository.findAllByUser(userId, {status: 'overdue'});
|
||||
return invoices as unknown as Invoice[];
|
||||
}
|
||||
|
||||
private validateInvoiceData(data: CreateInvoiceDTO): void {
|
||||
if (!data.clientId) {
|
||||
throw new ValidationError('Client ID is required');
|
||||
|
||||
Reference in New Issue
Block a user