Files
erpnext/banking/src/hooks/usePaymentEntryCalculations.tsx
Nikhil Kothari 6de5367f12 feat: new banking module (#54720)
* feat: initial SPA setup for banking

* wip: bring over new banking module

* feat: added Espresso design tokens

* feat: button styles

* fix: add all ink colors

* wip: espresso design system changes

* feat: button and badge espresso components

* fix: button styling for reconcile

* feat: Espresso progress bar

* feat: Espresso toggle switch

* feat: Espresso tabs design

* fix: vertical tab support

* fix: button sizing across modals

* feat: Espresso style table layout

* feat: Espresso tooltip

* feat: Espresso elevations and checkbox

* feat: Dialog with Espresso styles

* feat: Espresso textarea

* fix: input styles

* fix: colors on bank picker

* fix: breadcrumb styling

* fix: bank picker styling

* feat: create doctypes and fields for bank reconciliation

* feat: APIs for banking

* fix: use date format parser

* fix: font styling to match Espresso

* wip: settings modal

* feat: settings dialog component

* fix: icons and invalid requests

* feat: preferences tab

* fix: adjust icon stroke width to 1.5

* feat: rule configuration in settings

* fix: remove sheet component

* feat: alert and error banner component

* feat: dropdown in Espresso

* feat: popover and select in Espresso

* fix: cleanup more styles

* fix: match size of link fields

* feat: command styling

* fix: remove unused style tokens

* fix: styles for global date picker dropdown

* fix: styles for match and reconcile

* feat: table Espresso component

* feat: remove all other design tokens

* fix: remove unused tokens

* fix: form elements

* fix: remove unused styles and fix filters in bank transaction list

* feat: fetch bank rec doctypes for filtering

* fix: record payment modal

* feat: support for dark mode switching

* fix: move bank logos to public folder

* feat: add support for RTL

* feat: support for RTL

* chore: send layout direction in dev boot

* fix: make checkbox work in RTL

* feat: dark mode support

* fix: dark mode style

* feat: bank logos in dark mode

* feat: dark mode bank logos

* chore: use dark mode bank logos everywhere

* chore: move rule evaluation to controller

* chore: add tests for bank transaction rules

* fix: move deps to fix actions errors

* fix: move tw-animate-css to deps

* fix: remove shadcn

* fix: do not open modal if no transactions selected

* fix: add translation strings

* feat: add banner on existing bank reconciliation tool

* feat: bank statement import

* fix: translations and layout directions

* fix: validation for transaction matching rule

* fix: styles

* fix: show conflicting transactions in alert

* fix: show help text for new banking module forms

* feat: show total debits and credits

* fix: dark mode colors in automatic config

* feat: add keyboard shortcuts help

* feat: added keyboard shortcut for settings

* fix: decrease size of progress bar

* chore: bump packages

* feat: add tests for statement import

* fix: settings dialog

* fix: show banner on small screens

* fix: show banner when no bank account set
2026-05-09 23:14:58 +05:30

138 lines
7.0 KiB
TypeScript

import { flt } from "@/lib/numbers"
import { PaymentEntryDeduction } from "@/types/Accounts/PaymentEntryDeduction"
import { PaymentEntryReference } from "@/types/Accounts/PaymentEntryReference"
import { useCallback } from "react"
import { useFormContext } from "react-hook-form"
export const usePaymentEntryCalculations = () => {
const { setValue, getValues, watch } = useFormContext()
const payment_type = watch('payment_type')
const allocatePartyAmount = (paid_amount: number) => {
const deductionsTable = getValues('deductions') ?? []
const party_type = getValues('party_type')
let total_positive_outstanding_including_order = 0
let total_negative_outstanding = 0
const total_deductions = deductionsTable.reduce((acc: number, row: PaymentEntryDeduction) => acc + row.amount, 0)
paid_amount -= total_deductions
const references = getValues('references') ?? []
references.forEach((ref: PaymentEntryReference) => {
if (flt(ref.outstanding_amount) > 0) {
total_positive_outstanding_including_order += flt(ref.outstanding_amount)
} else {
total_negative_outstanding += Math.abs(flt(ref.outstanding_amount))
}
})
let allocated_negative_outstanding = 0
let allocated_positive_outstanding = 0
if (
(payment_type == "Receive" && party_type == "Customer") ||
(payment_type == "Pay" && party_type == "Supplier")
) {
if (total_positive_outstanding_including_order > paid_amount) {
const remaining_outstanding = total_positive_outstanding_including_order - paid_amount
allocated_negative_outstanding = total_negative_outstanding < remaining_outstanding ? total_negative_outstanding : remaining_outstanding
}
allocated_positive_outstanding = paid_amount + allocated_negative_outstanding
} else {
total_negative_outstanding = flt(total_negative_outstanding)
if (paid_amount > total_negative_outstanding) {
allocated_positive_outstanding = total_negative_outstanding - paid_amount
allocated_negative_outstanding = paid_amount + (total_positive_outstanding_including_order < allocated_positive_outstanding ? total_positive_outstanding_including_order : allocated_positive_outstanding)
}
}
references.forEach((ref: PaymentEntryReference, index: number) => {
if (flt(ref.outstanding_amount) > 0 && allocated_positive_outstanding >= 0) {
setValue(`references.${index}.allocated_amount`, (flt(ref.outstanding_amount) >= allocated_positive_outstanding) ?
allocated_positive_outstanding : ref.outstanding_amount)
allocated_positive_outstanding -= flt(ref.allocated_amount)
} else if (flt(ref.outstanding_amount) < 0 && allocated_negative_outstanding) {
setValue(`references.${index}.allocated_amount`, (flt(ref.outstanding_amount) >= allocated_negative_outstanding) ?
-1 * allocated_negative_outstanding : ref.outstanding_amount)
allocated_negative_outstanding -= flt(ref.allocated_amount)
}
})
setTotalAllocatedAmount()
}
const setDifferenceAmount = useCallback((value: number) => {
const base_total_allocated_amount = getValues('base_total_allocated_amount') ?? 0
const base_received_amount = getValues('base_received_amount') ?? 0
const base_paid_amount = getValues('base_paid_amount') ?? 0
const deductionsTable = getValues('deductions') ?? []
const base_total_taxes_and_charges = getValues('base_total_taxes_and_charges') ?? 0
let difference_amount = 0
const base_party_amount = base_total_allocated_amount + flt(value)
if (payment_type == "Receive") {
difference_amount = base_party_amount - base_received_amount
}
else if (payment_type == "Pay") {
difference_amount = base_paid_amount - base_party_amount
}
else {
difference_amount = base_paid_amount - base_received_amount
}
const total_deductions = deductionsTable.reduce((acc: number, row: PaymentEntryDeduction) => acc + row.amount, 0)
setValue('difference_amount', difference_amount - total_deductions + base_total_taxes_and_charges)
}, [getValues, setValue, payment_type])
const setUnallocatedAmount = useCallback(() => {
const deductionsTable = getValues('deductions') ?? []
const base_total_allocated_amount = getValues('base_total_allocated_amount') ?? 0
const base_received_amount = getValues('base_received_amount') ?? 0
const total_allocated_amount = getValues('total_allocated_amount') ?? 0
const paid_amount = getValues('paid_amount') ?? 0
const base_total_taxes_and_charges = getValues('base_total_taxes_and_charges') ?? 0
const base_paid_amount = getValues('base_paid_amount') ?? 0
const received_amount = getValues('received_amount') ?? 0
const party = getValues('party')
let unallocated_amount = 0
const total_deductions = deductionsTable.reduce((acc: number, row: PaymentEntryDeduction) => acc + row.amount, 0)
if (party) {
if (payment_type == "Receive" && base_total_allocated_amount < base_received_amount + total_deductions
&& total_allocated_amount < paid_amount + total_deductions) {
unallocated_amount = base_received_amount + total_deductions + base_total_taxes_and_charges
- base_total_allocated_amount
} else if (payment_type == "Pay"
&& base_total_allocated_amount < base_paid_amount - total_deductions
&& total_allocated_amount < received_amount + total_deductions) {
unallocated_amount = base_paid_amount + base_total_taxes_and_charges - (total_deductions
+ base_total_allocated_amount)
}
}
setValue('unallocated_amount', unallocated_amount)
setDifferenceAmount(unallocated_amount)
}, [getValues, setValue, payment_type, setDifferenceAmount])
const setTotalAllocatedAmount = useCallback(() => {
let total_allocated_amount = 0
let base_total_allocated_amount = 0
const references = getValues('references')
references?.forEach((ref: PaymentEntryReference) => {
if (ref.allocated_amount) {
total_allocated_amount += flt(ref.allocated_amount)
base_total_allocated_amount += flt(ref.allocated_amount)
}
})
setValue('total_allocated_amount', Math.abs(total_allocated_amount))
setValue('base_total_allocated_amount', Math.abs(base_total_allocated_amount))
setUnallocatedAmount()
}, [getValues, setValue, setUnallocatedAmount])
return {
setTotalAllocatedAmount,
setUnallocatedAmount,
setDifferenceAmount,
allocatePartyAmount
}
}