From 20fe11e60dafb5638d58d9c43fc7545b094c25bf Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Fri, 21 Aug 2026 12:57:02 +0530 Subject: [PATCH] fix(deposits): correct FD reinvestment on maturity and unblock product edit submission Reinvestment on maturity was creating the new deposit with a zero balance, because it sourced the amount from activateWithBalance() -- a guard that deliberately returns zero to stop a manually opened FD from auto-crediting itself. It now carries over what reInvest() actually put on the new account. Both legs of the renewal are also posted as an internal transfer, so the funds move through the transfer-suspense GL instead of appearing to leave and re-enter the cash vault. Reinvested accounts no longer fall back to a 0% rate when no chart slab covers the tenure, and a senior-citizen depositor keeps their premium rate on the renewed term. Adds a reinvestment trail: the new account records which account it came from, and the account view resolves both directions of the link so either side can be traced. Also fixes product edit submission, which failed outright for any product carrying payment-channel GL mappings: the pending-edit mapper read "glAccountId" while the payload carries "fundSourceAccountId" (and "incomeAccountId" for fee/penalty arrays), leaving gl_account_id null and tripping the satellite table's NOT NULL constraint. Co-Authored-By: Claude Opus 5 (1M context) --- .../domain/DepositAccountDomainService.java | 8 ++++ .../DepositAccountDomainServiceJpa.java | 43 ++++++++++++++++-- .../savings/domain/FixedDepositAccount.java | 20 ++++++++- ...DepositAccountReadPlatformServiceImpl.java | 40 +++++++++++++++++ ...WritePlatformServiceJpaRepositoryImpl.java | 14 +++++- .../db/changelog/tenant/changelog-tenant.xml | 1 + ...70_add_reinvested_from_to_deposit_term.xml | 30 +++++++++++++ .../savings/data/FixedDepositAccountData.java | 21 +++++++++ .../DepositAccountTermAndPreClosure.java | 15 +++++++ .../domain/ProductPendingEditFieldMapper.java | 44 ++++++++++++++----- 10 files changed, 217 insertions(+), 19 deletions(-) create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0470_add_reinvested_from_to_deposit_term.xml diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java index 3b29b900c..aff6abadd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainService.java @@ -35,6 +35,14 @@ public interface DepositAccountDomainService { SavingsAccountTransaction handleFDDeposit(FixedDepositAccount account, DateTimeFormatter fmt, LocalDate transactionDate, BigDecimal transactionAmount, PaymentDetail paymentDetail); + /** + * As above, but lets the caller declare the deposit as an internal account-to-account movement rather + * than cash taken over the counter. Used by reinvestment on maturity, where the funds come straight + * from the matured deposit and must post against the transfer-suspense GL instead of cash/vault. + */ + SavingsAccountTransaction handleFDDeposit(FixedDepositAccount account, DateTimeFormatter fmt, LocalDate transactionDate, + BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isAccountTransfer); + SavingsAccountTransaction handleRDDeposit(RecurringDepositAccount account, DateTimeFormatter fmt, LocalDate transactionDate, BigDecimal transactionAmount, PaymentDetail paymentDetail, boolean isRegularTransaction); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java index 2ae1124a7..05f48a177 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountDomainServiceJpa.java @@ -86,7 +86,19 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi public SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, final boolean applyWithdrawFee, final boolean isRegularTransaction) { - boolean isAccountTransfer = false; + final boolean isAccountTransfer = false; + return handleWithdrawal(account, fmt, transactionDate, transactionAmount, paymentDetail, applyWithdrawFee, isRegularTransaction, + isAccountTransfer); + } + + /** + * As above, but lets the caller declare the withdrawal as an internal account-to-account movement + * rather than cash paid out over the counter. Used by reinvestment on maturity, where the balance + * moves straight into the new deposit and must post against the transfer-suspense GL, not cash/vault. + */ + private SavingsAccountTransaction handleWithdrawal(final SavingsAccount account, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, + final boolean applyWithdrawFee, final boolean isRegularTransaction, final boolean isAccountTransfer) { boolean isInterestTransfer = false; boolean isWithdrawBalance = false; final boolean backdatedTxnsAllowedTill = false; @@ -100,7 +112,15 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi @Override public SavingsAccountTransaction handleFDDeposit(final FixedDepositAccount account, final DateTimeFormatter fmt, final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail) { - boolean isAccountTransfer = false; + final boolean isAccountTransfer = false; + return handleFDDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, isAccountTransfer); + } + + @Transactional + @Override + public SavingsAccountTransaction handleFDDeposit(final FixedDepositAccount account, final DateTimeFormatter fmt, + final LocalDate transactionDate, final BigDecimal transactionAmount, final PaymentDetail paymentDetail, + final boolean isAccountTransfer) { boolean isRegularTransaction = false; final boolean backdatedTxnsAllowedTill = false; return this.savingsAccountDomainService.handleDeposit(account, fmt, transactionDate, transactionAmount, paymentDetail, @@ -207,8 +227,11 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); this.savingsAccountRepository.saveAndFlush(reinvestedDeposit); autoGenerateAccountNumber(reinvestedDeposit); + // Renewal is an internal movement into the new deposit, not cash paid out -- post it + // against the transfer-suspense GL rather than crediting cash/vault. + final boolean isInternalTransferLeg = true; final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(), - paymentDetail, false, isRegularTransaction); + paymentDetail, false, isRegularTransaction, isInternalTransferLeg); savingsTransactionId = withdrawal.getId(); } else if (onClosureType.isTransferToSavings()) { final Long toSavingsId = command.longValueOfParameterNamed(toSavingsAccountIdParamName); @@ -276,9 +299,17 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi this.savingsAccountRepository.saveAndFlush(reinvestedDeposit); autoGenerateAccountNumber(reinvestedDeposit); + // Renewal moves the balance internally into the new deposit -- no cash leaves the branch -- + // so this leg is posted as an account transfer (against the transfer-suspense GL) rather + // than as a counter withdrawal, which would wrongly credit the cash/vault account. + final boolean isInternalTransferLeg = true; final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, reInvestAmount, paymentDetail, - false, isRegularTransaction); + false, isRegularTransaction, isInternalTransferLeg); savingsTransactionId = withdrawal.getId(); + // handleWithdrawal() posts this withdrawal's own journal entries, so it has to be marked as + // already accounted for -- exactly as the transfer-to-savings branch below does -- or the + // postJournalEntries() call at the end of this method posts it a second time. + updateAlreadyPostedTransactions(existingTransactionIds, account); if (onClosureType.isReinvestPrincipalAndInterest()) { account.updateClosedStatus(); @@ -304,6 +335,7 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(), paymentDetail, false, isRegularTransaction); savingsTransactionId = withdrawal.getId(); + updateAlreadyPostedTransactions(existingTransactionIds, account); } // if(!processMaturityInstructionOnly) @@ -474,6 +506,9 @@ public class DepositAccountDomainServiceJpa implements DepositAccountDomainServi final SavingsAccountTransaction withdrawal = this.handleWithdrawal(account, fmt, closedDate, account.getAccountBalance(), paymentDetail, false, isRegularTransaction); savingsTransactionId = withdrawal.getId(); + // handleWithdrawal() posts this withdrawal's journal entries itself, so mark it as already + // accounted for or the postJournalEntries() call below posts it a second time. + updateAlreadyPostedTransactions(existingTransactionIds, account); } account.prematureClosure(user, command, changes); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java index 642fb8f08..95cff6e3f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java @@ -829,7 +829,12 @@ public class FixedDepositAccount extends SavingsAccount { final SavingsInterestCalculationDaysInYearType daysInYearType = SavingsInterestCalculationDaysInYearType .fromInt(this.interestCalculationDaysInYearType); final BigDecimal minRequiredOpeningBalance = null; - final BigDecimal interestRate = BigDecimal.ZERO; + // Falls back to the product's own flat rate -- same fallback DepositAccountAssembler uses + // for a brand-new account -- so a product with no chart slab covering this tenure/amount + // doesn't silently reinvest the customer at 0%. getEffectiveInterestRateAsFraction() still + // re-prices off newChart (the product's CURRENT chart, reflecting any edits made since the + // original account was opened) whenever a matching slab exists. + final BigDecimal interestRate = product.nominalAnnualInterestRate(); final Set savingsAccountCharges = null; final SavingsPeriodFrequencyType lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(this.lockinPeriodFrequencyType); final Integer lockinPeriodFrequency = this.lockinPeriodFrequency; @@ -845,6 +850,15 @@ public class FixedDepositAccount extends SavingsAccount { newAccountTermAndPreClosure.updateAccountReference(reInvestedAccount); newChart.updateDepositAccountReference(reInvestedAccount); + // Carry the senior-citizen premium forward -- without this, a senior-citizen depositor's + // reinvested FD silently drops back to the standard rate on the new term. + reInvestedAccount.setSeniorCitizen(this.isSeniorCitizen()); + reInvestedAccount.setSeniorCitizenInterestRate(this.getSeniorCitizenInterestRate()); + + // Track which account this was reinvested from, so staff/UI can trace the two accounts to + // each other instead of the link disappearing the moment the old one closes. + newAccountTermAndPreClosure.setReinvestedFromSavingsId(this.getId()); + return reInvestedAccount; } @@ -886,7 +900,9 @@ public class FixedDepositAccount extends SavingsAccount { @Override public void loadLazyCollections() { super.loadLazyCollections(); - this.chart.getId(); + if (this.chart != null) { + this.chart.getId(); + } } public BigDecimal getDepositAmount() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java index 61d8afdf2..130b1fb60 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java @@ -321,6 +321,9 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead ((org.apache.fineract.portfolio.savings.data.RecurringDepositAccountData) account).setJointHolders(jointHolders); } } + if (account instanceof org.apache.fineract.portfolio.savings.data.FixedDepositAccountData) { + applyReinvestmentLinks((org.apache.fineract.portfolio.savings.data.FixedDepositAccountData) account, accountId); + } } return account; @@ -329,6 +332,43 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead } } + /** + * Populates the "reinvested from" / "reinvested into" links on a Fixed Deposit account, so the + * account-view page can show either direction of a "Re-Invest Maturity Amount"/"Re-Invest + * Principal Only" closure. There is only one column (reinvested_from_savings_id, set on the NEW + * account) -- "reinvested into" is found on the OLD account by reverse-looking up whichever row + * points at it. + */ + private void applyReinvestmentLinks(final org.apache.fineract.portfolio.savings.data.FixedDepositAccountData result, + final Long accountId) { + final String fromSql = "select src.id as id, src.account_no as accountNo " + + "from m_deposit_account_term_and_preclosure t " + + "join m_savings_account src on src.id = t.reinvested_from_savings_id " + "where t.savings_account_id = ?"; + final String intoSql = "select dest.id as id, dest.account_no as accountNo " + + "from m_deposit_account_term_and_preclosure t " + "join m_savings_account dest on dest.id = t.savings_account_id " + + "where t.reinvested_from_savings_id = ?"; + + Long fromId = null; + String fromNo = null; + final java.util.List> fromRows = this.jdbcTemplate.queryForList(fromSql, accountId); + if (!fromRows.isEmpty()) { + fromId = ((Number) fromRows.get(0).get("id")).longValue(); + fromNo = (String) fromRows.get(0).get("accountNo"); + } + + Long intoId = null; + String intoNo = null; + final java.util.List> intoRows = this.jdbcTemplate.queryForList(intoSql, accountId); + if (!intoRows.isEmpty()) { + intoId = ((Number) intoRows.get(0).get("id")).longValue(); + intoNo = (String) intoRows.get(0).get("accountNo"); + } + + if (fromId != null || intoId != null) { + result.setReinvestmentLinks(fromId, fromNo, intoId, intoNo); + } + } + private java.util.List> retrieveJointHolders(final Long accountId) { final String sql = "SELECT jh.id, jh.client_id, jh.cif_number, jh.display_name, " + "jh.share_percentage, jh.signing_authority, jh.role, jh.is_primary, jh.is_mandatory " diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java index 79a9e8aa8..fe4c4a6c8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java @@ -1404,16 +1404,26 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo if (changes.get("reinvestedDepositId") != null) { Long reinvestedDepositId = (Long) changes.get("reinvestedDepositId"); - Money amountForDeposit = account.activateWithBalance(); final FixedDepositAccount reinvestAccount = (FixedDepositAccount) this.depositAccountAssembler .assembleFrom(reinvestedDepositId, DepositAccountType.FIXED_DEPOSIT); + // activateWithBalance() always returns zero here -- it exists to stop a manually + // opened FD from auto-crediting itself at activation (funding is supposed to be a + // separate, clerk-posted deposit). A reinvestment isn't that: the balance was just + // withdrawn from the matured account and needs to land in the new one as part of + // this same automated closure, so it has to come from what reInvest() actually + // carried over onto the new account, not from that unrelated activation guard. + Money amountForDeposit = Money.of(reinvestAccount.getCurrency(), reinvestAccount.getDepositAmount()); Money activationChargeAmount = getActivationCharge(reinvestAccount); if (activationChargeAmount.isGreaterThanZero()) { payActivationCharge(reinvestAccount); amountForDeposit = amountForDeposit.plus(activationChargeAmount); } + // Matching internal-transfer leg for the withdrawal the closure just made on the + // matured account: the funds never touch cash, so this posts against the + // transfer-suspense GL instead of debiting cash/vault. + final boolean isInternalTransferLeg = true; this.depositAccountDomainService.handleFDDeposit(reinvestAccount, fmt, fdAccount.maturityDate(), - amountForDeposit.getAmount(), null); + amountForDeposit.getAmount(), null, isInternalTransferLeg); } } } else if (depositAccountType.isRecurringDeposit()) { diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index d2407861e..22f8bf57a 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -537,4 +537,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0470_add_reinvested_from_to_deposit_term.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0470_add_reinvested_from_to_deposit_term.xml new file mode 100644 index 000000000..9e5c62f6c --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0470_add_reinvested_from_to_deposit_term.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java index 8bea46e3a..d67d12baf 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java @@ -138,6 +138,23 @@ public final class FixedDepositAccountData extends DepositAccountData { private Boolean isSeniorCitizen; private java.math.BigDecimal seniorCitizenInterestRate; + // Reinvestment trail: which account this one was reinvested FROM (set on the new account) and + // which account it was reinvested INTO (derived by reverse lookup on the old account), so staff + // can trace a "Re-Invest Maturity Amount"/"Re-Invest Principal Only" closure both directions + // instead of the link disappearing once the old account closes. + private Long reinvestedFromAccountId; + private String reinvestedFromAccountNo; + private Long reinvestedIntoAccountId; + private String reinvestedIntoAccountNo; + + public void setReinvestmentLinks(final Long reinvestedFromAccountId, final String reinvestedFromAccountNo, + final Long reinvestedIntoAccountId, final String reinvestedIntoAccountNo) { + this.reinvestedFromAccountId = reinvestedFromAccountId; + this.reinvestedFromAccountNo = reinvestedFromAccountNo; + this.reinvestedIntoAccountId = reinvestedIntoAccountId; + this.reinvestedIntoAccountNo = reinvestedIntoAccountNo; + } + // CBS account-level fields (stored per-account in m_savings_account) private String accountStatus; private String modeOfOperation; @@ -329,6 +346,10 @@ public final class FixedDepositAccountData extends DepositAccountData { this.jointHolders = src.jointHolders; this.isSeniorCitizen = src.isSeniorCitizen; this.seniorCitizenInterestRate = src.seniorCitizenInterestRate; + this.reinvestedFromAccountId = src.reinvestedFromAccountId; + this.reinvestedFromAccountNo = src.reinvestedFromAccountNo; + this.reinvestedIntoAccountId = src.reinvestedIntoAccountId; + this.reinvestedIntoAccountNo = src.reinvestedIntoAccountNo; this.subStatus = src.subStatus; this.reasonForBlock = src.reasonForBlock; this.lastActiveTransactionDate = src.lastActiveTransactionDate; diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java index eb2fc6022..37b3db921 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountTermAndPreClosure.java @@ -89,6 +89,21 @@ public class DepositAccountTermAndPreClosure extends AbstractPersistableCustom extractGlArrayMappings(final JsonCommand command, final ProductPendingEdit edit) { final List mappings = new java.util.ArrayList<>(); boolean any = false; any |= extractGlArray(command, edit, "paymentChannelToFundSourceMappings", ProductPendingEditGlMapping.TYPE_PAYMENT_CHANNEL, - "paymentTypeId", mappings); - any |= extractGlArray(command, edit, "feeToIncomeAccountMappings", ProductPendingEditGlMapping.TYPE_FEE, "chargeId", mappings); + "paymentTypeId", PAYMENT_CHANNEL_GL_FIELD, mappings); + any |= extractGlArray(command, edit, "feeToIncomeAccountMappings", ProductPendingEditGlMapping.TYPE_FEE, "chargeId", + INCOME_ACCOUNT_GL_FIELD, mappings); any |= extractGlArray(command, edit, "penaltyToIncomeAccountMappings", ProductPendingEditGlMapping.TYPE_PENALTY, "chargeId", - mappings); + INCOME_ACCOUNT_GL_FIELD, mappings); return any ? mappings : null; } private static boolean extractGlArray(final JsonCommand command, final ProductPendingEdit edit, final String paramName, - final String mappingType, final String idFieldName, final List out) { + final String mappingType, final String idFieldName, final String glFieldName, + final List out) { if (!command.hasParameter(paramName)) { return false; } @@ -625,8 +635,13 @@ public final class ProductPendingEditFieldMapper { if (raw != null && raw.isJsonArray()) { for (final com.google.gson.JsonElement el : raw.getAsJsonArray()) { final JsonObject o = el.getAsJsonObject(); - final Long idFieldValue = o.has(idFieldName) ? o.get(idFieldName).getAsLong() : null; - final Long glAccountId = o.has("glAccountId") ? o.get("glAccountId").getAsLong() : null; + final Long idFieldValue = longOrNull(o, idFieldName); + final Long glAccountId = longOrNull(o, glFieldName); + // A mapping is only meaningful with both halves present; keeping a half-filled row + // would fail the satellite table's NOT NULL constraint and abort the whole edit. + if (idFieldValue == null || glAccountId == null) { + continue; + } final Long paymentTypeId = "paymentTypeId".equals(idFieldName) ? idFieldValue : null; final Long chargeId = "chargeId".equals(idFieldName) ? idFieldValue : null; out.add(new ProductPendingEditGlMapping(edit, mappingType, paymentTypeId, chargeId, glAccountId)); @@ -635,15 +650,22 @@ public final class ProductPendingEditFieldMapper { return true; } + private static Long longOrNull(final JsonObject o, final String fieldName) { + final com.google.gson.JsonElement el = o.get(fieldName); + return el == null || el.isJsonNull() ? null : el.getAsLong(); + } + public static void writeGlArrayMappingsToJson(final JsonObject root, final List mappings) { writeGlArrayGroup(root, mappings, ProductPendingEditGlMapping.TYPE_PAYMENT_CHANNEL, "paymentChannelToFundSourceMappings", - "paymentTypeId"); - writeGlArrayGroup(root, mappings, ProductPendingEditGlMapping.TYPE_FEE, "feeToIncomeAccountMappings", "chargeId"); - writeGlArrayGroup(root, mappings, ProductPendingEditGlMapping.TYPE_PENALTY, "penaltyToIncomeAccountMappings", "chargeId"); + "paymentTypeId", PAYMENT_CHANNEL_GL_FIELD); + writeGlArrayGroup(root, mappings, ProductPendingEditGlMapping.TYPE_FEE, "feeToIncomeAccountMappings", "chargeId", + INCOME_ACCOUNT_GL_FIELD); + writeGlArrayGroup(root, mappings, ProductPendingEditGlMapping.TYPE_PENALTY, "penaltyToIncomeAccountMappings", "chargeId", + INCOME_ACCOUNT_GL_FIELD); } private static void writeGlArrayGroup(final JsonObject root, final List mappings, final String type, - final String paramName, final String idFieldName) { + final String paramName, final String idFieldName, final String glFieldName) { final JsonArray array = new JsonArray(); for (final ProductPendingEditGlMapping mapping : mappings) { if (!type.equals(mapping.getMappingType())) { @@ -652,7 +674,7 @@ public final class ProductPendingEditFieldMapper { final JsonObject entry = new JsonObject(); final Long idFieldValue = "paymentTypeId".equals(idFieldName) ? mapping.getPaymentTypeId() : mapping.getChargeId(); entry.addProperty(idFieldName, idFieldValue); - entry.addProperty("glAccountId", mapping.getGlAccountId()); + entry.addProperty(glFieldName, mapping.getGlAccountId()); array.add(entry); } root.add(paramName, array);