hotfix(FE): fixing failed test scenario in module finance

This commit is contained in:
randy-ar
2026-01-17 16:45:56 +07:00
parent 08715e39c2
commit 36da05890a
6 changed files with 151 additions and 87 deletions
+19 -22
View File
@@ -34,7 +34,7 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
}, },
{ {
label: 'Pihak', label: 'Pihak',
value: finance.party.id ? finance.party.name : '-', value: finance.party?.id ? finance.party?.name : '-',
}, },
{ {
label: 'Tanggal', label: 'Tanggal',
@@ -56,25 +56,21 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
}, },
{ {
label: 'Nomor Rekening', label: 'Nomor Rekening',
value: `${finance.bank.alias} - ${finance.bank.account_number} - ${finance.bank.owner}`, value: `${finance.bank?.alias} - ${finance.bank?.account_number} - ${finance.bank?.owner}`,
}, },
{ {
label: `Rekening ${formatTitleCase(finance.party.type)}`, label: `Rekening ${formatTitleCase(finance.party?.type)}`,
value: finance.party.account_number, value: finance.party?.account_number,
}, },
{ {
label: 'Nominal', label: 'Nominal',
value: formatCurrency(finance.expense_amount), value: formatCurrency(finance.nominal),
},
{
label: 'Sisa',
value: formatCurrency(finance.income_amount),
}, },
].filter((item) => { ].filter((item) => {
// Hide party account number row if transaction type is INJECTION // Hide party account number row if transaction type is INJECTION
if ( if (
FINANCE_INJECTION_STATUS.includes(finance.transaction_type) && FINANCE_INJECTION_STATUS.includes(finance.transaction_type) &&
item.label === `Rekening ${formatTitleCase(finance.party.type)}` item.label === `Rekening ${formatTitleCase(finance.party?.type)}`
) { ) {
return false; return false;
} }
@@ -148,18 +144,19 @@ const FinanceDetail = ({ finance }: { finance: Finance }) => {
</Card> </Card>
<div className='flex flex-row gap-2 justify-end'> <div className='flex flex-row gap-2 justify-end'>
{FINANCE_TRANSACTION_STATUS.includes(finance.transaction_type) && ( {FINANCE_TRANSACTION_STATUS.includes(finance.transaction_type) &&
<RequirePermission permissions='lti.finance.payments.update'> finance.party?.type !== 'SUPPLIER' && (
<Button <RequirePermission permissions='lti.finance.payments.update'>
color='warning' <Button
className='min-w-24' color='warning'
href={`/finance/detail/edit?financeId=${finance.id}`} className='min-w-24'
> href={`/finance/detail/edit?financeId=${finance.id}`}
<Icon icon='mdi:pencil-outline' /> >
Edit <Icon icon='mdi:pencil-outline' />
</Button> Edit
</RequirePermission> </Button>
)} </RequirePermission>
)}
{FINANCE_INITIAL_BALANCE_STATUS.includes(finance.transaction_type) && ( {FINANCE_INITIAL_BALANCE_STATUS.includes(finance.transaction_type) && (
<RequirePermission permissions='lti.finance.initial_balances.update'> <RequirePermission permissions='lti.finance.initial_balances.update'>
<Button <Button
+59 -40
View File
@@ -22,7 +22,7 @@ import {
} from '@/config/constant'; } from '@/config/constant';
import { FinanceApi } from '@/services/api/finance'; import { FinanceApi } from '@/services/api/finance';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { BankApi } from '@/services/api/master-data'; import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data';
import { Bank } from '@/types/api/master-data/bank'; import { Bank } from '@/types/api/master-data/bank';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
@@ -65,19 +65,24 @@ const RowOptionsMenu = ({
{FINANCE_TRANSACTION_STATUS.includes( {FINANCE_TRANSACTION_STATUS.includes(
props.row.original.transaction_type props.row.original.transaction_type
) && ( ) &&
<RequirePermission permissions='lti.finance.payments.update'> props.row.original.party?.type !== 'SUPPLIER' && (
<Button <RequirePermission permissions='lti.finance.payments.update'>
href={`/finance/detail/edit?financeId=${props.row.original.id}`} <Button
variant='ghost' href={`/finance/detail/edit?financeId=${props.row.original.id}`}
color='warning' variant='ghost'
className='justify-start text-sm' color='warning'
> className='justify-start text-sm'
<Icon icon='material-symbols:edit-outline' width={16} height={16} /> >
Edit <Icon
</Button> icon='material-symbols:edit-outline'
</RequirePermission> width={16}
)} height={16}
/>
Edit
</Button>
</RequirePermission>
)}
{FINANCE_INITIAL_BALANCE_STATUS.includes( {FINANCE_INITIAL_BALANCE_STATUS.includes(
props.row.original.transaction_type props.row.original.transaction_type
@@ -194,20 +199,25 @@ const FinanceTable = () => {
// ===== Options ===== // ===== Options =====
const transactionTypeOptions = useMemo(() => { const transactionTypeOptions = useMemo(() => {
return [
{ label: 'Transfer', value: 'TRANSFER' },
{ label: 'Cash', value: 'CASH' },
{ label: 'Card', value: 'CARD' },
{ label: 'Cheque', value: 'CHEQUE' },
{ label: 'Saldo', value: 'SALDO' },
];
}, []);
const partyTypeOptions = useMemo(() => {
return [ return [
{ label: 'Customer', value: 'CUSTOMER' }, { label: 'Customer', value: 'CUSTOMER' },
{ label: 'Supplier', value: 'SUPPLIER' }, { label: 'Supplier', value: 'SUPPLIER' },
]; ];
}, []); }, []);
const {
options: partyTypeOptions,
isLoadingOptions: partyTypeIsLoadingOptions,
setInputValue: partyTypeInputValue,
loadMore: partyTypeLoadMore,
} = useSelect(
selectedTransactionType
? selectedTransactionType.value === 'CUSTOMER'
? CustomerApi.basePath
: SupplierApi.basePath
: '',
'id',
'name'
);
const sortByOptions = useMemo(() => { const sortByOptions = useMemo(() => {
return [ return [
{ label: 'Tanggal Pembayaran', value: 'payment_date' }, { label: 'Tanggal Pembayaran', value: 'payment_date' },
@@ -336,10 +346,10 @@ const FinanceTable = () => {
}, },
{ {
header: 'Pihak', header: 'Pihak',
accessorFn: (finance: Finance) => finance.party.name, accessorFn: (finance: Finance) => finance.party?.name,
cell: (props: CellContext<Finance, unknown>) => { cell: (props: CellContext<Finance, unknown>) => {
if (props.row.original.party.id) { if (props.row.original.party?.id) {
return <span>{props.row.original.party.name}</span>; return <span>{props.row.original.party?.name}</span>;
} }
return <span>{'-'}</span>; return <span>{'-'}</span>;
}, },
@@ -360,12 +370,12 @@ const FinanceTable = () => {
{ {
header: 'Bank', header: 'Bank',
accessorFn: (finance: Finance) => accessorFn: (finance: Finance) =>
`${finance.bank.alias} - ${finance.bank.account_number} - ${finance.bank.owner}`, `${finance.bank?.alias} - ${finance.bank?.account_number} - ${finance.bank?.owner}`,
}, },
{ {
header: 'Pengeluaran (Rp)', header: 'Pengeluaran (Rp)',
accessorFn: (finance: Finance) => accessorFn: (finance: Finance) =>
formatCurrency(finance.expense_amount), formatCurrency(Math.abs(finance.expense_amount)),
}, },
{ {
header: 'Pemasukan (Rp)', header: 'Pemasukan (Rp)',
@@ -468,25 +478,41 @@ const FinanceTable = () => {
<div className='grid grid-cols-4 gap-6'> <div className='grid grid-cols-4 gap-6'>
<SelectInput <SelectInput
options={transactionTypeOptions} options={transactionTypeOptions}
label='Jenis Transaksi' label='Tipe Transaksi'
value={selectedTransactionType} value={selectedTransactionType}
onChange={transactionTypeChangeHandler} onChange={transactionTypeChangeHandler}
isClearable isClearable
/> />
<SelectInput
options={partyTypeOptions}
label={
selectedTransactionType
? selectedTransactionType.value === 'CUSTOMER'
? 'Pelanggan'
: 'Supplier'
: 'Pihak'
}
value={selectedPartyType}
onChange={partyTypeChangeHandler}
onInputChange={partyTypeInputValue}
onMenuScrollToBottom={partyTypeLoadMore}
isLoading={partyTypeIsLoadingOptions}
isClearable
/>
<SelectInput <SelectInput
options={ options={
isResponseSuccess(bankRawData) isResponseSuccess(bankRawData)
? bankOptions.map((bank) => ({ ? bankOptions.map((bank) => ({
label: label:
bankRawData.data.find((data) => data.id === bank.value) bankRawData.data.find((data) => data.id === bank?.value)
?.alias + ?.alias +
' - ' + ' - ' +
bankRawData.data.find((data) => data.id === bank.value) bankRawData.data.find((data) => data.id === bank?.value)
?.account_number + ?.account_number +
' - ' + ' - ' +
bankRawData.data.find((data) => data.id === bank.value) bankRawData.data.find((data) => data.id === bank?.value)
?.owner, ?.owner,
value: bank.value, value: bank?.value,
})) }))
: [] : []
} }
@@ -497,13 +523,6 @@ const FinanceTable = () => {
onMenuScrollToBottom={bankLoadMore} onMenuScrollToBottom={bankLoadMore}
isClearable isClearable
/> />
<SelectInput
options={partyTypeOptions}
label='Pihak'
value={selectedPartyType}
onChange={partyTypeChangeHandler}
isClearable
/>
<DebouncedTextInput <DebouncedTextInput
name='search' name='search'
label='Cari' label='Cari'
@@ -32,8 +32,10 @@ import {
import { Bank } from '@/types/api/master-data/bank'; import { Bank } from '@/types/api/master-data/bank';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import Alert from '@/components/Alert';
import { Icon } from '@iconify/react';
interface FormFinanceAddProps { interface FormFinanceAddProps {
type?: 'add' | 'edit'; type?: 'add' | 'edit';
@@ -51,18 +53,22 @@ const FormFinanceAdd = ({
initialValues, initialValues,
}: FormFinanceAddProps) => { }: FormFinanceAddProps) => {
const router = useRouter(); const router = useRouter();
const [serverErrorMessage, setServerErrorMessage] = useState('');
const [isSupplier, setIsSupplier] = useState(
initialValues?.party?.type === 'SUPPLIER'
);
// ===== Formik ===== // ===== Formik =====
const formikInitialValues = useMemo((): FinanceFormValues => { const formikInitialValues = useMemo((): FinanceFormValues => {
return { return {
party_type_option: party_type_option:
FINANCE_PARTY_TYPE_OPTIONS.find( FINANCE_PARTY_TYPE_OPTIONS.find(
(option) => option.value === initialValues?.party.type (option) => option.value === initialValues?.party?.type
) || null, ) || null,
party_id_option: initialValues?.party party_id_option: initialValues?.party
? { ? {
label: initialValues?.party.name || '', label: initialValues?.party?.name || '',
value: initialValues?.party.id || 0, value: initialValues?.party?.id || 0,
} }
: null, : null,
payment_date: initialValues?.payment_date || '', payment_date: initialValues?.payment_date || '',
@@ -72,11 +78,11 @@ const FormFinanceAdd = ({
) || null, ) || null,
bank_id_option: initialValues?.bank bank_id_option: initialValues?.bank
? { ? {
label: initialValues.bank.name, label: initialValues?.bank?.name,
value: initialValues.bank.id, value: initialValues?.bank?.id,
} }
: null, : null,
party_account_number: initialValues?.party.account_number || '', party_account_number: initialValues?.party?.account_number || '',
reference_number: initialValues?.reference_number || '', reference_number: initialValues?.reference_number || '',
nominal: initialValues?.nominal.toString() || '', nominal: initialValues?.nominal.toString() || '',
notes: initialValues?.notes || '', notes: initialValues?.notes || '',
@@ -153,6 +159,7 @@ const FormFinanceAdd = ({
if (isResponseError(response)) { if (isResponseError(response)) {
toast.error(response.message); toast.error(response.message);
setServerErrorMessage(response.message);
return; return;
} }
@@ -168,6 +175,7 @@ const FormFinanceAdd = ({
if (isResponseError(response)) { if (isResponseError(response)) {
toast.error(response.message); toast.error(response.message);
setServerErrorMessage(response.message);
return; return;
} }
@@ -207,6 +215,7 @@ const FormFinanceAdd = ({
? formik.errors.party_type_option ? formik.errors.party_type_option
: '' : ''
} }
isDisabled={type === 'edit' || isSupplier}
required required
isClearable isClearable
/> />
@@ -245,7 +254,7 @@ const FormFinanceAdd = ({
} }
required required
isClearable isClearable
isDisabled={!formik.values.party_type_option?.value} isDisabled={!formik.values.party_type_option?.value || isSupplier}
/> />
<DateInput <DateInput
label='Tanggal' label='Tanggal'
@@ -263,6 +272,7 @@ const FormFinanceAdd = ({
: '' : ''
} }
required required
disabled={isSupplier}
/> />
<SelectInput <SelectInput
label='Metode Pembayaran' label='Metode Pembayaran'
@@ -284,6 +294,7 @@ const FormFinanceAdd = ({
} }
required required
isClearable isClearable
isDisabled={isSupplier}
/> />
<SelectInput <SelectInput
label='Bank' label='Bank'
@@ -324,6 +335,7 @@ const FormFinanceAdd = ({
} }
required required
isClearable isClearable
isDisabled={isSupplier}
/> />
<TextInput <TextInput
label={`Nomor Rekening ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'Pihak'}`} label={`Nomor Rekening ${formik.values.party_type_option?.value ? formatTitleCase(formik.values.party_type_option.value as string) : 'Pihak'}`}
@@ -344,6 +356,7 @@ const FormFinanceAdd = ({
} }
required required
readOnly readOnly
disabled={isSupplier}
/> />
<TextInput <TextInput
label='Nomor Referensi' label='Nomor Referensi'
@@ -363,6 +376,7 @@ const FormFinanceAdd = ({
: '' : ''
} }
required required
disabled={isSupplier}
/> />
<NumberInput <NumberInput
label='Nominal' label='Nominal'
@@ -378,6 +392,7 @@ const FormFinanceAdd = ({
: '' : ''
} }
required required
disabled={isSupplier}
/> />
<TextArea <TextArea
label='Catatan' label='Catatan'
@@ -393,8 +408,18 @@ const FormFinanceAdd = ({
: '' : ''
} }
required required
disabled={isSupplier}
/> />
<AlertErrorList formErrorList={formErrorList} onClose={close} /> <AlertErrorList formErrorList={formErrorList} onClose={close} />
{serverErrorMessage && (
<Alert color='error'>
<Icon icon='mdi:alert' />
{serverErrorMessage}
<Button color='error' onClick={() => setServerErrorMessage('')}>
<Icon icon='mdi:close' />
</Button>
</Alert>
)}
<div className='flex justify-center gap-4'> <div className='flex justify-center gap-4'>
<Button <Button
type='reset' type='reset'
@@ -27,13 +27,7 @@ export const InitialBalanceFormSchema = Yup.object().shape({
'Pihak wajib diisi', 'Pihak wajib diisi',
(value) => value !== null && value !== undefined (value) => value !== null && value !== undefined
), ),
bank_id_option: Yup.mixed() bank_id_option: Yup.mixed().nullable(),
.nullable()
.test(
'is-valid-option',
'Bank wajib diisi',
(value) => value !== null && value !== undefined
),
reference_number: Yup.string().required('Nomor referensi wajib diisi'), reference_number: Yup.string().required('Nomor referensi wajib diisi'),
initial_balance_type_option: Yup.mixed() initial_balance_type_option: Yup.mixed()
.nullable() .nullable()
@@ -29,8 +29,9 @@ import { Bank } from '@/types/api/master-data/bank';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import Alert from '@/components/Alert';
interface FormFinanceAddInitialBalanceProps { interface FormFinanceAddInitialBalanceProps {
type?: 'add' | 'edit'; type?: 'add' | 'edit';
@@ -42,6 +43,7 @@ const FormFinanceAddInitialBalance = ({
initialValues, initialValues,
}: FormFinanceAddInitialBalanceProps) => { }: FormFinanceAddInitialBalanceProps) => {
const router = useRouter(); const router = useRouter();
const [serverErrorMessage, setServerErrorMessage] = useState('');
// ===== Formik ===== // ===== Formik =====
const formikInitialValues = useMemo((): InitialBalanceFormValues => { const formikInitialValues = useMemo((): InitialBalanceFormValues => {
@@ -53,18 +55,18 @@ const FormFinanceAddInitialBalance = ({
return { return {
party_type_option: party_type_option:
FINANCE_PARTY_TYPE_OPTIONS.find( FINANCE_PARTY_TYPE_OPTIONS.find(
(option) => option.value === initialValues?.party.type (option) => option.value === initialValues?.party?.type
) || null, ) || null,
party_id_option: initialValues?.party party_id_option: initialValues?.party
? { ? {
label: initialValues.party.name, label: initialValues.party?.name,
value: initialValues.party.id, value: initialValues.party?.id,
} }
: null, : null,
bank_id_option: initialValues?.bank bank_id_option: initialValues?.bank
? { ? {
label: initialValues.bank.name, label: initialValues.bank?.name,
value: initialValues.bank.id, value: initialValues.bank?.id,
} }
: null, : null,
reference_number: initialValues?.reference_number || '', reference_number: initialValues?.reference_number || '',
@@ -147,6 +149,7 @@ const FormFinanceAddInitialBalance = ({
if (isResponseError(response)) { if (isResponseError(response)) {
toast.error(response.message); toast.error(response.message);
setServerErrorMessage(response.message);
return; return;
} }
@@ -166,6 +169,7 @@ const FormFinanceAddInitialBalance = ({
if (isResponseError(response)) { if (isResponseError(response)) {
toast.error(response.message); toast.error(response.message);
setServerErrorMessage(response.message);
return; return;
} }
@@ -211,6 +215,7 @@ const FormFinanceAddInitialBalance = ({
: '' : ''
} }
required required
isDisabled={type === 'edit'}
isClearable isClearable
/> />
<SelectInput <SelectInput
@@ -277,7 +282,6 @@ const FormFinanceAddInitialBalance = ({
? formik.errors.bank_id_option ? formik.errors.bank_id_option
: '' : ''
} }
required
isClearable isClearable
/> />
<TextInput <TextInput
@@ -362,7 +366,18 @@ const FormFinanceAddInitialBalance = ({
} }
required required
/> />
<AlertErrorList formErrorList={formErrorList} onClose={close} /> <AlertErrorList formErrorList={formErrorList} onClose={close} />
{serverErrorMessage && (
<Alert color='error'>
<Icon icon='mdi:alert' />
{serverErrorMessage}
<Button color='error' onClick={() => setServerErrorMessage('')}>
<Icon icon='mdi:close' />
</Button>
</Alert>
)}
<div className='flex justify-center gap-4'> <div className='flex justify-center gap-4'>
<Button <Button
type='reset' type='reset'
@@ -24,8 +24,10 @@ import {
import { Bank } from '@/types/api/master-data/bank'; import { Bank } from '@/types/api/master-data/bank';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo, useState } from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import Alert from '@/components/Alert';
import { Icon } from '@iconify/react';
interface FormFinanceInjectionProps { interface FormFinanceInjectionProps {
type?: 'add' | 'edit'; type?: 'add' | 'edit';
@@ -37,14 +39,15 @@ const FormFinanceInjection = ({
initialValues, initialValues,
}: FormFinanceInjectionProps) => { }: FormFinanceInjectionProps) => {
const router = useRouter(); const router = useRouter();
const [serverErrorMessage, setServerErrorMessage] = useState('');
// ===== Formik ===== // ===== Formik =====
const formikInitialValues = useMemo((): InjectionFormValues => { const formikInitialValues = useMemo((): InjectionFormValues => {
return { return {
bank_id_option: initialValues?.bank bank_id_option: initialValues?.bank
? { ? {
label: initialValues.bank.name, label: initialValues.bank?.name,
value: initialValues.bank.id, value: initialValues.bank?.id,
} }
: null, : null,
adjustment_date: initialValues?.payment_date || '', adjustment_date: initialValues?.payment_date || '',
@@ -103,6 +106,7 @@ const FormFinanceInjection = ({
if (isResponseError(response)) { if (isResponseError(response)) {
toast.error(response.message); toast.error(response.message);
setServerErrorMessage(response.message);
return; return;
} }
@@ -119,6 +123,7 @@ const FormFinanceInjection = ({
if (isResponseError(response)) { if (isResponseError(response)) {
toast.error(response.message); toast.error(response.message);
setServerErrorMessage(response.message);
return; return;
} }
@@ -230,6 +235,15 @@ const FormFinanceInjection = ({
required required
/> />
<AlertErrorList formErrorList={formErrorList} onClose={close} /> <AlertErrorList formErrorList={formErrorList} onClose={close} />
{serverErrorMessage && (
<Alert color='error'>
<Icon icon='mdi:alert' />
{serverErrorMessage}
<Button color='error' onClick={() => setServerErrorMessage('')}>
<Icon icon='mdi:close' />
</Button>
</Alert>
)}
<div className='flex justify-center gap-4'> <div className='flex justify-center gap-4'>
<Button <Button
type='reset' type='reset'