"use server"; import { createAdminClient } from "@/lib/supabase/server"; import { auth } from "@clerk/nextjs/server"; import { analyzePayrollAnomalies, analyzeLeavePatterns, generateInvoiceChaseEmails, generateNaturalLanguageReport, type PayslipDataForAI, type PayrollAnomalyResult, type LeaveRequestForAI, type LeavePatternResult, type OverdueInvoiceForAI, type InvoiceChaseDraft, type NLReportResult, } from "@/lib/gemini"; import { checkUsage, checkAndIncrement, type FeatureKey } from "@/lib/usage"; // ── Helpers ────────────────────────────────────────────────── async function getOrganisationId(): Promise { const { userId } = await auth(); if (!userId) throw new Error("Unauthenticated"); const supabase = await createAdminClient(); const { data: member, error } = await supabase .from("organisation_members") .select("organisation_id") .eq("user_id", userId) .single(); if (error || !member) throw new Error("No organisation found"); return member.organisation_id; } async function checkAiUsage(orgId: string): Promise<{ allowed: boolean; current: number; limit: number }> { const check = await checkUsage(orgId, "ai_queries"); return { allowed: check.allowed, current: check.current, limit: check.limit }; } // ── Payroll Anomaly Analysis ───────────────────────────────── export async function runPayrollAnomalyAnalysis( payslips: PayslipDataForAI[], period: string ): Promise<{ success: boolean; result?: PayrollAnomalyResult; error?: string; blocked?: boolean; current?: number; limit?: number }> { const { userId } = await auth(); if (!userId) return { success: false, error: "Unauthenticated" }; const orgId = await getOrganisationId(); // Check usage const usage = await checkAiUsage(orgId); if (!usage.allowed) { return { success: false, blocked: true, current: usage.current, limit: usage.limit }; } const result = await analyzePayrollAnomalies(payslips, period); // Increment usage await checkAndIncrement(orgId, "ai_queries"); return { success: true, result }; } // ── Leave Pattern Analysis ─────────────────────────────────── export async function runLeavePatternAnalysis( leaveRequests: LeaveRequestForAI[], monthLabel: string ): Promise<{ success: boolean; result?: LeavePatternResult; error?: string; blocked?: boolean; current?: number; limit?: number }> { const { userId } = await auth(); if (!userId) return { success: false, error: "Unauthenticated" }; const orgId = await getOrganisationId(); // Check usage const usage = await checkAiUsage(orgId); if (!usage.allowed) { return { success: false, blocked: true, current: usage.current, limit: usage.limit } as any; } const result = await analyzeLeavePatterns(leaveRequests, monthLabel); // Increment usage await checkAndIncrement(orgId, "ai_queries"); return { success: true, result }; } // ── Invoice Chase Email Generation ─────────────────────────── export async function generateInvoiceChaseEmailsAction( invoiceIds: string[], orgName: string ): Promise<{ success: boolean; drafts?: InvoiceChaseDraft[]; error?: string; blocked?: boolean; current?: number; limit?: number }> { const { userId } = await auth(); if (!userId) return { success: false, error: "Unauthenticated" }; const orgId = await getOrganisationId(); const supabase = await createAdminClient(); // Fetch overdue invoices const { data: invoices, error: fetchError } = await supabase .from("invoices") .select("invoice_number, client_name, client_email, total_cents, due_date") .eq("organisation_id", orgId) .in("id", invoiceIds) .eq("status", "overdue"); if (fetchError) return { success: false, error: fetchError.message }; if (!invoices || invoices.length === 0) return { success: false, error: "No overdue invoices found" }; // Check usage const usage = await checkAiUsage(orgId); if (!usage.allowed) { return { success: false, blocked: true, current: usage.current, limit: usage.limit }; } // Calculate days overdue const now = new Date(); const invoicesForAI: OverdueInvoiceForAI[] = invoices.map((inv) => { const dueDate = new Date(inv.due_date + "T00:00:00"); const daysOverdue = Math.floor((now.getTime() - dueDate.getTime()) / (1000 * 60 * 60 * 24)); return { invoice_number: inv.invoice_number, client_name: inv.client_name, client_email: inv.client_email, total_cents: inv.total_cents, due_date: inv.due_date, days_overdue: Math.max(0, daysOverdue), }; }); const drafts = await generateInvoiceChaseEmails(invoicesForAI, orgName); // Increment usage await checkAndIncrement(orgId, "ai_queries"); return { success: true, drafts }; } export async function fetchOverdueInvoices(): Promise<{ success: boolean; invoices?: { id: string; invoice_number: string; client_name: string; total_cents: number; due_date: string }[]; error?: string; }> { const { userId } = await auth(); if (!userId) return { success: false, error: "Unauthenticated" }; const orgId = await getOrganisationId(); const supabase = await createAdminClient(); const { data, error } = await supabase .from("invoices") .select("id, invoice_number, client_name, total_cents, due_date") .eq("organisation_id", orgId) .eq("status", "overdue") .order("due_date", { ascending: true }); if (error) return { success: false, error: error.message }; return { success: true, invoices: data || [] }; } // ── Natural Language Reports ───────────────────────────────── export async function runNaturalLanguageReport( query: string ): Promise<{ success: boolean; result?: NLReportResult; error?: string; blocked?: boolean; current?: number; limit?: number }> { const { userId } = await auth(); if (!userId) return { success: false, error: "Unauthenticated" }; const orgId = await getOrganisationId(); const supabase = await createAdminClient(); // Check usage const usage = await checkAiUsage(orgId); if (!usage.allowed) { return { success: false, blocked: true, current: usage.current, limit: usage.limit }; } // Gather context data const [ { count: employeeCount }, { count: activeLeaveRequests }, { count: pendingInvoices }, { count: overdueInvoices }, { data: paidInvoices }, { count: recentPayrollRuns }, ] = await Promise.all([ supabase.from("employees").select("id", { count: "exact", head: true }).eq("organisation_id", orgId).eq("status", "active"), supabase.from("leave_requests").select("id", { count: "exact", head: true }).eq("organisation_id", orgId).in("status", ["pending", "approved"]), supabase.from("invoices").select("id", { count: "exact", head: true }).eq("organisation_id", orgId).in("status", ["draft", "sent"]), supabase.from("invoices").select("id", { count: "exact", head: true }).eq("organisation_id", orgId).eq("status", "overdue"), supabase.from("invoices").select("total_cents").eq("organisation_id", orgId).eq("status", "paid"), supabase.from("payroll_runs").select("id", { count: "exact", head: true }).eq("organisation_id", orgId).gte("created_at", new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()), ]); const totalRevenueCents = (paidInvoices || []).reduce((sum, inv) => sum + (inv.total_cents || 0), 0); const result = await generateNaturalLanguageReport(query, { employeeCount: employeeCount || 0, activeLeaveRequests: activeLeaveRequests || 0, pendingInvoices: pendingInvoices || 0, overdueInvoices: overdueInvoices || 0, totalRevenue: totalRevenueCents / 100, recentPayrollRuns: recentPayrollRuns || 0, }); // Increment usage await checkAndIncrement(orgId, "ai_queries"); return { success: true, result }; } // ── AI Permission Suggestion ───────────────────────────────── import { suggestPermissionsForRole, type AIPermissionSuggestionResult } from "@/lib/gemini"; export async function suggestPermissionsAction( roleName: string, additionalContext?: string ): Promise<{ success: boolean; result?: AIPermissionSuggestionResult; error?: string; blocked?: boolean; current?: number; limit?: number }> { const { userId } = await auth(); if (!userId) return { success: false, error: "Unauthenticated" }; const orgId = await getOrganisationId(); // Check usage const usage = await checkAiUsage(orgId); if (!usage.allowed) { return { success: false, blocked: true, current: usage.current, limit: usage.limit }; } const result = await suggestPermissionsForRole(roleName, additionalContext); // Don't increment usage for suggestions (lightweight operation) return { success: true, result }; }