{
// If the input ends with a decimal or a decimal with trailing zeroes, store the string since we need the user to be able to type the decimals.
// When the user eventually types the decimals or blurs out, the value is formatted anyway.
// Otherwise store the float value
// Check if the value ends with a decimal or a decimal with trailing zeroes
const isDecimal = v?.endsWith(decimalSeparator) || v?.endsWith(decimalSeparator + '0')
const newValue = isDecimal ? v : values?.float ?? ''
const nextAmountFilter = {
value: Number(newValue),
stringValue: newValue
}
const hasAmountFilterChanged = amountFilter.value !== nextAmountFilter.value || amountFilter.stringValue !== nextAmountFilter.stringValue
setAmountFilter(nextAmountFilter)
// `onValueChange` also fires on blur; avoid clearing selected transaction unless filter value actually changed.
if (hasAmountFilterChanged) {
onFilterChange()
}
}}
// @ts-expect-error - CurrencyInputProps doesn't have a variant prop but Input does
variant={"outline"}
customInput={Input}
/>
{_("Record a journal entry for expenses, income or split transactions")}
B
{_("Record a payment entry against a customer or supplier")}
P
{_("Record an internal transfer to another bank/credit card/cash account")}
I
{_("Record a payment entry against a customer or supplier")}
P
{_("Record a journal entry for expenses, income or split transactions")}
B
{_("Record an internal transfer to another bank/credit card/cash account")}
I
{transaction.matched_transaction_rule && }
}
const RuleAction = ({ transaction }: { transaction: UnreconciledTransaction }) => {
const { data: rule } = useGetRuleForTransaction(transaction)
const setTransferModalOpen = useSetAtom(bankRecTransferModalAtom)
const setRecordPaymentModalOpen = useSetAtom(bankRecRecordPaymentModalAtom)
const setRecordJournalEntryModalOpen = useSetAtom(bankRecRecordJournalEntryModalAtom)
const getActionIcon = () => {
if (!rule) return null
switch (rule.classify_as) {
case "Bank Entry":
return
case "Payment Entry":
return
case "Transfer":
return
default:
return
}
}
const getActionStyles = () => {
if (!rule) return {}
switch (rule.classify_as) {
case "Bank Entry":
return {
border: "border-outline-blue-3",
bg: "bg-surface-blue-1/50",
text: "text-ink-blue-4",
theme: "blue",
}
case "Payment Entry":
return {
border: "border-outline-green-3",
bg: "bg-surface-green-1/50",
text: "text-ink-green-4",
theme: "green",
}
case "Transfer":
return {
border: "border-outline-violet-3",
bg: "bg-surface-violet-2/50",
text: "text-ink-violet-4",
theme: "violet",
}
default:
return {
border: "border-outline-amber-3",
bg: "bg-surface-amber-1/50",
text: "text-ink-amber-4",
theme: "orange",
}
}
}
const handleActionClick = () => {
if (!rule) return
switch (rule.classify_as) {
case "Bank Entry":
setRecordJournalEntryModalOpen(true)
break
case "Payment Entry":
setRecordPaymentModalOpen(true)
break
case "Transfer":
setTransferModalOpen(true)
break
}
}
const getActionDescription = () => {
if (!rule) return ""
switch (rule.classify_as) {
case "Bank Entry":
return _("Create a journal entry for expenses, income or split transactions")
case "Payment Entry":
return _("Record a payment entry against a customer or supplier")
case "Transfer":
return _("Record an internal transfer to another bank/credit card/cash account")
default:
return _("Create a new entry based on the rule")
}
}
useHotkeys('alt+r', () => {
handleActionClick()
}, {
enabled: true,
enableOnFormTags: false,
preventDefault: true
})
const styles = getActionStyles()
if (!rule) {
return null
}
return (
{getActionIcon()}
{rule.rule_name}
{rule.rule_description || _("Rule matched based on transaction description and other criteria.")}
{voucher.reference_no}
{referenceMatchesFull ? `${_("Complete Match")}` : referenceMatchesPartial ? `${_("Partial Match")}` : `${_("No Match")}`}
{referenceMatchesFull ? `${_("Reference matches the selected transaction")}` : referenceMatchesPartial ? `${_("Reference matches the selected transaction partially")}` : `${_("Reference does not match the selected transaction")}`}
}
{isSuggested &&
{_("Suggested")}
}
}
const MatchBadge = ({ matchType, label }: { matchType: 'full' | 'partial' | 'none', label: string }) => {
return
{matchType === 'full' ? : matchType === 'partial' ?
{_("Partial Match")} :
}
{label}
}
const OlderUnreconciledTransactionsBanner = () => {
// A banner to show when there are unreconciled transactions for the given bank account before the current selected date
const [dates, setDates] = useAtom(bankRecDateAtom)
const selectedBank = useAtomValue(selectedBankAccountAtom)
const { data } = useFrappeGetCall<{
message: {
count: number,
oldest_date: string
}
}>("erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_older_unreconciled_transactions", {
bank_account: selectedBank?.name,
from_date: dates.fromDate,
}, undefined, {
revalidateOnFocus: false,
})
if (data && data.message.count > 0) {
return
{data.message.count > 1 ? (
{_("There are {0} unreconciled transactions before {1}.", [data.message.count.toString(), formatDate(dates.fromDate)])}
) : (
{_("There is one unreconciled transaction before {0}.", [formatDate(dates.fromDate)])}
)}
{_("The opening balance might not match your bank statement. Would you like to reconcile them?")}