mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-15 07:28:39 +00:00
fix(banking): use custom renderer for translated strings and parser for formula evaluation
230 lines
8.9 KiB
TypeScript
230 lines
8.9 KiB
TypeScript
import { useAtomValue } from "jotai"
|
|
import { MissingFiltersBanner } from "./MissingFiltersBanner"
|
|
import { bankRecDateAtom, selectedBankAccountAtom } from "./bankRecAtoms"
|
|
import { useCurrentCompany } from "@/hooks/useCurrentCompany"
|
|
import type { ColumnDef } from "@tanstack/react-table"
|
|
import { useCallback, useMemo } from "react"
|
|
import { useFrappeGetCall, useFrappePostCall } 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 { getErrorMessage, slug } from "@/lib/frappe"
|
|
import { Button } from "@/components/ui/button"
|
|
import { toast } from "sonner"
|
|
import { PartyPopper } from "lucide-react"
|
|
import ErrorBanner from "@/components/ui/error-banner"
|
|
import _ from "@/lib/translate"
|
|
import { Empty, EmptyTitle, EmptyDescription, EmptyMedia, EmptyHeader } from "@/components/ui/empty"
|
|
import MarkdownRenderer from "@/components/ui/markdown"
|
|
|
|
const IncorrectlyClearedEntries = () => {
|
|
const companyID = useCurrentCompany()
|
|
const bankAccount = useAtomValue(selectedBankAccountAtom)
|
|
const dates = useAtomValue(bankRecDateAtom)
|
|
|
|
if (!companyID || !bankAccount || !dates) {
|
|
const missingFields = []
|
|
if (!companyID) {
|
|
missingFields.push('Company')
|
|
}
|
|
if (!bankAccount) {
|
|
missingFields.push('Bank Account')
|
|
}
|
|
if (!dates) {
|
|
missingFields.push('Dates')
|
|
}
|
|
return <MissingFiltersBanner text={`Please select ${missingFields.join(', ')} to view the incorrectly cleared entries.`} />
|
|
}
|
|
|
|
return <IncorrectlyClearedEntriesView />
|
|
}
|
|
|
|
interface IncorrectlyClearedEntry {
|
|
payment_document: string
|
|
payment_entry: string
|
|
debit: number
|
|
credit: number
|
|
posting_date: string,
|
|
clearance_date: string,
|
|
}
|
|
|
|
const IncorrectlyClearedEntriesView = () => {
|
|
|
|
const companyID = useCurrentCompany()
|
|
const bankAccount = useAtomValue(selectedBankAccountAtom)
|
|
const dates = useAtomValue(bankRecDateAtom)
|
|
|
|
const filters = useMemo(() => {
|
|
return JSON.stringify({
|
|
company: companyID,
|
|
account: bankAccount?.account,
|
|
report_date: dates.toDate
|
|
})
|
|
}, [companyID, bankAccount, dates])
|
|
|
|
const { data, error, mutate } = useFrappeGetCall<{ message: QueryReportReturnType<IncorrectlyClearedEntry> }>('frappe.desk.query_report.run', {
|
|
report_name: 'Cheques and Deposits Incorrectly cleared',
|
|
filters,
|
|
ignore_prepared_report: 1,
|
|
are_default_filters: false,
|
|
}, `Report-Cheques and Deposits Incorrectly cleared-${filters}`, { keepPreviousData: true, revalidateOnFocus: false }, 'POST')
|
|
|
|
const formattedToDate = formatDate(dates.toDate)
|
|
|
|
const { call: clearClearingDate } = useFrappePostCall('erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.clear_clearing_date')
|
|
|
|
const onClearClick = useCallback(
|
|
(voucher_type: string, voucher_name: string) => {
|
|
clearClearingDate({ voucher_type, voucher_name })
|
|
.then(() => {
|
|
toast.success(_("Cleared"), {
|
|
duration: 1000,
|
|
})
|
|
mutate()
|
|
})
|
|
.catch((e) => {
|
|
toast.error(_("There was an error while performing the action."), {
|
|
description: getErrorMessage(e),
|
|
duration: 5000,
|
|
})
|
|
})
|
|
},
|
|
[clearClearingDate, mutate],
|
|
)
|
|
|
|
const accountCurrency = useMemo(
|
|
() => bankAccount?.account_currency ?? getCompanyCurrency(companyID),
|
|
[bankAccount?.account_currency, companyID],
|
|
)
|
|
|
|
const incorrectlyClearedColumns = useMemo<ColumnDef<IncorrectlyClearedEntry, unknown>[]>(
|
|
() => [
|
|
{
|
|
accessorKey: "payment_document",
|
|
header: _("Document Type"),
|
|
size: 128,
|
|
cell: ({ row }) => _(row.original.payment_document),
|
|
},
|
|
{
|
|
id: "payment_entry",
|
|
header: _("Payment Document"),
|
|
size: 160,
|
|
meta: {
|
|
getTooltipText: (r) => {
|
|
const x = r as IncorrectlyClearedEntry
|
|
return [x.payment_document, x.payment_entry].filter(Boolean).join(" · ") || undefined
|
|
},
|
|
} satisfies ListViewColumnMeta,
|
|
cell: ({ row }) => (
|
|
<a
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="text-ink-gray-8 block min-w-0 w-full underline underline-offset-4"
|
|
href={`/desk/${slug(row.original.payment_document)}/${row.original.payment_entry}`}
|
|
>
|
|
{row.original.payment_entry}
|
|
</a>
|
|
),
|
|
},
|
|
{
|
|
accessorKey: "debit",
|
|
header: _("Debit"),
|
|
size: 120,
|
|
meta: { align: "right" } satisfies ListViewColumnMeta,
|
|
cell: ({ row }) => formatCurrency(row.original.debit, accountCurrency),
|
|
},
|
|
{
|
|
accessorKey: "credit",
|
|
header: _("Credit"),
|
|
size: 120,
|
|
meta: { align: "right" } satisfies ListViewColumnMeta,
|
|
cell: ({ row }) => formatCurrency(row.original.credit, accountCurrency),
|
|
},
|
|
{
|
|
accessorKey: "posting_date",
|
|
header: _("Posting Date"),
|
|
size: 118,
|
|
meta: { tabularNums: true } satisfies ListViewColumnMeta,
|
|
cell: ({ row }) => formatDate(row.original.posting_date),
|
|
},
|
|
{
|
|
accessorKey: "clearance_date",
|
|
header: _("Clearance Date"),
|
|
size: 118,
|
|
meta: { tabularNums: true } satisfies ListViewColumnMeta,
|
|
cell: ({ row }) => formatDate(row.original.clearance_date),
|
|
},
|
|
{
|
|
id: "actions",
|
|
header: _("Actions"),
|
|
size: 180,
|
|
enableResizing: false,
|
|
meta: { truncate: false, truncateTooltip: false } satisfies ListViewColumnMeta,
|
|
cell: ({ row }) => (
|
|
<Button
|
|
variant="link"
|
|
size="sm"
|
|
className="text-ink-red-3 px-0"
|
|
onClick={() => onClearClick(row.original.payment_document, row.original.payment_entry)}
|
|
>
|
|
{_("Reset Clearing Date")}
|
|
</Button>
|
|
),
|
|
},
|
|
],
|
|
[accountCurrency, onClearClick],
|
|
)
|
|
|
|
const content = _("This report shows all entries in the system where the <strong>clearance date is before the posting date</strong> which is incorrect.")
|
|
|
|
const entriesContent = _("Entries below have a posting date after {0} but the clearance date is before {1}.", [`<strong>${formattedToDate}</strong>`, `<strong>${formattedToDate}</strong>`])
|
|
|
|
return <div className="space-y-4 py-2">
|
|
|
|
<div>
|
|
<span className="text-p-sm">
|
|
<MarkdownRenderer content={content} />
|
|
<br />
|
|
{data && data.message.result.length > 0 && <span>
|
|
<MarkdownRenderer content={entriesContent} />
|
|
<br />
|
|
{_("You can reset the clearing dates of these entries here.")}
|
|
</span>}
|
|
</span>
|
|
</div>
|
|
|
|
{error && <ErrorBanner error={error} />}
|
|
|
|
{data && data.message.result.length > 0 && (
|
|
<div className="space-y-2">
|
|
<p className="text-ink-gray-5 text-sm">{_("Incorrectly cleared entries as per the report.")}</p>
|
|
<ListView
|
|
data={data.message.result}
|
|
columns={incorrectlyClearedColumns}
|
|
getRowId={(row) => `${row.payment_entry}-${row.posting_date}`}
|
|
maxHeight="min(70vh, 640px)"
|
|
emptyState={_("No rows to display.")}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{data && data.message.result.length === 0 &&
|
|
<Empty>
|
|
<EmptyMedia>
|
|
<PartyPopper />
|
|
</EmptyMedia>
|
|
<EmptyHeader>
|
|
<EmptyTitle>{_("It's all good!")}</EmptyTitle>
|
|
<EmptyDescription>{_("There are no entries in the system where the clearance date is before the posting date.")}</EmptyDescription>
|
|
</EmptyHeader>
|
|
</Empty>
|
|
}
|
|
|
|
|
|
</div>
|
|
}
|
|
|
|
export default IncorrectlyClearedEntries
|