diff --git a/src/pages/accounting/AccountingChartOfAccountsPage.tsx b/src/pages/accounting/AccountingChartOfAccountsPage.tsx index 0eb120c..eaf2df7 100644 --- a/src/pages/accounting/AccountingChartOfAccountsPage.tsx +++ b/src/pages/accounting/AccountingChartOfAccountsPage.tsx @@ -20,7 +20,7 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; -import { Plus, Trash2, CalendarIcon, Lock, Pencil, RotateCcw, Save, Info, Loader2, Upload, X, Archive, ArchiveRestore } from "lucide-react"; +import { Plus, Trash2, CalendarIcon, Lock, Pencil, RotateCcw, Save, Info, Loader2, Upload, X, Archive, ArchiveRestore, Combine } from "lucide-react"; import { Switch } from "@/components/ui/switch"; import { toast } from "sonner"; import { money } from "./lib/format"; @@ -58,6 +58,11 @@ export default function AccountingChartOfAccountsPage() { const [showArchived, setShowArchived] = useState(false); const [deleteTarget, setDeleteTarget] = useState(null); + // ── Merge state ── + const [mergeSource, setMergeSource] = useState(null); + const [mergeTargetId, setMergeTargetId] = useState(""); + const [merging, setMerging] = useState(false); + // ── Bulk selection / edit state ── const [selectedIds, setSelectedIds] = useState>(new Set()); const [bulkEditOpen, setBulkEditOpen] = useState(false); @@ -235,6 +240,26 @@ export default function AccountingChartOfAccountsPage() { qc.invalidateQueries({ queryKey: ["accounts", cid] }); }; + // Merge `mergeSource` into the chosen target: all GL lines, bills, deposits, + // budgets, child accounts, etc. move to the target, then the source is deleted. + const runMerge = async () => { + if (!mergeSource || !mergeTargetId) return; + setMerging(true); + try { + const { data, error } = await (accounting as any).rpc("merge_accounts", { + _source: mergeSource.id, + _target: mergeTargetId, + }); + if (error) return toast.error(error.message); + const moved = data?.journal_lines_moved ?? 0; + toast.success(`Merged "${data?.merged ?? mergeSource.name}" into "${data?.into ?? ""}"${moved ? ` — ${moved} GL line(s) moved` : ""}`); + setMergeSource(null); setMergeTargetId(""); + qc.invalidateQueries({ queryKey: ["accounts", cid] }); + } finally { + setMerging(false); + } + }; + const setArchived = async (ids: string[], archived: boolean) => { const { error } = await accounting.from("accounts").update({ is_archived: archived }).in("id", ids); if (error) return toast.error(error.message); @@ -603,6 +628,42 @@ export default function AccountingChartOfAccountsPage() { + {/* ── Merge accounts ── */} + { if (!v) { setMergeSource(null); setMergeTargetId(""); } }}> + + + Merge account + +
+

+ Move all history from{" "} + {mergeSource?.code ? `${mergeSource.code} · ` : ""}{mergeSource?.name}{" "} + into another account, then delete it. Every journal entry, bill, deposit, budget line and child account moves over. This can't be undone. +

+
+ + +

Only active {mergeSource?.type} accounts are eligible (same type).

+
+
+ + + + +
+
+ {/* ── Bulk delete confirm ── */} @@ -730,6 +791,9 @@ export default function AccountingChartOfAccountsPage() { )} +