| import { |
| getAccountBalancesSchema, |
| getBillableHoursSchema, |
| getCashFlowSchema, |
| getCategoryExpensesSchema, |
| getCustomerLifetimeValueSchema, |
| getGrowthRateSchema, |
| getInboxStatsSchema, |
| getMonthlySpendingSchema, |
| getNetPositionSchema, |
| getOutstandingInvoicesSchema, |
| getOverdueInvoicesAlertSchema, |
| getProfitMarginSchema, |
| getRecurringExpensesSchema, |
| getRevenueSummarySchema, |
| getRunwaySchema, |
| getTaxSummarySchema, |
| getTrackedTimeSchema, |
| getVaultActivitySchema, |
| updateWidgetPreferencesSchema, |
| } from "@api/schemas/widgets"; |
| import { createTRPCRouter, protectedProcedure } from "@api/trpc/init"; |
| import { widgetPreferencesCache } from "@midday/cache/widget-preferences-cache"; |
| import { |
| getBillableHours, |
| getCashBalance, |
| getCashFlow, |
| getCustomerLifetimeValue, |
| getGrowthRate, |
| getInboxStats, |
| getNetPosition, |
| getOutstandingInvoices, |
| getOverdueInvoicesAlert, |
| getProfitMargin, |
| getRecentDocuments, |
| getRecurringExpenses, |
| getReports, |
| getRunway, |
| getSpending, |
| getSpendingForPeriod, |
| getTaxSummary, |
| getTopRevenueClient, |
| getTrackedTime, |
| } from "@midday/db/queries"; |
|
|
| export const widgetsRouter = createTRPCRouter({ |
| getRunway: protectedProcedure |
| .input(getRunwaySchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const runway = await getRunway(db, { |
| teamId: teamId!, |
| currency: input.currency, |
| }); |
|
|
| return { |
| result: runway, |
| toolCall: { |
| toolName: "getBurnRateAnalysis", |
| toolParams: { |
| currency: input.currency, |
| }, |
| }, |
| }; |
| }), |
|
|
| getTopCustomer: protectedProcedure.query(async ({ ctx: { db, teamId } }) => { |
| const topCustomer = await getTopRevenueClient(db, { |
| teamId: teamId!, |
| }); |
|
|
| return { |
| result: topCustomer, |
| }; |
| }), |
|
|
| getRevenueSummary: protectedProcedure |
| .input(getRevenueSummarySchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const result = await getReports(db, { |
| teamId: teamId!, |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| type: "revenue", |
| revenueType: input.revenueType, |
| }); |
|
|
| return { |
| result: { |
| totalRevenue: result.summary.currentTotal, |
| currency: result.summary.currency, |
| revenueType: input.revenueType, |
| monthCount: result.result.length, |
| }, |
| }; |
| }), |
|
|
| getGrowthRate: protectedProcedure |
| .input(getGrowthRateSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const growthData = await getGrowthRate(db, { |
| teamId: teamId!, |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| type: input.type, |
| revenueType: input.revenueType, |
| period: input.period, |
| }); |
|
|
| return { |
| result: { |
| currentTotal: growthData.summary.currentTotal, |
| prevTotal: growthData.summary.previousTotal, |
| growthRate: growthData.summary.growthRate, |
| quarterlyGrowthRate: growthData.summary.periodGrowthRate, |
| currency: growthData.summary.currency, |
| type: growthData.summary.type, |
| revenueType: growthData.summary.revenueType, |
| period: growthData.summary.period, |
| trend: growthData.summary.trend, |
| meta: growthData.meta, |
| }, |
| }; |
| }), |
|
|
| getProfitMargin: protectedProcedure |
| .input(getProfitMarginSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const profitMarginData = await getProfitMargin(db, { |
| teamId: teamId!, |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| revenueType: input.revenueType, |
| }); |
|
|
| return { |
| result: { |
| totalRevenue: profitMarginData.summary.totalRevenue, |
| totalProfit: profitMarginData.summary.totalProfit, |
| profitMargin: profitMarginData.summary.profitMargin, |
| averageMargin: profitMarginData.summary.averageMargin, |
| currency: profitMarginData.summary.currency, |
| revenueType: profitMarginData.summary.revenueType, |
| trend: profitMarginData.summary.trend, |
| monthCount: profitMarginData.summary.monthCount, |
| monthlyData: profitMarginData.result, |
| meta: profitMarginData.meta, |
| }, |
| }; |
| }), |
|
|
| getCashFlow: protectedProcedure |
| .input(getCashFlowSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const cashFlowData = await getCashFlow(db, { |
| teamId: teamId!, |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| period: input.period, |
| }); |
|
|
| return { |
| result: { |
| netCashFlow: cashFlowData.summary.netCashFlow, |
| currency: cashFlowData.summary.currency, |
| period: cashFlowData.summary.period, |
| meta: cashFlowData.meta, |
| }, |
| }; |
| }), |
|
|
| getOutstandingInvoices: protectedProcedure |
| .input(getOutstandingInvoicesSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const invoicesData = await getOutstandingInvoices(db, { |
| teamId: teamId!, |
| currency: input.currency, |
| status: input.status, |
| }); |
|
|
| return { |
| result: { |
| count: invoicesData.summary.count, |
| totalAmount: invoicesData.summary.totalAmount, |
| currency: invoicesData.summary.currency, |
| status: invoicesData.summary.status, |
| meta: invoicesData.meta, |
| }, |
| }; |
| }), |
|
|
| getInboxStats: protectedProcedure |
| .input(getInboxStatsSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const inboxStats = await getInboxStats(db, { |
| teamId: teamId!, |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| }); |
|
|
| return { |
| result: inboxStats.result, |
| }; |
| }), |
|
|
| getTrackedTime: protectedProcedure |
| .input(getTrackedTimeSchema) |
| .query(async ({ ctx: { db, teamId, session }, input }) => { |
| const trackedTime = await getTrackedTime(db, { |
| teamId: teamId!, |
| assignedId: input.assignedId ?? session.user.id, |
| from: input.from, |
| to: input.to, |
| }); |
|
|
| return { |
| result: trackedTime, |
| }; |
| }), |
|
|
| getVaultActivity: protectedProcedure |
| .input(getVaultActivitySchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const vaultActivity = await getRecentDocuments(db, { |
| teamId: teamId!, |
| limit: input.limit, |
| }); |
|
|
| return { |
| result: vaultActivity, |
| }; |
| }), |
|
|
| getAccountBalances: protectedProcedure |
| .input(getAccountBalancesSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const accountBalances = await getCashBalance(db, { |
| teamId: teamId!, |
| currency: input.currency, |
| }); |
|
|
| return { |
| result: accountBalances, |
| }; |
| }), |
|
|
| getNetPosition: protectedProcedure |
| .input(getNetPositionSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const netPosition = await getNetPosition(db, { |
| teamId: teamId!, |
| currency: input.currency, |
| }); |
|
|
| return { |
| result: netPosition, |
| }; |
| }), |
|
|
| getMonthlySpending: protectedProcedure |
| .input(getMonthlySpendingSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const spending = await getSpendingForPeriod(db, { |
| teamId: teamId!, |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| }); |
|
|
| return { |
| result: spending, |
| toolCall: { |
| toolName: "getSpendingAnalysis", |
| toolParams: { |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| }, |
| }, |
| }; |
| }), |
|
|
| getRecurringExpenses: protectedProcedure |
| .input(getRecurringExpensesSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const recurringExpenses = await getRecurringExpenses(db, { |
| teamId: teamId!, |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| }); |
|
|
| return { |
| result: recurringExpenses, |
| }; |
| }), |
|
|
| getTaxSummary: protectedProcedure |
| .input(getTaxSummarySchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| |
| const [paidTaxes, collectedTaxes] = await Promise.all([ |
| getTaxSummary(db, { |
| teamId: teamId!, |
| type: "paid", |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| }), |
| getTaxSummary(db, { |
| teamId: teamId!, |
| type: "collected", |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| }), |
| ]); |
|
|
| return { |
| result: { |
| paid: paidTaxes.summary, |
| collected: collectedTaxes.summary, |
| currency: paidTaxes.summary.currency || input.currency || "USD", |
| }, |
| }; |
| }), |
|
|
| getCategoryExpenses: protectedProcedure |
| .input(getCategoryExpensesSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const categoryExpenses = await getSpending(db, { |
| teamId: teamId!, |
| from: input.from, |
| to: input.to, |
| currency: input.currency, |
| }); |
|
|
| |
| const topCategories = categoryExpenses |
| .sort((a, b) => b.amount - a.amount) |
| .slice(0, input.limit || 5); |
|
|
| const totalAmount = topCategories.reduce( |
| (sum, cat) => sum + cat.amount, |
| 0, |
| ); |
|
|
| return { |
| result: { |
| categories: topCategories, |
| totalAmount, |
| currency: topCategories[0]?.currency || input.currency || "USD", |
| totalCategories: categoryExpenses.length, |
| }, |
| }; |
| }), |
|
|
| getOverdueInvoicesAlert: protectedProcedure |
| .input(getOverdueInvoicesAlertSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const overdueData = await getOverdueInvoicesAlert(db, { |
| teamId: teamId!, |
| currency: input?.currency, |
| }); |
|
|
| return { |
| result: overdueData.summary, |
| }; |
| }), |
|
|
| getBillableHours: protectedProcedure |
| .input(getBillableHoursSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| return getBillableHours(db, { |
| teamId: teamId!, |
| date: input.date, |
| view: input.view, |
| weekStartsOnMonday: input.weekStartsOnMonday, |
| }); |
| }), |
|
|
| getCustomerLifetimeValue: protectedProcedure |
| .input(getCustomerLifetimeValueSchema) |
| .query(async ({ ctx: { db, teamId }, input }) => { |
| const result = await getCustomerLifetimeValue(db, { |
| teamId: teamId!, |
| currency: input.currency, |
| }); |
|
|
| return { |
| result, |
| toolCall: { |
| toolName: "getCustomerLifetimeValue", |
| toolParams: { |
| currency: input.currency, |
| }, |
| }, |
| }; |
| }), |
|
|
| getWidgetPreferences: protectedProcedure.query( |
| async ({ ctx: { teamId, session } }) => { |
| const preferences = await widgetPreferencesCache.getWidgetPreferences( |
| teamId!, |
| session.user.id, |
| ); |
| return preferences; |
| }, |
| ), |
|
|
| updateWidgetPreferences: protectedProcedure |
| .input(updateWidgetPreferencesSchema) |
| .mutation(async ({ ctx: { teamId, session }, input }) => { |
| const preferences = await widgetPreferencesCache.updatePrimaryWidgets( |
| teamId!, |
| session.user.id, |
| input.primaryWidgets, |
| ); |
| return preferences; |
| }), |
| }); |
|
|