car_mms/app/lib/__tests__/financial-reporting.test.ts
2025-09-11 14:22:27 +03:00

458 lines
17 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
getFinancialSummary,
getMonthlyFinancialData,
getIncomeByMaintenanceType,
getExpenseBreakdown,
getTopCustomersByRevenue,
getFinancialTrends
} from '../financial-reporting.server';
import { prisma } from '../db.server';
describe('Financial Reporting', () => {
beforeEach(async () => {
// Clean up test data
await prisma.income.deleteMany();
await prisma.expense.deleteMany();
await prisma.maintenanceVisit.deleteMany();
await prisma.vehicle.deleteMany();
await prisma.customer.deleteMany();
});
afterEach(async () => {
// Clean up test data
await prisma.income.deleteMany();
await prisma.expense.deleteMany();
await prisma.maintenanceVisit.deleteMany();
await prisma.vehicle.deleteMany();
await prisma.customer.deleteMany();
});
describe('getFinancialSummary', () => {
beforeEach(async () => {
// Create test customer
const customer = await prisma.customer.create({
data: {
name: 'عميل تجريبي',
phone: '0501234567',
},
});
// Create test vehicle
const vehicle = await prisma.vehicle.create({
data: {
plateNumber: 'ABC-123',
bodyType: 'سيدان',
manufacturer: 'تويوتا',
model: 'كامري',
year: 2020,
transmission: 'Automatic',
fuel: 'Gasoline',
useType: 'personal',
ownerId: customer.id,
},
});
// Create test maintenance visit
const visit = await prisma.maintenanceVisit.create({
data: {
vehicleId: vehicle.id,
customerId: customer.id,
maintenanceType: 'تغيير زيت',
description: 'تغيير زيت المحرك',
cost: 200.00,
kilometers: 50000,
nextVisitDelay: 3,
},
});
// Create test income
await prisma.income.create({
data: {
maintenanceVisitId: visit.id,
amount: 200.00,
incomeDate: new Date('2024-01-15'),
},
});
// Create test expenses
await prisma.expense.createMany({
data: [
{
description: 'قطع غيار',
category: 'قطع غيار',
amount: 50.00,
expenseDate: new Date('2024-01-10'),
},
{
description: 'أدوات',
category: 'أدوات',
amount: 30.00,
expenseDate: new Date('2024-01-12'),
},
],
});
});
it('should calculate financial summary correctly', async () => {
const summary = await getFinancialSummary();
expect(summary.totalIncome).toBe(200.00);
expect(summary.totalExpenses).toBe(80.00);
expect(summary.netProfit).toBe(120.00);
expect(summary.incomeCount).toBe(1);
expect(summary.expenseCount).toBe(2);
expect(summary.profitMargin).toBe(60.0); // (120/200) * 100
});
it('should filter by date range', async () => {
const dateFrom = new Date('2024-01-11');
const dateTo = new Date('2024-01-20');
const summary = await getFinancialSummary(dateFrom, dateTo);
expect(summary.totalIncome).toBe(200.00);
expect(summary.totalExpenses).toBe(30.00); // Only one expense in range
expect(summary.netProfit).toBe(170.00);
});
});
describe('getIncomeByMaintenanceType', () => {
beforeEach(async () => {
// Create test data
const customer = await prisma.customer.create({
data: { name: 'عميل تجريبي', phone: '0501234567' },
});
const vehicle = await prisma.vehicle.create({
data: {
plateNumber: 'ABC-123',
bodyType: 'سيدان',
manufacturer: 'تويوتا',
model: 'كامري',
year: 2020,
transmission: 'Automatic',
fuel: 'Gasoline',
useType: 'personal',
ownerId: customer.id,
},
});
// Create maintenance visits with different types
const visit1 = await prisma.maintenanceVisit.create({
data: {
vehicleId: vehicle.id,
customerId: customer.id,
maintenanceType: 'تغيير زيت',
description: 'تغيير زيت المحرك',
cost: 200.00,
kilometers: 50000,
nextVisitDelay: 3,
},
});
const visit2 = await prisma.maintenanceVisit.create({
data: {
vehicleId: vehicle.id,
customerId: customer.id,
maintenanceType: 'فحص دوري',
description: 'فحص دوري شامل',
cost: 150.00,
kilometers: 51000,
nextVisitDelay: 3,
},
});
const visit3 = await prisma.maintenanceVisit.create({
data: {
vehicleId: vehicle.id,
customerId: customer.id,
maintenanceType: 'تغيير زيت',
description: 'تغيير زيت مرة أخرى',
cost: 180.00,
kilometers: 52000,
nextVisitDelay: 3,
},
});
// Create corresponding income records
await prisma.income.createMany({
data: [
{ maintenanceVisitId: visit1.id, amount: 200.00 },
{ maintenanceVisitId: visit2.id, amount: 150.00 },
{ maintenanceVisitId: visit3.id, amount: 180.00 },
],
});
});
it('should group income by maintenance type', async () => {
const result = await getIncomeByMaintenanceType();
expect(result).toHaveLength(2);
const oilChange = result.find(r => r.category === 'تغيير زيت');
expect(oilChange?.amount).toBe(380.00);
expect(oilChange?.count).toBe(2);
expect(oilChange?.percentage).toBeCloseTo(71.7, 1); // 380/530 * 100
const inspection = result.find(r => r.category === 'فحص دوري');
expect(inspection?.amount).toBe(150.00);
expect(inspection?.count).toBe(1);
expect(inspection?.percentage).toBeCloseTo(28.3, 1); // 150/530 * 100
});
});
describe('getExpenseBreakdown', () => {
beforeEach(async () => {
await prisma.expense.createMany({
data: [
{ description: 'قطع غيار 1', category: 'قطع غيار', amount: 100 },
{ description: 'قطع غيار 2', category: 'قطع غيار', amount: 150 },
{ description: 'أدوات 1', category: 'أدوات', amount: 200 },
{ description: 'إيجار', category: 'إيجار', amount: 3000 },
],
});
});
it('should group expenses by category with percentages', async () => {
const result = await getExpenseBreakdown();
expect(result).toHaveLength(3);
const rent = result.find(r => r.category === 'إيجار');
expect(rent?.amount).toBe(3000);
expect(rent?.count).toBe(1);
expect(rent?.percentage).toBeCloseTo(86.96, 1); // 3000/3450 * 100
const spareParts = result.find(r => r.category === 'قطع غيار');
expect(spareParts?.amount).toBe(250);
expect(spareParts?.count).toBe(2);
expect(spareParts?.percentage).toBeCloseTo(7.25, 1); // 250/3450 * 100
const tools = result.find(r => r.category === 'أدوات');
expect(tools?.amount).toBe(200);
expect(tools?.count).toBe(1);
expect(tools?.percentage).toBeCloseTo(5.80, 1); // 200/3450 * 100
});
});
describe('getTopCustomersByRevenue', () => {
beforeEach(async () => {
// Create test customers
const customer1 = await prisma.customer.create({
data: { name: 'عميل أول', phone: '0501111111' },
});
const customer2 = await prisma.customer.create({
data: { name: 'عميل ثاني', phone: '0502222222' },
});
// Create vehicles
const vehicle1 = await prisma.vehicle.create({
data: {
plateNumber: 'ABC-111',
bodyType: 'سيدان',
manufacturer: 'تويوتا',
model: 'كامري',
year: 2020,
transmission: 'Automatic',
fuel: 'Gasoline',
useType: 'personal',
ownerId: customer1.id,
},
});
const vehicle2 = await prisma.vehicle.create({
data: {
plateNumber: 'ABC-222',
bodyType: 'SUV',
manufacturer: 'هيونداي',
model: 'توسان',
year: 2021,
transmission: 'Automatic',
fuel: 'Gasoline',
useType: 'personal',
ownerId: customer2.id,
},
});
// Create maintenance visits
const visit1 = await prisma.maintenanceVisit.create({
data: {
vehicleId: vehicle1.id,
customerId: customer1.id,
maintenanceType: 'تغيير زيت',
description: 'تغيير زيت',
cost: 300.00,
kilometers: 50000,
nextVisitDelay: 3,
},
});
const visit2 = await prisma.maintenanceVisit.create({
data: {
vehicleId: vehicle1.id,
customerId: customer1.id,
maintenanceType: 'فحص دوري',
description: 'فحص دوري',
cost: 200.00,
kilometers: 51000,
nextVisitDelay: 3,
},
});
const visit3 = await prisma.maintenanceVisit.create({
data: {
vehicleId: vehicle2.id,
customerId: customer2.id,
maintenanceType: 'تغيير زيت',
description: 'تغيير زيت',
cost: 150.00,
kilometers: 30000,
nextVisitDelay: 3,
},
});
// Create income records
await prisma.income.createMany({
data: [
{ maintenanceVisitId: visit1.id, amount: 300.00 },
{ maintenanceVisitId: visit2.id, amount: 200.00 },
{ maintenanceVisitId: visit3.id, amount: 150.00 },
],
});
});
it('should return top customers by revenue', async () => {
const result = await getTopCustomersByRevenue(10);
expect(result).toHaveLength(2);
// Should be sorted by revenue descending
expect(result[0].customerName).toBe('عميل أول');
expect(result[0].totalRevenue).toBe(500.00);
expect(result[0].visitCount).toBe(2);
expect(result[1].customerName).toBe('عميل ثاني');
expect(result[1].totalRevenue).toBe(150.00);
expect(result[1].visitCount).toBe(1);
});
it('should limit results correctly', async () => {
const result = await getTopCustomersByRevenue(1);
expect(result).toHaveLength(1);
expect(result[0].customerName).toBe('عميل أول');
});
});
describe('getFinancialTrends', () => {
beforeEach(async () => {
// Create test customer and vehicle
const customer = await prisma.customer.create({
data: { name: 'عميل تجريبي', phone: '0501234567' },
});
const vehicle = await prisma.vehicle.create({
data: {
plateNumber: 'ABC-123',
bodyType: 'سيدان',
manufacturer: 'تويوتا',
model: 'كامري',
year: 2020,
transmission: 'Automatic',
fuel: 'Gasoline',
useType: 'personal',
ownerId: customer.id,
},
});
// Create maintenance visits for different periods
const currentVisit = await prisma.maintenanceVisit.create({
data: {
vehicleId: vehicle.id,
customerId: customer.id,
maintenanceType: 'تغيير زيت',
description: 'تغيير زيت حالي',
cost: 300.00,
kilometers: 50000,
nextVisitDelay: 3,
},
});
const previousVisit = await prisma.maintenanceVisit.create({
data: {
vehicleId: vehicle.id,
customerId: customer.id,
maintenanceType: 'تغيير زيت',
description: 'تغيير زيت سابق',
cost: 200.00,
kilometers: 45000,
nextVisitDelay: 3,
},
});
// Create income for current period (Jan 2024)
await prisma.income.create({
data: {
maintenanceVisitId: currentVisit.id,
amount: 300.00,
incomeDate: new Date('2024-01-15'),
},
});
// Create income for previous period (Dec 2023)
await prisma.income.create({
data: {
maintenanceVisitId: previousVisit.id,
amount: 200.00,
incomeDate: new Date('2023-12-15'),
},
});
// Create expenses for current period
await prisma.expense.create({
data: {
description: 'مصروف حالي',
category: 'قطع غيار',
amount: 100.00,
expenseDate: new Date('2024-01-10'),
},
});
// Create expenses for previous period
await prisma.expense.create({
data: {
description: 'مصروف سابق',
category: 'قطع غيار',
amount: 80.00,
expenseDate: new Date('2023-12-10'),
},
});
});
it('should calculate financial trends correctly', async () => {
const dateFrom = new Date('2024-01-01');
const dateTo = new Date('2024-01-31');
const result = await getFinancialTrends(dateFrom, dateTo);
expect(result.currentPeriod.totalIncome).toBe(300.00);
expect(result.currentPeriod.totalExpenses).toBe(100.00);
expect(result.currentPeriod.netProfit).toBe(200.00);
expect(result.previousPeriod.totalIncome).toBe(200.00);
expect(result.previousPeriod.totalExpenses).toBe(80.00);
expect(result.previousPeriod.netProfit).toBe(120.00);
// Income growth: (300-200)/200 * 100 = 50%
expect(result.trends.incomeGrowth).toBeCloseTo(50.0, 1);
// Expense growth: (100-80)/80 * 100 = 25%
expect(result.trends.expenseGrowth).toBeCloseTo(25.0, 1);
// Profit growth: (200-120)/120 * 100 = 66.67%
expect(result.trends.profitGrowth).toBeCloseTo(66.67, 1);
});
});
});