import { useAtomValue } from "jotai" import { MissingFiltersBanner } from "./MissingFiltersBanner" import { bankRecDateAtom, selectedBankAccountAtom } from "./bankRecAtoms" import { useCurrentCompany } from "@/hooks/useCurrentCompany" import { useCallback, useMemo } from "react" import type { ColumnDef } from "@tanstack/react-table" import { useFrappeGetCall } from "frappe-react-sdk" import { QueryReportReturnType } from "@/types/custom/Reports" import { formatDate } from "@/lib/date" import { ListView, type ListViewColumnMeta } from "@/components/ui/list-view" import { formatCurrency } from "@/lib/numbers" import { getCompanyCurrency } from "@/lib/company" import { slug } from "@/lib/frappe" import { ScrollTextIcon } from "lucide-react" import ErrorBanner from "@/components/ui/error-banner" import { StatContainer, StatLabel, StatValue } from "@/components/ui/stats" import _ from "@/lib/translate" import { toast } from "sonner" import { useCopyToClipboard } from "usehooks-ts" import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty" import MarkdownRenderer from "@/components/ui/markdown" const BankReconciliationStatement = () => { const bankAccount = useAtomValue(selectedBankAccountAtom) const dates = useAtomValue(bankRecDateAtom) if (!bankAccount) { return } if (!dates) { return } return } interface BankClearanceSummaryEntry { payment_document: string payment_entry: string posting_date: string, reference_no: string, credit: number, debit: number, against_account: string, ref_date: string, account_currency: string, clearance_date: string } const BankReconciliationStatementView = () => { const companyID = useCurrentCompany() const bankAccount = useAtomValue(selectedBankAccountAtom) const dates = useAtomValue(bankRecDateAtom) const filters = useMemo(() => { return JSON.stringify({ account: bankAccount?.account, report_date: dates.toDate, company: companyID }) }, [bankAccount, dates, companyID]) const { data, error } = useFrappeGetCall<{ message: QueryReportReturnType }>('frappe.desk.query_report.run', { report_name: 'Bank Reconciliation Statement', filters, ignore_prepared_report: 1, are_default_filters: false, }, `Report-Bank Reconciliation Statement-${filters}`, { keepPreviousData: true, revalidateOnFocus: false }, 'POST') const [, copyToClipboard] = useCopyToClipboard() const onCopy = useCallback( (text: string) => { copyToClipboard(text).then(() => { toast.success(_("Copied to clipboard")) }) }, [copyToClipboard], ) const statementColumns = useMemo[]>( () => [ { accessorKey: "posting_date", header: _("Posting Date"), size: 118, meta: { tabularNums: true } satisfies ListViewColumnMeta, cell: ({ row }) => formatDate(row.original.posting_date), }, { accessorKey: "payment_document", header: _("Document Type"), size: 140, cell: ({ row }) => _(row.original.payment_document), }, { id: "payment_entry", header: _("Payment Document"), size: 300, meta: { getTooltipText: (r) => { const x = r as BankClearanceSummaryEntry const parts = [x.payment_document, x.payment_entry].filter(Boolean) return parts.length ? parts.join(" ยท ") : undefined }, } satisfies ListViewColumnMeta, cell: ({ row }) => { const { payment_document, payment_entry } = row.original return payment_document ? ( {payment_entry} ) : ( payment_entry ) }, }, { accessorKey: "debit", header: _("Debit"), size: 112, meta: { align: "right" } satisfies ListViewColumnMeta, cell: ({ row }) => {formatCurrency(row.original.debit, row.original.account_currency)}, }, { accessorKey: "credit", header: _("Credit"), size: 112, meta: { align: "right" } satisfies ListViewColumnMeta, cell: ({ row }) => {formatCurrency(row.original.credit, row.original.account_currency)}, }, { accessorKey: "against_account", header: _("Against Account"), meta: { gridWidth: "minmax(0,1.25fr)" } satisfies ListViewColumnMeta, cell: ({ row }) => ( {row.original.against_account} ), }, { accessorKey: "reference_no", header: _("Reference #"), cell: ({ row }) => { const ref = row.original.reference_no return ( ) }, }, { accessorKey: "ref_date", header: _("Reference Date"), size: 118, meta: { tabularNums: true } satisfies ListViewColumnMeta, cell: ({ row }) => formatDate(row.original.ref_date), }, { accessorKey: "clearance_date", header: _("Clearance Date"), size: 118, meta: { tabularNums: true } satisfies ListViewColumnMeta, cell: ({ row }) => formatDate(row.original.clearance_date), }, ], [onCopy], ) const statementRows = useMemo(() => { if (!data?.message.result) return [] return data.message.result.filter((row: BankClearanceSummaryEntry) => Boolean(row.payment_entry)) }, [data]) const content = _("Below is a list of all entries posted against the bank account {0} which have not been cleared till {1}.", [`${bankAccount?.account}`, `${formatDate(dates.toDate)}`]) return
{error && } {data && } {data && data.message.result.length > 0 && (

{_("Bank Reconciliation Statement")}

row.payment_entry} maxHeight="min(70vh, 640px)" emptyState={_("No entries with a payment document in this list.")} />
)} {data && data.message.result.length === 0 && {_("No entries found")} {_("There are no accounting entries in the system for the selected account and dates.")} }
} const SummarySection = ({ data }: { data: { message: QueryReportReturnType } }) => { const company = useCurrentCompany() const bankAccount = useAtomValue(selectedBankAccountAtom) const { bankStatementBalanceAsPerGL, outstandingChecksDebit, outstandingChecksCredit, incorrectlyClearedEntriesDebit, incorrectlyClearedEntriesCredit, calculatedBankStatementBalance } = useMemo(() => { // Loop over the results and find the corresponding rows let bankStatementBalanceAsPerGL = 0 let outstandingChecksDebit = 0 let outstandingChecksCredit = 0 let incorrectlyClearedEntriesDebit = 0 let incorrectlyClearedEntriesCredit = 0 let calculatedBankStatementBalance = 0 // eslint-disable-next-line @typescript-eslint/no-explicit-any data?.message.result.forEach((r: any) => { if (r.payment_entry === 'Bank Statement balance as per General Ledger') { bankStatementBalanceAsPerGL = r.debit - r.credit } if (r.payment_entry === 'Outstanding Checks and Deposits to clear') { outstandingChecksDebit = r.debit outstandingChecksCredit = r.credit } if (r.payment_entry === 'Checks and Deposits incorrectly cleared') { incorrectlyClearedEntriesDebit = r.debit incorrectlyClearedEntriesCredit = r.credit } if (r.payment_entry === 'Calculated Bank Statement balance') { calculatedBankStatementBalance = r.debit - r.credit } }) return { bankStatementBalanceAsPerGL, outstandingChecksDebit, outstandingChecksCredit, incorrectlyClearedEntriesDebit, incorrectlyClearedEntriesCredit, calculatedBankStatementBalance } }, [data]) const currency = bankAccount?.account_currency ?? getCompanyCurrency(company) return
{_("Bank Statement Balance as per General Ledger")} {formatCurrency(bankStatementBalanceAsPerGL, currency)} {_("Outstanding Checks and Deposits to clear")} {formatCurrency(outstandingChecksDebit - outstandingChecksCredit, currency)} {(incorrectlyClearedEntriesDebit > 0 || incorrectlyClearedEntriesCredit > 0) && {_("Checks and Deposits incorrectly cleared")} {formatCurrency(incorrectlyClearedEntriesDebit - incorrectlyClearedEntriesCredit)} {/*
}> {incorrectlyClearedEntriesDebit !== 0 && Debit: {formatCurrency(incorrectlyClearedEntriesDebit)}} {incorrectlyClearedEntriesCredit !== 0 && Credit: {formatCurrency(incorrectlyClearedEntriesCredit)}}
*/}
} {_("Calculated Bank Statement Balance")} {formatCurrency(calculatedBankStatementBalance)}
} export default BankReconciliationStatement