import { prisma } from "./db.server"; import { getTotalExpenses, getExpensesByCategory } from "./expense-management.server"; // Financial summary interface export interface FinancialSummary { totalIncome: number; totalExpenses: number; netProfit: number; incomeCount: number; expenseCount: number; profitMargin: number; } // Monthly financial data interface export interface MonthlyFinancialData { month: string; year: number; income: number; expenses: number; profit: number; } // Category breakdown interface export interface CategoryBreakdown { category: string; amount: number; count: number; percentage: number; } // Get financial summary for a date range export async function getFinancialSummary( dateFrom?: Date, dateTo?: Date ): Promise { const whereClause: any = {}; if (dateFrom || dateTo) { whereClause.incomeDate = {}; if (dateFrom) { whereClause.incomeDate.gte = dateFrom; } if (dateTo) { whereClause.incomeDate.lte = dateTo; } } // Get income data const incomeResult = await prisma.income.aggregate({ where: whereClause, _sum: { amount: true, }, _count: { id: true, }, }); const totalIncome = incomeResult._sum.amount || 0; const incomeCount = incomeResult._count.id; // Get expense data const totalExpenses = await getTotalExpenses(dateFrom, dateTo); const expenseCount = await prisma.expense.count({ where: dateFrom || dateTo ? { expenseDate: { ...(dateFrom && { gte: dateFrom }), ...(dateTo && { lte: dateTo }), }, } : undefined, }); // Calculate derived metrics const netProfit = totalIncome - totalExpenses; const profitMargin = totalIncome > 0 ? (netProfit / totalIncome) * 100 : 0; return { totalIncome, totalExpenses, netProfit, incomeCount, expenseCount, profitMargin, }; } // Get monthly financial data for the last 12 months export async function getMonthlyFinancialData(): Promise { const months: MonthlyFinancialData[] = []; const currentDate = new Date(); for (let i = 11; i >= 0; i--) { const date = new Date(currentDate.getFullYear(), currentDate.getMonth() - i, 1); const nextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1); const monthName = date.toLocaleDateString('ar-SA', { month: 'long' }); const year = date.getFullYear(); // Get income for this month const incomeResult = await prisma.income.aggregate({ where: { incomeDate: { gte: date, lt: nextMonth, }, }, _sum: { amount: true, }, }); // Get expenses for this month const expenseResult = await prisma.expense.aggregate({ where: { expenseDate: { gte: date, lt: nextMonth, }, }, _sum: { amount: true, }, }); const income = incomeResult._sum.amount || 0; const expenses = expenseResult._sum.amount || 0; const profit = income - expenses; months.push({ month: monthName, year, income, expenses, profit, }); } return months; } // Get income breakdown by maintenance type export async function getIncomeByMaintenanceType( dateFrom?: Date, dateTo?: Date ): Promise { const whereClause: any = {}; if (dateFrom || dateTo) { whereClause.incomeDate = {}; if (dateFrom) { whereClause.incomeDate.gte = dateFrom; } if (dateTo) { whereClause.incomeDate.lte = dateTo; } } // Get income grouped by maintenance type const result = await prisma.income.findMany({ where: whereClause, include: { maintenanceVisit: { select: { maintenanceJobs: true, }, }, }, }); // Group by maintenance type const grouped = result.reduce((acc, income) => { try { const jobs = JSON.parse(income.maintenanceVisit.maintenanceJobs); const types = jobs.map((job: any) => job.job || 'غير محدد'); types.forEach((type: string) => { if (!acc[type]) { acc[type] = { amount: 0, count: 0 }; } acc[type].amount += income.amount / types.length; // Distribute amount across multiple jobs acc[type].count += 1; }); } catch { // Fallback for invalid JSON const type = 'غير محدد'; if (!acc[type]) { acc[type] = { amount: 0, count: 0 }; } acc[type].amount += income.amount; acc[type].count += 1; } return acc; }, {} as Record); // Calculate total for percentage calculation const total = Object.values(grouped).reduce((sum, item) => sum + item.amount, 0); // Convert to array with percentages return Object.entries(grouped) .map(([category, data]) => ({ category, amount: data.amount, count: data.count, percentage: total > 0 ? (data.amount / total) * 100 : 0, })) .sort((a, b) => b.amount - a.amount); } // Get expense breakdown by category export async function getExpenseBreakdown( dateFrom?: Date, dateTo?: Date ): Promise { const categoryData = await getExpensesByCategory(dateFrom, dateTo); const total = categoryData.reduce((sum, item) => sum + item.total, 0); return categoryData.map(item => ({ category: item.category, amount: item.total, count: item.count, percentage: total > 0 ? (item.total / total) * 100 : 0, })); } // Get top customers by revenue export async function getTopCustomersByRevenue( limit: number = 10, dateFrom?: Date, dateTo?: Date ): Promise<{ customerId: number; customerName: string; totalRevenue: number; visitCount: number; }[]> { const whereClause: any = {}; if (dateFrom || dateTo) { whereClause.incomeDate = {}; if (dateFrom) { whereClause.incomeDate.gte = dateFrom; } if (dateTo) { whereClause.incomeDate.lte = dateTo; } } const result = await prisma.income.findMany({ where: whereClause, include: { maintenanceVisit: { include: { customer: { select: { id: true, name: true, }, }, }, }, }, }); // Group by customer const customerRevenue = result.reduce((acc, income) => { const customer = income.maintenanceVisit.customer; const customerId = customer.id; if (!acc[customerId]) { acc[customerId] = { customerId, customerName: customer.name, totalRevenue: 0, visitCount: 0, }; } acc[customerId].totalRevenue += income.amount; acc[customerId].visitCount += 1; return acc; }, {} as Record); return Object.values(customerRevenue) .sort((a, b) => b.totalRevenue - a.totalRevenue) .slice(0, limit); } // Get financial trends (comparing current period with previous period) export async function getFinancialTrends( dateFrom: Date, dateTo: Date ): Promise<{ currentPeriod: FinancialSummary; previousPeriod: FinancialSummary; trends: { incomeGrowth: number; expenseGrowth: number; profitGrowth: number; }; }> { // Calculate previous period dates const periodLength = dateTo.getTime() - dateFrom.getTime(); const previousDateTo = new Date(dateFrom.getTime() - 1); const previousDateFrom = new Date(previousDateTo.getTime() - periodLength); // Get current and previous period summaries const [currentPeriod, previousPeriod] = await Promise.all([ getFinancialSummary(dateFrom, dateTo), getFinancialSummary(previousDateFrom, previousDateTo), ]); // Calculate growth percentages const incomeGrowth = previousPeriod.totalIncome > 0 ? ((currentPeriod.totalIncome - previousPeriod.totalIncome) / previousPeriod.totalIncome) * 100 : 0; const expenseGrowth = previousPeriod.totalExpenses > 0 ? ((currentPeriod.totalExpenses - previousPeriod.totalExpenses) / previousPeriod.totalExpenses) * 100 : 0; const profitGrowth = previousPeriod.netProfit !== 0 ? ((currentPeriod.netProfit - previousPeriod.netProfit) / Math.abs(previousPeriod.netProfit)) * 100 : 0; return { currentPeriod, previousPeriod, trends: { incomeGrowth, expenseGrowth, profitGrowth, }, }; }