From e999959829077f626a35a20e33a96654af60c4a2 Mon Sep 17 00:00:00 2001 From: John Woodlock Date: Mon, 16 May 2022 15:31:12 +0100 Subject: [PATCH] FINERACT-1596 Goodwill Accounting Changes --- .../common/AccountingConstants.java | 41 +++++++------ .../service/AccountingProcessorHelper.java | 3 +- ...ccrualBasedAccountingProcessorForLoan.java | 2 +- .../CashBasedAccountingProcessorForLoan.java | 12 +++- .../LoanProductToGLAccountMappingHelper.java | 4 ++ .../ProductToGLAccountMappingHelper.java | 31 ++++++---- ...AccountMappingReadPlatformServiceImpl.java | 4 ++ ...ccountMappingWritePlatformServiceImpl.java | 6 ++ .../LoanProductDataValidator.java | 12 +++- ...RefundandRepaymentTypeIntegrationTest.java | 58 ++++++++++++++++++- .../common/loans/LoanProductTestBuilder.java | 2 + 11 files changed, 137 insertions(+), 38 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java index 320ff34fc..d2e8a3372 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java @@ -37,7 +37,7 @@ public final class AccountingConstants { public enum CashAccountsForLoan { FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), LOSSES_WRITTEN_OFF( - 6), TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), INCOME_FROM_RECOVERY(12); + 6), TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), INCOME_FROM_RECOVERY(12), GOODWILL_CREDIT(13); private final Integer value; @@ -75,7 +75,7 @@ public final class AccountingConstants { FUND_SOURCE(1), LOAN_PORTFOLIO(2), INTEREST_ON_LOANS(3), INCOME_FROM_FEES(4), INCOME_FROM_PENALTIES(5), // LOSSES_WRITTEN_OFF(6), INTEREST_RECEIVABLE(7), FEES_RECEIVABLE(8), PENALTIES_RECEIVABLE(9), // - TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), INCOME_FROM_RECOVERY(12); + TRANSFERS_SUSPENSE(10), OVERPAYMENT(11), INCOME_FROM_RECOVERY(12), GOODWILL_CREDIT(13); private final Integer value; @@ -114,17 +114,18 @@ public final class AccountingConstants { FUND_SOURCE("fundSourceAccountId"), LOAN_PORTFOLIO("loanPortfolioAccountId"), INTEREST_ON_LOANS( "interestOnLoanAccountId"), INCOME_FROM_FEES("incomeFromFeeAccountId"), INCOME_FROM_PENALTIES( - "incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), OVERPAYMENT( - "overpaymentLiabilityAccountId"), INTEREST_RECEIVABLE("receivableInterestAccountId"), FEES_RECEIVABLE( - "receivableFeeAccountId"), PENALTIES_RECEIVABLE("receivablePenaltyAccountId"), TRANSFERS_SUSPENSE( - "transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING( - "paymentChannelToFundSourceMappings"), PAYMENT_TYPE( - "paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING( - "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING( - "penaltyToIncomeAccountMappings"), CHARGE_ID( - "chargeId"), INCOME_ACCOUNT_ID( - "incomeAccountId"), INCOME_FROM_RECOVERY( - "incomeFromRecoveryAccountId"); + "incomeFromPenaltyAccountId"), LOSSES_WRITTEN_OFF("writeOffAccountId"), GOODWILL_CREDIT( + "goodwillCreditAccountId"), OVERPAYMENT("overpaymentLiabilityAccountId"), INTEREST_RECEIVABLE( + "receivableInterestAccountId"), FEES_RECEIVABLE("receivableFeeAccountId"), PENALTIES_RECEIVABLE( + "receivablePenaltyAccountId"), TRANSFERS_SUSPENSE( + "transfersInSuspenseAccountId"), PAYMENT_CHANNEL_FUND_SOURCE_MAPPING( + "paymentChannelToFundSourceMappings"), PAYMENT_TYPE( + "paymentTypeId"), FEE_INCOME_ACCOUNT_MAPPING( + "feeToIncomeAccountMappings"), PENALTY_INCOME_ACCOUNT_MAPPING( + "penaltyToIncomeAccountMappings"), CHARGE_ID( + "chargeId"), INCOME_ACCOUNT_ID( + "incomeAccountId"), INCOME_FROM_RECOVERY( + "incomeFromRecoveryAccountId"); private final String value; @@ -146,12 +147,14 @@ public final class AccountingConstants { FUND_SOURCE("fundSourceAccount"), LOAN_PORTFOLIO("loanPortfolioAccount"), INTEREST_ON_LOANS( "interestOnLoanAccount"), INCOME_FROM_FEES("incomeFromFeeAccount"), INCOME_FROM_PENALTIES( - "incomeFromPenaltyAccount"), LOSSES_WRITTEN_OFF("writeOffAccount"), OVERPAYMENT( - "overpaymentLiabilityAccount"), INTEREST_RECEIVABLE("receivableInterestAccount"), FEES_RECEIVABLE( - "receivableFeeAccount"), PENALTIES_RECEIVABLE("receivablePenaltyAccount"), TRANSFERS_SUSPENSE( - "transfersInSuspenseAccount"), INCOME_ACCOUNT_ID("incomeAccount"), INCOME_FROM_RECOVERY( - "incomeFromRecoveryAccount"), LIABILITY_TRANSFER_SUSPENSE( - "liabilityTransferInSuspenseAccount"); + "incomeFromPenaltyAccount"), LOSSES_WRITTEN_OFF("writeOffAccount"), GOODWILL_CREDIT( + "goodwillCreditAccount"), OVERPAYMENT("overpaymentLiabilityAccount"), INTEREST_RECEIVABLE( + "receivableInterestAccount"), FEES_RECEIVABLE("receivableFeeAccount"), PENALTIES_RECEIVABLE( + "receivablePenaltyAccount"), TRANSFERS_SUSPENSE( + "transfersInSuspenseAccount"), INCOME_ACCOUNT_ID( + "incomeAccount"), INCOME_FROM_RECOVERY( + "incomeFromRecoveryAccount"), LIABILITY_TRANSFER_SUSPENSE( + "liabilityTransferInSuspenseAccount"); private final String value; diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java index 9a742a85e..08ea57ecc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java @@ -1114,7 +1114,8 @@ public class AccountingProcessorHelper { if (accountMapping == null) { throw new ProductToGLAccountMappingNotFoundException(PortfolioProductType.LOAN, loanProductId, - AccrualAccountsForLoan.OVERPAYMENT.toString()); + AccrualAccountsForLoan.fromInt(accountMappingTypeId).toString()); + } glAccount = accountMapping.getGlAccount(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java index 537d906ea..ca45de6a7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java @@ -296,7 +296,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } else { if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) { this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, - AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, + AccrualAccountsForLoan.GOODWILL_CREDIT.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal); } else { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java index 67a5024e3..a49e80e51 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/CashBasedAccountingProcessorForLoan.java @@ -62,7 +62,7 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF * Logic for repayments, repayments at disbursement and reversal of Repayments and Repayments at * disbursement ***/ - else if (loanTransactionDTO.getTransactionType().isRepayment() + else if (loanTransactionDTO.getTransactionType().isRepaymentType() || loanTransactionDTO.getTransactionType().isRepaymentAtDisbursement() || loanTransactionDTO.getTransactionType().isChargePayment()) { createJournalEntriesForRepayments(loanDTO, loanTransactionDTO, office); @@ -247,8 +247,14 @@ public class CashBasedAccountingProcessorForLoan implements AccountingProcessorF this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, FinancialActivity.LIABILITY_TRANSFER.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal); } else { - this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.FUND_SOURCE.getValue(), - loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal); + if (loanTransactionDTO.getTransactionType().isGoodwillCredit()) { + this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.GOODWILL_CREDIT.getValue(), + loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal); + + } else { + this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CashAccountsForLoan.FUND_SOURCE.getValue(), + loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, isReversal); + } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java index 2a1859abe..82e5c3ef2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/LoanProductToGLAccountMappingHelper.java @@ -245,6 +245,8 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi // expenses mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId, CashAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), CashAccountsForLoan.LOSSES_WRITTEN_OFF.toString(), changes); + mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId, + CashAccountsForLoan.GOODWILL_CREDIT.getValue(), CashAccountsForLoan.GOODWILL_CREDIT.toString(), changes); // liabilities mergeLoanToLiabilityAccountMappingChanges(element, LoanProductAccountingParams.OVERPAYMENT.getValue(), loanProductId, @@ -286,6 +288,8 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId, AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue(), AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.toString(), changes); + mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId, + AccrualAccountsForLoan.GOODWILL_CREDIT.getValue(), AccrualAccountsForLoan.GOODWILL_CREDIT.toString(), changes); // liabilities mergeLoanToLiabilityAccountMappingChanges(element, LoanProductAccountingParams.OVERPAYMENT.getValue(), loanProductId, diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java index 102bfa4c7..7887156a4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java @@ -61,11 +61,13 @@ public class ProductToGLAccountMappingHelper { public void saveProductToAccountMapping(final JsonElement element, final String paramName, final Long productId, final int placeHolderTypeId, final GLAccountType expectedAccountType, final PortfolioProductType portfolioProductType) { final Long accountId = this.fromApiJsonHelper.extractLongNamed(paramName, element); - final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); + if (accountId != null) { // optional entries may be null + final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); - final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId, - portfolioProductType.getValue(), placeHolderTypeId); - this.accountMappingRepository.saveAndFlush(accountMapping); + final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping(glAccount, productId, + portfolioProductType.getValue(), placeHolderTypeId); + this.accountMappingRepository.saveAndFlush(accountMapping); + } } public void mergeProductToAccountMappingChanges(final JsonElement element, final String paramName, final Long productId, @@ -78,13 +80,20 @@ public class ProductToGLAccountMappingHelper { final ProductToGLAccountMapping accountMapping = this.accountMappingRepository.findCoreProductToFinAccountMapping(productId, portfolioProductType.getValue(), accountTypeId); if (accountMapping == null) { - throw new ProductToGLAccountMappingNotFoundException(portfolioProductType, productId, accountTypeName); - } - if (!accountMapping.getGlAccount().getId().equals(accountId)) { - final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); - changes.put(paramName, accountId); - accountMapping.setGlAccount(glAccount); - this.accountMappingRepository.saveAndFlush(accountMapping); + ArrayList optionalProductToGLAccountMappingEntries = new ArrayList(); + optionalProductToGLAccountMappingEntries.add("goodwillCreditAccountId"); + if (optionalProductToGLAccountMappingEntries.contains(paramName)) { + saveProductToAccountMapping(element, paramName, productId, accountTypeId, expectedAccountType, portfolioProductType); + } else { + throw new ProductToGLAccountMappingNotFoundException(portfolioProductType, productId, accountTypeName); + } + } else { + if (!accountMapping.getGlAccount().getId().equals(accountId)) { + final GLAccount glAccount = getAccountByIdAndType(paramName, expectedAccountType, accountId); + changes.put(paramName, accountId); + accountMapping.setGlAccount(glAccount); + this.accountMappingRepository.saveAndFlush(accountMapping); + } } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java index 2a4629c55..5c9122534 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java @@ -132,6 +132,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(LoanProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), gLAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.LOSSES_WRITTEN_OFF)) { accountMappingDetails.put(LoanProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), gLAccountData); + } else if (glAccountForLoan.equals(CashAccountsForLoan.GOODWILL_CREDIT)) { + accountMappingDetails.put(LoanProductAccountingDataParams.GOODWILL_CREDIT.getValue(), gLAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.OVERPAYMENT)) { accountMappingDetails.put(LoanProductAccountingDataParams.OVERPAYMENT.getValue(), gLAccountData); } else if (glAccountForLoan.equals(CashAccountsForLoan.INCOME_FROM_RECOVERY)) { @@ -166,6 +168,8 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(LoanProductAccountingDataParams.TRANSFERS_SUSPENSE.getValue(), gLAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.LOSSES_WRITTEN_OFF)) { accountMappingDetails.put(LoanProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), gLAccountData); + } else if (glAccountForLoan.equals(AccrualAccountsForLoan.GOODWILL_CREDIT)) { + accountMappingDetails.put(LoanProductAccountingDataParams.GOODWILL_CREDIT.getValue(), gLAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INTEREST_RECEIVABLE)) { accountMappingDetails.put(LoanProductAccountingDataParams.INTEREST_RECEIVABLE.getValue(), gLAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.FEES_RECEIVABLE)) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java index 9b83d02af..b48dafb27 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -90,6 +90,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId, CashAccountsForLoan.LOSSES_WRITTEN_OFF.getValue()); + this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, + LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId, + CashAccountsForLoan.GOODWILL_CREDIT.getValue()); // liabilities this.loanProductToGLAccountMappingHelper.saveLoanToLiabilityAccountMapping(element, @@ -139,6 +142,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId, AccrualAccountsForLoan.LOSSES_WRITTEN_OFF.getValue()); + this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, + LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), loanProductId, + AccrualAccountsForLoan.GOODWILL_CREDIT.getValue()); // liabilities this.loanProductToGLAccountMappingHelper.saveLoanToLiabilityAccountMapping(element, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index d428672f8..5087aea6d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -77,7 +77,7 @@ public final class LoanProductDataValidator { LoanProductAccountingParams.INTEREST_ON_LOANS.getValue(), LoanProductAccountingParams.INTEREST_RECEIVABLE.getValue(), LoanProductAccountingParams.LOAN_PORTFOLIO.getValue(), LoanProductAccountingParams.OVERPAYMENT.getValue(), LoanProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), - LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), + LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), LoanProductAccountingParams.PENALTIES_RECEIVABLE.getValue(), LoanProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), LoanProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(), LoanProductAccountingParams.INCOME_FROM_RECOVERY.getValue(), LoanProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(), LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME, @@ -590,6 +590,11 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) .notNull().integerGreaterThanZero(); + final Long goodwillCreditAccountId = this.fromApiJsonHelper + .extractLongNamed(LoanProductAccountingParams.GOODWILL_CREDIT.getValue(), element); + baseDataValidator.reset().parameter(LoanProductAccountingParams.GOODWILL_CREDIT.getValue()).value(goodwillCreditAccountId) + .ignoreIfNull().integerGreaterThanZero(); + final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.OVERPAYMENT.getValue(), element); baseDataValidator.reset().parameter(LoanProductAccountingParams.OVERPAYMENT.getValue()).value(overpaymentAccountId).notNull() @@ -1427,6 +1432,11 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue()).value(writeOffAccountId) .ignoreIfNull().integerGreaterThanZero(); + final Long goodwillCreditAccountId = this.fromApiJsonHelper + .extractLongNamed(LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), element); + baseDataValidator.reset().parameter(LoanProductAccountingParams.GOODWILL_CREDIT.getValue()).value(goodwillCreditAccountId) + .ignoreIfNull().integerGreaterThanZero(); + final Long overpaymentAccountId = this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.OVERPAYMENT.getValue(), element); baseDataValidator.reset().parameter(LoanProductAccountingParams.OVERPAYMENT.getValue()).value(overpaymentAccountId).ignoreIfNull() diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest.java index 61508badb..ebc94ded9 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest.java @@ -85,7 +85,7 @@ public class ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest { private void disburseLoanOfAccountingRule(final String accountingType) { final String principal = "12000.00"; final String submitApproveDisburseDate = "01 January 2022"; - this.disbursedLoanID = fromStartToDisburseLoan(submitApproveDisburseDate, principal, ACCRUAL_PERIODIC, assetAccount, incomeAccount, + this.disbursedLoanID = fromStartToDisburseLoan(submitApproveDisburseDate, principal, accountingType, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); } @@ -313,7 +313,7 @@ public class ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest { } @Test - public void newCreditBalanceRefundCreatesCorrectJournalEntriesForCashAccrualsTest() { + public void newCreditBalanceRefundCreatesCorrectJournalEntriesForCashAccountingTest() { disburseLoanOfAccountingRule(CASH_BASED); HashMap loanStatusHashMap = makeRepayment("08 January 2022", 20000.00f); // overpayment @@ -353,4 +353,58 @@ public class ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest { Assertions.assertTrue(Boolean.TRUE.equals(isTypeCorrect), "Not " + repaymentTransactionType); } + @Test + public void newGoodwillCreditCreatesCorrectJournalEntriesForPeriodicAccrualsTest() { + + disburseLoanOfAccountingRule(ACCRUAL_PERIODIC); + HashMap loanSummaryMap = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, disbursedLoanID); + + // pay off all of principal, interest (no fees or penalties) + final Float principalOutstanding = (Float) loanSummaryMap.get("principalOutstanding"); + final Float interestOutstanding = (Float) loanSummaryMap.get("interestOutstanding"); + final Float totalOutstanding = (Float) loanSummaryMap.get("totalOutstanding"); + final Float overpaidAmount = 159.00f; + final Float goodwillAmount = totalOutstanding + overpaidAmount; + final String goodwillDate = "09 January 2022"; + HashMap loanStatusHashMap = this.loanTransactionHelper.makeRepaymentTypePayment(GOODWILL_CREDIT, goodwillDate, goodwillAmount, + this.disbursedLoanID); + + // only a single credit for principal and interest as test sets up same GL account for both (summed up) + this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, goodwillDate, + new JournalEntry(totalOutstanding, JournalEntry.TransactionType.CREDIT)); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, goodwillDate, + new JournalEntry(overpaidAmount, JournalEntry.TransactionType.CREDIT)); + this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, goodwillDate, + new JournalEntry(goodwillAmount, JournalEntry.TransactionType.DEBIT)); + + } + + @Test + public void newGoodwillCreditCreatesCorrectJournalEntriesForCashAccountingTest() { + + disburseLoanOfAccountingRule(CASH_BASED); + HashMap loanSummaryMap = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, disbursedLoanID); + + // pay off all of principal, interest (no fees or penalties) + final Float principalOutstanding = (Float) loanSummaryMap.get("principalOutstanding"); + final Float interestOutstanding = (Float) loanSummaryMap.get("interestOutstanding"); + final Float totalOutstanding = (Float) loanSummaryMap.get("totalOutstanding"); + final Float overpaidAmount = 159.00f; + final Float goodwillAmount = totalOutstanding + overpaidAmount; + final String goodwillDate = "09 January 2022"; + HashMap loanStatusHashMap = this.loanTransactionHelper.makeRepaymentTypePayment(GOODWILL_CREDIT, goodwillDate, goodwillAmount, + this.disbursedLoanID); + + this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, goodwillDate, + new JournalEntry(principalOutstanding, JournalEntry.TransactionType.CREDIT)); + this.journalEntryHelper.checkJournalEntryForIncomeAccount(incomeAccount, goodwillDate, + new JournalEntry(interestOutstanding, JournalEntry.TransactionType.CREDIT)); + + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, goodwillDate, + new JournalEntry(overpaidAmount, JournalEntry.TransactionType.CREDIT)); + this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, goodwillDate, + new JournalEntry(goodwillAmount, JournalEntry.TransactionType.DEBIT)); + + } + } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java index 593a0d7dc..3271c04cc 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java @@ -405,6 +405,7 @@ public class LoanProductTestBuilder { if (this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) { final String ID = this.accountList[i].getAccountID().toString(); map.put("writeOffAccountId", ID); + map.put("goodwillCreditAccountId", ID); } if (this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) { final String ID = this.accountList[i].getAccountID().toString(); @@ -437,6 +438,7 @@ public class LoanProductTestBuilder { if (this.accountList[i].getAccountType().equals(Account.AccountType.EXPENSE)) { final String ID = this.accountList[i].getAccountID().toString(); map.put("writeOffAccountId", ID); + map.put("goodwillCreditAccountId", ID); } if (this.accountList[i].getAccountType().equals(Account.AccountType.LIABILITY)) { final String ID = this.accountList[i].getAccountID().toString();