Config.ts
import { Module, registerModule } from '@txp-core/runtime'; import { outstandingTableSideEffectPattern } from './outstandingTableSideEffectPattern'; import { outstandingTransactionsHandler, updateLinks, amountUpdateHandler, creditsTransactionsHandler, creditsTransactionsHandlerAmount } from './outstandingTransactionsSaga'; import { TransactionTypes } from '@txp-core/transactions-utils'; import { CHARGES_TABLE_DATA_UPDATE, CREDITS_TABLE_DATA_UPDATE, CHARGES_TABLE_DATA_FETCHED, CREDITS_TABLE_WHOLE_UPDATE, CHARGES_TABLE_WHOLE_UPDATE } from '@txp-core/payment-transactions-table'; export const outstandingTransactionsModule: Module = registerModule({ id: 'outstandingTransactions', sideEffects: [ { id: 'outstandingTransactions', description: 'update amount when payment amount get updated', // pattern: outstandingTableSideEffectPattern, pattern: CHARGES_TABLE_DATA_UPDATE, handler: outstandingTransactionsHandler }, { id: 'amountUpdate1', description: 'update amount when payment amount get updated', // pattern: outstandingTableSideEffectPattern, pattern: CHARGES_TABLE_DATA_FETCHED, handler: amountUpdateHandler }, { id: 'amountUpdate', description: 'update amount when payment amount get updated', // pattern: outstandingTableSideEffectPattern, pattern: CREDITS_TABLE_DATA_UPDATE, handler: amountUpdateHandler }, //commented // { // id: 'creditTableUpdate', // description: 'update amount when payment amount get updated', // // pattern: outstandingTableSideEffectPattern, // pattern: CREDITS_TABLE_DATA_UPDATE, // handler: creditsTransactionsHandler // }, //commented //lets try { id: 'credit update on amountUpdate', description: 'update credit when amount entered manullay', // pattern: outstandingTableSideEffectPattern, pattern: CHARGES_TABLE_WHOLE_UPDATE, handler: creditsTransactionsHandlerAmount } ], // preSave: [ // { // id: 'updatelinks', // description: 'update links based on checked table rows of charges and credit table', // handler: updateLinks, // enabled: { // 'environment.txnType': [TransactionTypes.PURCHASE_BILL_PAYMENT] // } // } // ], withModuleDependencies: ['coreTransactionState', 'coreUIState'] });

AmountField
import React from 'react'; import { useFormatMessage } from '@txp-core/intl'; import AmountField, { amountFieldValidation } from '@txp-core/amount-field'; import styles from 'src/js/billpayment/modules/AmountField/AmountField.module.css'; import { enabled } from '@txp-core/enabled'; import { TransactionTypes } from '@txp-core/transactions-utils'; import { useTXPStateRead, useTXPStateWrite } from '@txp-core/runtime'; import { TransactionActions } from '@txp-core/transactions-core'; import { PaymentChargesTableAction, paymentTableSelectors } from '@txp-core/payment-transactions-table'; import { cloneDeep } from '@txp-core/basic-utils'; import { CreditChargesPaymentTypes } from '@txp-core/payment-transactions-table/dist/paymentTableStateTypes'; const AMOUNT_MAX_VALUE = 99999999999.99; const Amount = () => { const formatMessage = useFormatMessage(); const onChange = useTXPStateWrite(TransactionActions.genericTxnUpdate); const error = useTXPStateWrite(amountFieldValidation.errorAdded); const chargesTable = useTXPStateRead(paymentTableSelectors.getCharges); const creditTableLines = useTXPStateRead(paymentTableSelectors.getCredits); const customerBalanceAmount = useTXPStateRead(paymentTableSelectors.getCustomerBalanceAmount); const updateTablesData = useTXPStateWrite(PaymentChargesTableAction.chargesTableWholeUpdate); const amountLabel = formatMessage({ id: 'billpayment_amount', defaultMessage: 'Amount' }); const handleChange = (moneyValue: string | null) => { const amountValue = Number(moneyValue); if (moneyValue && amountValue > AMOUNT_MAX_VALUE) { error({ errorText: { id: 'amountReceivedField_range_limit_error', defaultMessage: 'This value is out of range' } }); } onChange({ amount: moneyValue || '0.00' }); // so here we have to update the chargesTable, based on amount value,means we have // charge table having data one field is openBalance and based on amount we need to update its // respective linkedPaymentAmount with openbalance, before updated charge table // we need to check first if we can adjust amount in openbalance with exact match, // otherwiser try to adjust from top const updateChargeTable: CreditChargesPaymentTypes[] = cloneDeep(chargesTable) || []; let balanceAmount = Number(moneyValue); const extactMatch = updateChargeTable.filter((charge) => { return Number(charge.openBalance) === balanceAmount; }); if (extactMatch.length !== 0) { updateChargeTable.map((item) => { if (item.txnId === extactMatch[0].txnId) { item.linkedPaymentAmount = item.openBalance.toString(); } else { item.linkedPaymentAmount = ''; } return item; }); } else { updateChargeTable.map((item) => { if (balanceAmount > 0) { const openBalance = Number(item.openBalance); if (balanceAmount > openBalance) { item.linkedPaymentAmount = item.openBalance.toString(); balanceAmount -= openBalance; } else { item.linkedPaymentAmount = balanceAmount.toFixed(2).toString(); balanceAmount = 0; } } else { item.linkedPaymentAmount = ''; } return item; }); } const result: any = { charges: updateChargeTable, credits: creditTableLines, customerBalanceAmount }; updateTablesData(result); }; return ( <div className={styles.billPaymentAmountField}> <AmountField idsProps={{ label: amountLabel }} enableValidation updateTxnSlice={handleChange} /> </div> ); }; export default enabled( { 'environment.txnType': [TransactionTypes.PURCHASE_BILL_PAYMENT] }, Amount );

Outsandingtable
import React from 'react'; import { ChargesTable, paymentTableSelectors, PaymentChargesTableAction } from '@txp-core/payment-transactions-table'; import { Table } from '@ids-ts/table'; import { useTXPStateRead, useTXPStateWrite } from '@txp-core/runtime'; import { useFormatMessage } from '@txp-core/intl'; import { TransactionSelectors } from '@txp-core/transactions-core'; import { enabled } from '@txp-core/enabled'; import { TransactionTypes, getTxnUrl } from '@txp-core/transactions-utils'; import { toLocalizedMoneyString, toLocalDateFormat, cloneDeep } from '@txp-core/basic-utils'; import TransactionsTableHeader from './TransactionTableHeaders'; import styles from 'src/js/staticLayout/Grid.module.css'; import Checkbox, { CheckboxOnChangeEventType } from '@ids-ts/checkbox'; import style from './Tables.module.css'; import { useTrowserHooks } from '@txp-core/trowser'; import LinkButton from '@txp-core/link-button'; import PaymentField from 'src/js/billpayment/modules/TransactionTables/PaymentField'; import TransactionTableFilter from './TransactionTableFilter'; import { getTotalOfChargesTable, getTotalOfCreditsTable } from './utilities'; export interface txnType { txnTypeId: number; txnTypeLongName: string; } export interface OutstandingTableDataTypes { txnId: number; txnType: txnType; date: string; amount: string; referenceNumber: number; dueDate: string; openBalance: string; linkedPaymentAmount: string; isLinkedPaymentAmount?: boolean; } export const OutstandingTransactionsTable = () => { const formatMessage = useFormatMessage(); const customerId = useTXPStateRead(TransactionSelectors.getContactId); const outstandingTableLines = useTXPStateRead(paymentTableSelectors.getCharges) || []; const creditTableLines = useTXPStateRead(paymentTableSelectors.getCredits) || []; const updateTablesData = useTXPStateWrite(PaymentChargesTableAction.chargesTableDataFetched); const customerBalanceAmount = useTXPStateRead(paymentTableSelectors.getCustomerBalanceAmount); const onPaymentFieldUpdate = useTXPStateWrite(PaymentChargesTableAction.chargesTableDataUpdate); const oncreditTableLinesUpdate = useTXPStateWrite( PaymentChargesTableAction.creditsTableDataUpdate ); const { closeTrowser } = useTrowserHooks(); const Items = (props: { item: OutstandingTableDataTypes; idx: number }) => { const { item, idx } = props; const txnUrl = getTxnUrl(`${item.txnType.txnTypeId}`, item.txnId); const referenceNumber = item?.referenceNumber ? `# ${item?.referenceNumber}` : ''; const handleCheckboxChange = (event: CheckboxOnChangeEventType, txnId: number) => { if (event.target.checked) { const value = outstandingTableLines.filter((x) => x.txnId === txnId)[0].openBalance; onPaymentFieldUpdate(txnId, { linkedPaymentAmount: value.toString(), isLinkedPaymentAmount: true }); // Update the corresponding credit checked table // const updateCreditTable = cloneDeep(creditTableLines); // const filterCheckedCredit = updateCreditTable.filter( // (payment) => payment.linkedPaymentAmount !== '' // ); // const totalOfChargesTable = // getTotalOfChargesTable(outstandingTableLines) + Number(value); // console.log('123', totalOfChargesTable); // const totalOfCreditsTable = getTotalOfCreditsTable(creditTableLines); // let amountToAdjust = Math.abs(totalOfChargesTable - totalOfCreditsTable); // let totalOfChargesTable1 = totalOfChargesTable; // const updateFilterCheckedCredit = filterCheckedCredit.map((credit) => { // // const amountCanAdjust = amountToAdjust - credit.openBalance; // const amountCanAdjust = totalOfChargesTable1 - credit.openBalance; // if (amountCanAdjust > credit.openBalance) { // credit.linkedPaymentAmount = credit.openBalance.toString(); // const remainingAmountAfterAdjust = amountCanAdjust - credit.openBalance; // totalOfChargesTable1 = remainingAmountAfterAdjust; // } else { // credit.linkedPaymentAmount = amountCanAdjust.toString(); // totalOfChargesTable1 = 0; // } // return credit; // }); // console.log('hello', updateFilterCheckedCredit); // const updatedCreditTable = { ...updateCreditTable, ...updateFilterCheckedCredit }; // console.log('hello1', updatedCreditTable); // const result: any = { // charges: outstandingTableLines, // credits: updatedCreditTable, // customerBalanceAmount // }; // updateTablesData(result); } else { onPaymentFieldUpdate(txnId, { linkedPaymentAmount: '' }); } }; const handlePaymentFieldChange = (inputValue: string) => { let value = ''; if (item.linkedPaymentAmount) { value = Number(inputValue) <= Number(item.openBalance) ? inputValue : item.linkedPaymentAmount; } else { value = Number(inputValue) <= Number(item.openBalance) ? inputValue : ''; } onPaymentFieldUpdate(item.txnId, { linkedPaymentAmount: value, isLinkedPaymentAmount: true }); }; const handleNavigate = () => closeTrowser?.({ navigateTo: txnUrl, skipTrowserCloseDelay: true }); return ( <> <Table.Row key={idx} className='tableRow'> <Table.Cell className={style.checkbox}> <Checkbox aria-label={formatMessage({ id: 'charges_credits_table_select_rows', defaultMessage: 'Select rows' })} style={{ cursor: 'pointer' }} checked={!!item.linkedPaymentAmount} onChange={(event) => handleCheckboxChange(event, item.txnId)} title='Toggle Row Selected' /> </Table.Cell> <Table.Cell className={styles.leftAlign}> <LinkButton onClick={handleNavigate} text={`${item.txnType.txnTypeLongName} ${referenceNumber}`} />{' '} ({toLocalDateFormat(item.date)}) </Table.Cell> <Table.Cell className={styles.leftAlign}> {toLocalDateFormat(item.dueDate)} </Table.Cell> <Table.Cell className={styles.rightAlign}> {toLocalizedMoneyString(item.amount)} </Table.Cell> <Table.Cell className={styles.rightAlign}> {toLocalizedMoneyString(item.openBalance)} </Table.Cell> <Table.Cell> <PaymentField paymentAmount={item.linkedPaymentAmount} txnId={item.txnId} rowIndex={idx} handleOnPaymentChange={handlePaymentFieldChange} /> </Table.Cell> </Table.Row> </> ); }; return ( <div className={styles.gridWrapper}> {customerId && ( <ChargesTable header={<TransactionsTableHeader dueDateFlag />} filter={<TransactionTableFilter showOverDueCheck />} > {(item: OutstandingTableDataTypes, idx: number) => ( <Items item={item} idx={idx} /> )} </ChargesTable> )} </div> ); }; export default enabled( { 'environment.txnType': [TransactionTypes.PURCHASE_BILL_PAYMENT] }, OutstandingTransactionsTable );

outstandingTransactionsSaga
import { ResultType, StepResponse } from '@txp-core/runtime'; import { Effect, put } from 'redux-saga/effects'; import { PaymentChargesTableAction, paymentTableSelectors } from '@txp-core/payment-transactions-table'; import { TransactionSelectors, genericTxnUpdate } from '@txp-core/transactions-core'; import { cloneDeep, getAmount, select } from '@txp-core/basic-utils'; import { TransactionTypes } from '@txp-core/transactions-utils'; import { CreditChargesPaymentTypes } from '@txp-core/payment-transactions-table/dist/paymentTableStateTypes'; import { getKeyByValue } from 'src/js/billpayment/modules/TransactionTables/utilities'; import { getTotalOfChargesTable, getTotalOfCreditsTable } from './utilities'; export function* outstandingTransactionsHandler(): Generator<Effect, StepResponse, string> { const chargesTable = yield* select(paymentTableSelectors.getCharges); const creditTableLines = yield* select(paymentTableSelectors.getCredits); const customerBalanceAmount = yield* select(paymentTableSelectors.getCustomerBalanceAmount); const updateCreditTable: CreditChargesPaymentTypes[] = cloneDeep(creditTableLines) || []; const filterCheckedCredit = updateCreditTable?.filter( (payment) => payment.linkedPaymentAmount !== '' ); // const totalOfChargesTable = getTotalOfChargesTable(chargesTable); const totalOfChargesTable = yield* select(paymentTableSelectors.getTotalOfChargesTable); // const totalOfCreditsTable = getTotalOfCreditsTable(creditTableLines); //let amountToAdjust = Math.abs(totalOfChargesTable - totalOfCreditsTable); let totalOfChargesTable1: any = totalOfChargesTable; const updateFilterCheckedCredit = filterCheckedCredit?.map((credit) => { // const amountCanAdjust = amountToAdjust - credit.openBalance; const amountCanAdjust = totalOfChargesTable1 - credit.openBalance; if (totalOfChargesTable1 > credit.openBalance) { credit.linkedPaymentAmount = credit.openBalance.toString(); // const remainingAmountAfterAdjust = amountCanAdjust - credit.openBalance; // const remainingAmountAfterAdjust = amountCanAdjust; // totalOfChargesTable1 = remainingAmountAfterAdjust; const remainingAmountAfterAdjust = amountCanAdjust; totalOfChargesTable1 = remainingAmountAfterAdjust; } else { credit.linkedPaymentAmount = totalOfChargesTable1 === 0 ? '' : totalOfChargesTable1.toString(); totalOfChargesTable1 = ''; } return credit; }); // const updatedCreditTable = [...updateCreditTable, ...updateFilterCheckedCredit]; const updatedCreditTable = updateCreditTable?.map((credit) => { const updatedCredit = updateFilterCheckedCredit?.find((c) => c.txnId === credit.txnId); return updatedCredit ? { ...credit, ...updatedCredit } : credit; }) || []; const result: any = { charges: chargesTable, credits: updatedCreditTable, customerBalanceAmount }; yield put(PaymentChargesTableAction.chargesTableDataFetched(result)); return { result: ResultType.SUCCESS }; } // export function* updateLinks(): Generator<Effect, StepResponse> { // const payments = yield* select(paymentTableSelectors.getCharges); // if (Array.isArray(payments)) { // const filterPayments = // payments && // payments.filter( // (payment: CreditChargesPaymentTypes) => payment.linkedPaymentAmount !== '' // ); // if (filterPayments) { // const sources: [] = []; // filterPayments.map((data) => { // const linkSourceData = { // amountConsumed: data.linkedPaymentAmount, // sourceTxn: { // attachments: [], // type: getKeyByValue(data.txnType.txnTypeId.toString()) as TransactionTypes, // // id: `djQuMTo5MTMwMzU4MDQ4ODAyNTI2OjgwMjcxZWRkOGE:${data.txnId.toString()}`, // id: data.txnId.toString(), // header: { // amount: data.amount.toString(), // referenceNumber: data?.referenceNumber?.toString(), // txnDate: data.date // // contact: data.contact, // // deposited: data.deposited, // // currencyInfo: data.currencyInfo // } // } // }; // return sources.push(linkSourceData); // }); // yield put( // genericTxnUpdate({ // links: { // sources, // targets: [] // } // }) // ); // } // } // return { // result: ResultType.SUCCESS // }; // } export function* amountUpdateHandler(): Generator<Effect, StepResponse, string> { // const chargesTable = yield* select(paymentTableSelectors.getCharges); // const creditTable = yield* select(paymentTableSelectors.getCredits); // const totalOfChargesTable = getTotalOfChargesTable(chargesTable); // const totalOfCreditsTable = getTotalOfCreditsTable(creditTable); const totalOfChargesTable = yield* select(paymentTableSelectors.getTotalOfChargesTable); const totalOfCreditsTable = yield* select(paymentTableSelectors.getTotalOfCreditsTable); const updatedAmount = Number(totalOfChargesTable - totalOfCreditsTable) .toFixed(2) .toString(); yield put(genericTxnUpdate({ amount: updatedAmount })); return { result: ResultType.SUCCESS }; } // export function* creditsTransactionsHandler(): Generator<Effect, StepResponse, string> { // const chargesTable = yield* select(paymentTableSelectors.getCharges); // const creditTable = yield* select(paymentTableSelectors.getCredits); // const totalOfChargesTable = getTotalOfChargesTable(chargesTable); // const totalOfCreditsTable = getTotalOfCreditsTable(creditTable); // if (totalOfChargesTable > totalOfCreditsTable) { // const creditNeeded = Math.abs(totalOfChargesTable - totalOfCreditsTable); // if (creditNeeded <= value) { // onPaymentFieldUpdate(txnId, { // linkedPaymentAmount: creditNeeded.toString(), // isLinkedPaymentAmount: true // }); // } else { // onPaymentFieldUpdate(txnId, { // linkedPaymentAmount: value.toString(), // isLinkedPaymentAmount: true // }); // const amountNeeded = creditNeeded - value; // console.log('123', amountNeeded); // //do side effect to cal amount // } // } else { // onPaymentFieldUpdate(txnId, { // linkedPaymentAmount: '' // }); // } // return { result: ResultType.SUCCESS }; // } export function* creditsTransactionsHandlerAmount(): Generator<Effect, StepResponse, string> { const chargesTable = yield* select(paymentTableSelectors.getCharges); const creditTableLines = yield* select(paymentTableSelectors.getCredits); const customerBalanceAmount = yield* select(paymentTableSelectors.getCustomerBalanceAmount); const totalOfChargesTable = yield* select(paymentTableSelectors.getTotalOfChargesTable); const updateCreditTable: CreditChargesPaymentTypes[] = cloneDeep(creditTableLines) || []; const filterExactCredit = updateCreditTable?.filter( (payment) => Number(payment.openBalance) === totalOfChargesTable ); console.log('pqr', totalOfChargesTable); // if (totalOfChargesTable > 0) { if (filterExactCredit.length !== 0) { updateCreditTable.map((item) => { if (item.txnId === filterExactCredit[0].txnId) { item.linkedPaymentAmount = item.openBalance.toString(); } else { item.linkedPaymentAmount = ''; } return item; }); } else { let totalOfChargesTable1: any = totalOfChargesTable; updateCreditTable?.map((credit) => { // const amountCanAdjust = amountToAdjust - credit.openBalance; const amountCanAdjust = totalOfChargesTable1 - credit.openBalance; if (totalOfChargesTable1 > credit.openBalance) { credit.linkedPaymentAmount = credit.openBalance.toString(); // const remainingAmountAfterAdjust = amountCanAdjust - credit.openBalance; // const remainingAmountAfterAdjust = amountCanAdjust; // totalOfChargesTable1 = remainingAmountAfterAdjust; const remainingAmountAfterAdjust = amountCanAdjust; totalOfChargesTable1 = remainingAmountAfterAdjust; } else { credit.linkedPaymentAmount = totalOfChargesTable1 === 0 ? '' : totalOfChargesTable1.toString(); totalOfChargesTable1 = ''; } return credit; }); } // const updatedCreditTable = [...updateCreditTable, ...updateFilterCheckedCredit]; // const updatedCreditTable = // updateCreditTable?.map((credit) => { // const updatedCredit = updateFilterCheckedCredit?.find((c) => c.txnId === credit.txnId); // return updatedCredit ? { ...credit, ...updatedCredit } : credit; // }) || []; const result: any = { charges: chargesTable, credits: updateCreditTable, customerBalanceAmount }; yield put(PaymentChargesTableAction.creditsTableWholeUpdate(result)); // } return { result: ResultType.SUCCESS }; }


Please ignore comments in code, we have logic for when user check the checkbox the amount gets changed due to sideefeect, this is your requirement, but there is also one case in which if user manual update the amount field, and again if he checked the checkbox the amount should not affects, because it as entered manually, what I understand that , we should not run that amount update side effect to achieve this requirement, please correct I am wrong, and suggest some solution