feat: integrate payment functionality with new payment sheet and enhance supplier payments UI
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Successful in 1m51s
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 10m54s

This commit is contained in:
Kalana B. Thilakarathna
2026-10-19 22:02:15 +05:30
parent a980e636f5
commit eb0b042f0c
5 changed files with 161 additions and 11 deletions
@@ -1,6 +1,6 @@
'use client'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { useForm, zodResolver, z } from '@/lib/form'
import { FormField } from '@/components/shared/form-field'
import { Input } from '@/components/ui/input'
@@ -23,8 +23,9 @@ import {
} from '@/components/ui/dialog'
import { FieldGroup } from '@/components/ui/field'
import { toast } from '@/components/ui/toast'
import { payments as paymentsApi, getApiError } from '@/lib/api/index'
import { payments as paymentsApi, bankAccounts as bankAccountsApi, getApiError } from '@/lib/api/index'
import type { InvoiceResponse } from '@/types/api'
import type { BankAccountResponse } from '@/types/payments'
const schema = z
.object({
@@ -32,8 +33,15 @@ const schema = z
amount: z.number().min(0.01, 'Amount must be greater than 0'),
method: z.enum(['Cash', 'Cheque', 'Credit']),
entryMode: z.enum(['RealTime', 'DayEndManual']),
bankAccountId: z.string().optional(),
chequeNumber: z.string().optional(),
chequeDate: z.string().optional(),
drawerBankName: z.string().optional(),
accountHolderName: z.string().optional(),
})
.refine((data) => data.method !== 'Cash' || !!data.bankAccountId, {
message: 'Bank/Cash account is required for cash payments',
path: ['bankAccountId'],
})
.refine((data) => data.method !== 'Cheque' || !!data.chequeNumber, {
message: 'Cheque number is required for cheque payments',
@@ -51,21 +59,27 @@ interface PaymentSheetProps {
onClose: () => void
onSuccess: () => void
invoices: InvoiceResponse[]
defaultInvoiceId?: string
}
const fmtAmount = (n: number) => `Rs. ${n.toLocaleString()}`
const truncId = (id: string) => id.substring(0, 8)
export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentSheetProps) {
export function PaymentSheet({ open, onClose, onSuccess, invoices, defaultInvoiceId }: PaymentSheetProps) {
const [accounts, setAccounts] = useState<BankAccountResponse[]>([])
const form = useForm<FormValues>({
resolver: zodResolver(schema),
defaultValues: {
invoiceId: '',
invoiceId: defaultInvoiceId ?? '',
amount: 0,
method: 'Cash',
entryMode: 'RealTime',
bankAccountId: '',
chequeNumber: '',
chequeDate: '',
drawerBankName: '',
accountHolderName: '',
},
})
@@ -74,23 +88,32 @@ export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentShee
useEffect(() => {
if (!open) return
form.reset({
invoiceId: '',
invoiceId: defaultInvoiceId ?? '',
amount: 0,
method: 'Cash',
entryMode: 'RealTime',
bankAccountId: '',
chequeNumber: '',
chequeDate: '',
drawerBankName: '',
accountHolderName: '',
})
}, [open])
bankAccountsApi.list()
.then(list => setAccounts(list.filter(a => a.isActive !== false)))
.catch(() => {})
}, [open, defaultInvoiceId])
async function onSubmit(values: FormValues) {
const { invoiceId, method, chequeNumber, chequeDate, ...rest } = values
const { invoiceId, method, bankAccountId, chequeNumber, chequeDate, drawerBankName, accountHolderName, ...rest } = values
try {
await paymentsApi.create(invoiceId, {
...rest,
method,
bankAccountId: method === 'Cash' ? bankAccountId : undefined,
chequeNumber: method === 'Cheque' ? chequeNumber : undefined,
chequeDate: method === 'Cheque' ? chequeDate : undefined,
drawerBankName: method === 'Cheque' ? drawerBankName || undefined : undefined,
accountHolderName: method === 'Cheque' ? accountHolderName || undefined : undefined,
})
toast.add({ title: 'Payment recorded', type: 'success' })
onSuccess()
@@ -118,12 +141,11 @@ export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentShee
label="Invoice"
required
render={({ field }) => (
<Select value={field.value as string} onValueChange={field.onChange}>
<Select value={field.value as string} onValueChange={field.onChange} disabled={!!defaultInvoiceId}>
<SelectTrigger>
<SelectValue placeholder="Select invoice..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="" disabled>Select invoice...</SelectItem>
{pendingInvoices.map((inv) => (
<SelectItem key={inv.id} value={inv.id}>
{inv.storeName} — {fmtAmount(inv.totalAmount)} ({inv.paymentStatus}) [{truncId(inv.id)}]
@@ -174,6 +196,29 @@ export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentShee
)}
/>
{method === 'Cash' && (
<FormField
control={form.control}
name="bankAccountId"
label="Deposit Account"
required
render={({ field }) => (
<Select value={(field.value as string) ?? ''} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue placeholder="Choose where the money lands" />
</SelectTrigger>
<SelectContent>
{accounts.map((a) => (
<SelectItem key={a.accountId} value={a.accountId}>
{a.accountName}{a.bankName ? ` (${a.bankName})` : ''}
</SelectItem>
))}
</SelectContent>
</Select>
)}
/>
)}
{method === 'Cheque' && (
<>
<FormField
@@ -213,6 +258,40 @@ export function PaymentSheet({ open, onClose, onSuccess, invoices }: PaymentShee
/>
)}
/>
<FormField
control={form.control}
name="drawerBankName"
label="Drawer Bank (optional)"
render={({ field }) => (
<Input
id="drawerBankName"
name={field.name}
ref={field.ref}
value={(field.value as string) ?? ''}
onChange={field.onChange}
onBlur={field.onBlur}
placeholder="e.g. Sampath Bank"
/>
)}
/>
<FormField
control={form.control}
name="accountHolderName"
label="Account Holder (optional)"
render={({ field }) => (
<Input
id="accountHolderName"
name={field.name}
ref={field.ref}
value={(field.value as string) ?? ''}
onChange={field.onChange}
onBlur={field.onBlur}
placeholder="Name on the cheque"
/>
)}
/>
</>
)}