"use server"; import { createAdminClient } from "@/lib/supabase/server"; import { auth } from "@clerk/nextjs/server"; 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; } export interface BankTransaction { id: string; organisation_id: string; account_name: string; date: string; description: string; amount: number; type: "credit" | "debit"; category: string | null; reconciled: boolean; created_at: string; } /** * Parse CSV bank transactions and insert them. * Expected CSV columns: date, description, amount (positive = credit, negative = debit) */ export async function importBankTransactions( csvContent: string, accountName: string ): Promise<{ success: boolean; count?: number; error?: string }> { const { userId } = await auth(); if (!userId) return { success: false, error: "Unauthenticated" }; const orgId = await getOrganisationId(); const supabase = await createAdminClient(); // Parse CSV const lines = csvContent.trim().split("\n"); if (lines.length < 2) return { success: false, error: "CSV must have a header row and at least one data row" }; // Detect delimiter const delimiter = lines[0].includes("\t") ? "\t" : ","; const headers = lines[0].split(delimiter).map((h) => h.trim().toLowerCase()); const dateIdx = headers.findIndex((h) => h.includes("date")); const descIdx = headers.findIndex((h) => h.includes("desc") || h.includes("narrative") || h.includes("memo")); const amtIdx = headers.findIndex((h) => h.includes("amount") || h.includes("value") || h.includes("debit") || h.includes("credit")); if (dateIdx === -1 || descIdx === -1 || amtIdx === -1) { return { success: false, error: "CSV must have columns for date, description, and amount" }; } const records = []; for (let i = 1; i < lines.length; i++) { const cols = lines[i].split(delimiter).map((c) => c.trim().replace(/^"|"$/g, "")); if (cols.length < headers.length) continue; const dateStr = cols[dateIdx]; const description = cols[descIdx]; const amountStr = cols[amtIdx].replace(/[^0-9.\-]/g, ""); const amount = parseFloat(amountStr); if (isNaN(amount) || !dateStr) continue; // Parse date - try multiple formats let parsedDate: string | null = null; try { // Try ISO format first const d = new Date(dateStr + "T00:00:00"); if (!isNaN(d.getTime())) { parsedDate = d.toISOString().split("T")[0]; } else { // Try DD/MM/YYYY const parts = dateStr.split(/[\/\-\.]/); if (parts.length === 3) { const dd = parts[0].padStart(2, "0"); const mm = parts[1].padStart(2, "0"); const yyyy = parts[2].length === 2 ? `20${parts[2]}` : parts[2]; const d2 = new Date(`${yyyy}-${mm}-${dd}T00:00:00`); if (!isNaN(d2.getTime())) parsedDate = d2.toISOString().split("T")[0]; } } } catch { /* skip invalid dates */ } if (!parsedDate) continue; records.push({ organisation_id: orgId, account_name: accountName, date: parsedDate, description, amount, type: amount >= 0 ? "credit" : "debit", reconciled: false, }); } if (records.length === 0) return { success: false, error: "No valid transactions found in CSV" }; const { error } = await supabase.from("bank_transactions").insert(records); if (error) return { success: false, error: error.message }; return { success: true, count: records.length }; } export async function fetchBankTransactions(): Promise { const orgId = await getOrganisationId(); const supabase = await createAdminClient(); const { data, error } = await supabase .from("bank_transactions") .select("*") .eq("organisation_id", orgId) .order("date", { ascending: false }); if (error) throw error; return (data || []).map((t: any) => ({ id: t.id, organisation_id: t.organisation_id, account_name: t.account_name, date: t.date, description: t.description, amount: Number(t.amount), type: t.type, category: t.category, reconciled: t.reconciled, created_at: t.created_at, })); } export async function reconcileTransaction(transactionId: string): Promise<{ success: boolean; error?: string }> { const orgId = await getOrganisationId(); const supabase = await createAdminClient(); const { error } = await supabase .from("bank_transactions") .update({ reconciled: true }) .eq("id", transactionId) .eq("organisation_id", orgId); if (error) return { success: false, error: error.message }; return { success: true }; }