mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 01:40:01 +00:00
Accounting platform: remove Zoho, unify reports, board access, vendor sharing
- Remove the Zoho Books integration (edge functions, sync libs, settings, reports/overview, banking links, fees tab, import dialog); preserve fee rules as a standalone FeesTab and the COA accounting_system classification. - Financial Overview/Reports (staff + board) render the Accounting dashboard and reports; board reports mirror the rich Accounting Reports. - New Reserve Fund Schedule report + an is_reserve flag on accounts. - Unify all report exports to a branded format (logo + centered header + footer): shared ReportSheet (on-screen) and reportHeader (PDF). Budget vs Actuals and Bank Reconciliation PDFs now match the reference layout. - Render financial reports inline (no preview pop-up). - Budget Management mirrors Accounting Budgeting (staff-accessible) with SPA navigation; editable bills in the Accounting Bills page. - Negative opening balances flow through to the GL and reports (allow negative input; keep non-zero on save; signed CSV import). - Upload a per-account trial balance via CSV on Opening Balances. - Board members: read-only RLS access to their association's accounting ledger; editable board-members panel on the association page; share vendor contacts with the board (toggle + directory section). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
import { useState, useEffect, useRef, useCallback } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
import { pushBillToZohoAfterCreate, pushBillPaymentToZohoAfterPay } from "@/lib/zohoBillSync";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { UserCheck, Plus, Search, Eye, Upload, X, ArrowUpDown, Edit, Trash2, MoreHorizontal, AlertTriangle, Loader2, RefreshCw, ArrowUpFromLine, Bell, Printer, Sparkles, Download } from "lucide-react";
|
||||
import { UserCheck, Plus, Search, Eye, Upload, X, ArrowUpDown, Edit, Trash2, MoreHorizontal, AlertTriangle, Loader2, Bell, Printer, Sparkles, Download } from "lucide-react";
|
||||
import { downloadChecksPdf, type CheckData } from "@/utils/checkPdfGenerator";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
@@ -66,9 +65,6 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
|
||||
const [editingBill, setEditingBill] = useState<any>(null);
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const [deleteTarget, setDeleteTarget] = useState<any>(null);
|
||||
const [syncingId, setSyncingId] = useState<string | null>(null);
|
||||
const [syncingAll, setSyncingAll] = useState(false);
|
||||
const [resyncConfirm, setResyncConfirm] = useState<{ type: "single" | "all"; id?: string } | null>(null);
|
||||
|
||||
// Notify Board
|
||||
const [notifyOpen, setNotifyOpen] = useState(false);
|
||||
@@ -451,16 +447,6 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
|
||||
}).select("id").single();
|
||||
|
||||
if (error) throw error;
|
||||
if (newBill?.id) {
|
||||
const syncResult = await pushBillToZohoAfterCreate(newBill.id);
|
||||
if (!syncResult?.success) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Zoho sync failed",
|
||||
description: syncResult?.error || "Bill was created, but it could not be pushed to Zoho Books.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If board members were selected for approval, create approval requests for each
|
||||
if (form.approval_member_ids.length > 0) {
|
||||
@@ -579,15 +565,7 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
|
||||
await supabase.from("invoices").update({ raw_pdf_url: attachmentUrl }).eq("id", editingBill.source_invoice_id);
|
||||
}
|
||||
|
||||
const syncResult = await pushBillToZohoAfterCreate(editingBill.id);
|
||||
toast({ title: "Bill Updated", description: "Bill changes have been saved." });
|
||||
if (!syncResult?.success) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Zoho sync failed",
|
||||
description: syncResult?.error || "Bill was updated, but it could not be pushed to Zoho Books.",
|
||||
});
|
||||
}
|
||||
setEditOpen(false);
|
||||
setEditingBill(null);
|
||||
setUploadFile(null);
|
||||
@@ -618,52 +596,6 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
|
||||
const getClientName = (bill: any) => bill.associations?.name || "—";
|
||||
const getGlLabel = (bill: any) => bill.chart_of_accounts ? `${bill.chart_of_accounts.account_number} - ${bill.chart_of_accounts.account_name}` : "—";
|
||||
|
||||
const handlePushToZoho = async (billId: string) => {
|
||||
setSyncingId(billId);
|
||||
try {
|
||||
const syncResult = await pushBillToZohoAfterCreate(billId);
|
||||
if (syncResult?.success) {
|
||||
toast({ title: "Zoho sync complete", description: "Bill was sent to Zoho Books." });
|
||||
fetchData();
|
||||
return;
|
||||
}
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Zoho sync failed",
|
||||
description: syncResult?.error || "This bill could not be pushed to Zoho Books.",
|
||||
});
|
||||
} finally {
|
||||
setSyncingId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const pushAllToZoho = async (force = false) => {
|
||||
setSyncingAll(true);
|
||||
try {
|
||||
const { data, error } = await supabase.functions.invoke("zoho-books", {
|
||||
body: { action: "push_all_bills", params: { force } },
|
||||
});
|
||||
if (error) throw error;
|
||||
const r = data?.data;
|
||||
toast({ title: "Zoho Sync Complete", description: `${r?.synced || 0} synced, ${r?.errors || 0} errors out of ${r?.total || 0}.` });
|
||||
fetchData();
|
||||
} catch (err: any) {
|
||||
toast({ title: "Zoho sync failed", description: err.message, variant: "destructive" });
|
||||
} finally {
|
||||
setSyncingAll(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResyncConfirm = () => {
|
||||
if (!resyncConfirm) return;
|
||||
if (resyncConfirm.type === "single") {
|
||||
handlePushToZoho(resyncConfirm.id!);
|
||||
} else {
|
||||
pushAllToZoho(true);
|
||||
}
|
||||
setResyncConfirm(null);
|
||||
};
|
||||
|
||||
const getStatusDisplay = (bill: any) => {
|
||||
// Check overdue
|
||||
if (bill.status === "pending" && bill.due_date && new Date(bill.due_date) < new Date()) {
|
||||
@@ -932,10 +864,6 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
|
||||
check_id: u.checkId,
|
||||
})
|
||||
.eq("id", u.billId);
|
||||
// Push payment to Zoho Books (records vendor payment + check info on the bill)
|
||||
pushBillPaymentToZohoAfterPay(u.billId).then((r) => {
|
||||
if (!r.success) console.warn("Zoho payment sync failed for bill", u.billId, r.error);
|
||||
});
|
||||
}
|
||||
|
||||
if (txInserts.length > 0) await supabase.from("bank_transactions").insert(txInserts);
|
||||
@@ -985,14 +913,6 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
|
||||
<Button onClick={openNotifyBoard} variant="outline" size="sm" className="gap-2">
|
||||
<Bell className="w-4 h-4" /> Notify Board
|
||||
</Button>
|
||||
<Button onClick={() => setResyncConfirm({ type: "all" })} disabled={syncingAll} variant="outline" size="sm">
|
||||
{syncingAll ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <RefreshCw className="w-4 h-4 mr-2" />}
|
||||
Resync All to Zoho
|
||||
</Button>
|
||||
<Button onClick={() => pushAllToZoho(false)} disabled={syncingAll} variant="outline" size="sm">
|
||||
{syncingAll ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <ArrowUpFromLine className="w-4 h-4 mr-2" />}
|
||||
Sync All to Zoho
|
||||
</Button>
|
||||
<Button
|
||||
onClick={openPrintDialog}
|
||||
disabled={selectedBillIds.size === 0 || printing}
|
||||
@@ -1196,9 +1116,6 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
|
||||
<DropdownMenuItem onClick={() => openEdit(b)}>
|
||||
<Edit className="h-4 w-4 mr-2" /> Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handlePushToZoho(b.id)}>
|
||||
<Upload className="h-4 w-4 mr-2" /> Push to Zoho Books
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-destructive" onClick={() => setDeleteTarget(b)}>
|
||||
<Trash2 className="h-4 w-4 mr-2" /> Delete
|
||||
</DropdownMenuItem>
|
||||
@@ -1433,13 +1350,6 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
|
||||
{detailBill.description && (
|
||||
<div><p className="text-xs text-muted-foreground uppercase">Description</p><p className="text-sm">{detailBill.description}</p></div>
|
||||
)}
|
||||
{!isBoardView && (
|
||||
<div>
|
||||
<Button variant="outline" onClick={() => handlePushToZoho(detailBill.id)} className="gap-2">
|
||||
<Upload className="h-4 w-4" /> Push to Zoho Books
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{/* Embedded PDF / Attachment Preview */}
|
||||
{detailBill.attachment_url && (
|
||||
<div className="border rounded-lg overflow-hidden">
|
||||
@@ -1617,24 +1527,6 @@ export default function BillApprovalsPage({ boardAssociationIds }: { boardAssoci
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Resync Confirmation */}
|
||||
<AlertDialog open={!!resyncConfirm} onOpenChange={(open) => { if (!open) setResyncConfirm(null); }}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Resync to Zoho Books?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{resyncConfirm?.type === "all"
|
||||
? "This will re-push all bills to Zoho Books, including ones already synced. Existing Zoho records will be overwritten. This action cannot be undone."
|
||||
: "This will re-push this bill to Zoho Books, overwriting the existing Zoho record. This action cannot be undone."}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleResyncConfirm}>Confirm Resync</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
{/* Notify Board Dialog */}
|
||||
<Dialog open={notifyOpen} onOpenChange={setNotifyOpen}>
|
||||
<DialogContent>
|
||||
|
||||
Reference in New Issue
Block a user