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